/* httpprocwatchd provides HTTP interface to a list of process' statuses formatted as JSON. Copyright (c) 2021 Alexander "Arav" Andreev This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "context" "encoding/json" "errors" "flag" "fmt" "io/ioutil" "log" "net/http" "os" "os/exec" "os/signal" "syscall" "time" ) // ErrPgrepNotFound occurs when pgrep program is not found. var ErrPgrepNotFound = errors.New("pgrep not found") // Configuration holds a list of process names to be tracked and a listen address. type Configuration struct { ListenAddress string `json:"listen_address"` Processes []string `json:"processes"` } // LoadConfiguration loads configuration from a JSON file. func LoadConfiguration(path string) (conf *Configuration, err error) { conf = &Configuration{} file, err := ioutil.ReadFile(path) if err != nil { return nil, err } if err := json.Unmarshal(file, conf); err != nil { return nil, err } return conf, nil } // ProcessList is a map of processes' statuses. type ProcessList map[string]bool // NewProcessList returns a ProcessList with initialised map. func NewProcessList() *ProcessList { pl := make(ProcessList) return &pl } // AddProcess appends a process to a ProcessList func (pl *ProcessList) AddProcess(name string, isup bool) { (*pl)[name] = isup } // IsProcessUp returns true if process is up. Uses pgrep to get PID of a process // and if a process is not working, then pgrep returns nothing. func IsProcessUp(name string) (bool, error) { pgrep := exec.Command("pgrep", "-nf", name) out, err := pgrep.CombinedOutput() if err != nil { return false, err } if len(out) == 0 { return false, nil } return true, nil } // AreProcessesUp handles a GET /processes request and sends back status of given // processes. func AreProcessesUp(processes *[]string) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { proclist := NewProcessList() for _, proc := range *processes { isup, _ := IsProcessUp(proc) proclist.AddProcess(proc, isup) } w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(proclist) } else { w.WriteHeader(http.StatusMethodNotAllowed) w.Header().Add("Allow", "GET") } } } func version() { fmt.Println("httpprocwatchd ver. 1.0") fmt.Println("Copyright (c) 2021 Alexander \"Arav\" Andreev ") fmt.Println("This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.") fmt.Println("This is free software, and you are welcome to redistribute it") fmt.Println("under certain conditions; type `show c' for details.") } func main() { log.SetFlags(0) configPath := flag.String("config", "processes.json", "path to configuration file") showVersion := flag.Bool("version", false, "show version") flag.Parse() if *showVersion { version() return } if _, err := exec.LookPath("pgrep"); err != nil { log.Fatalln(ErrPgrepNotFound) } conf, err := LoadConfiguration(*configPath) if err != nil { log.Fatalf("Cannot load configuration file: %s\n", err) } router := http.NewServeMux() router.HandleFunc("/processes", AreProcessesUp(&conf.Processes)) srv := &http.Server{ Addr: conf.ListenAddress, Handler: router, } done := make(chan os.Signal, 1) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("ListenAndServe: %s\n", err) } }() log.Printf("httpprocwatchd is running on \"%s\".", conf.ListenAddress) <-done log.Printf("Shutting down... ") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("%s\n", err) } log.Println("Done.") }