From df3714d071b6f514cd0694698be4989d7c1e3816 Mon Sep 17 00:00:00 2001 From: "Alexander \"Arav\" Andreev" Date: Fri, 15 Dec 2023 04:00:26 +0400 Subject: [PATCH] Version 3.0.0. Throwed out everything not used but for whatever reason added previously. --- .gitignore | 4 +- Makefile | 15 ++++--- README.md | 46 -------------------- build/archlinux/PKGBUILD | 19 +++----- configs/config.example.conf | 3 -- configs/config.example.json | 6 +++ configuration.go | 87 ++++--------------------------------- go.mod | 2 +- http.go | 24 ++++++++++ httpserver.go | 86 ------------------------------------ main.go | 78 ++++++++++----------------------- proc.go => process.go | 23 +++++++++- processlist.go | 32 -------------- 13 files changed, 101 insertions(+), 324 deletions(-) delete mode 100644 README.md delete mode 100644 configs/config.example.conf create mode 100644 configs/config.example.json create mode 100644 http.go delete mode 100644 httpserver.go rename proc.go => process.go (61%) delete mode 100644 processlist.go diff --git a/.gitignore b/.gitignore index 5a23055..e11fa7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .vscode -*.conf -!config.example.conf +*.json +!config.example.json httpprocprobed \ No newline at end of file diff --git a/Makefile b/Makefile index c3bc600..930db28 100644 --- a/Makefile +++ b/Makefile @@ -2,26 +2,29 @@ TARGET=httpprocprobed SYSDDIR_=${shell pkg-config systemd --variable=systemdsystemunitdir} SYSDDIR=${SYSDDIR_:/%=%} -DESTDIR=/ +DESTDIR:= -LDFLAGS=-ldflags "-s -w" -tags netgo +VERSION:=3.0.0 + +FLAGS:=-buildmode=pie -modcacherw -mod=readonly -trimpath +LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}" -tags netgo SOURCES := ${wildcard *.go} -all: ${TARGET} +.PHONY: install uninstall clean ${TARGET}: ${SOURCES} - go build ${LDFLAGS} + go build ${LDFLAGS} ${FLAGS} install: install -Dm 0755 ${TARGET} ${DESTDIR}usr/bin/${TARGET} - install -Dm 0644 configs/config.example.conf ${DESTDIR}etc/${TARGET}.conf + install -Dm 0644 configs/config.example.json ${DESTDIR}etc/${TARGET}.json install -Dm 0644 LICENSE ${DESTDIR}usr/share/licenses/${TARGET}/LICENSE install -Dm 0644 init/systemd.service ${DESTDIR}${SYSDDIR}/${TARGET}.service uninstall: rm ${DESTDIR}usr/bin/${TARGET} - rm ${DESTDIR}usr/share/licenses/${TARGET}/LICENSE + rm ${DESTDIR}usr/share/licenses/${TARGET} rm ${DESTDIR}${SYSDDIR}/${TARGET}.service clean: diff --git a/README.md b/README.md deleted file mode 100644 index 27ac396..0000000 --- a/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# httpprocprobed Ver 2.0.1 - -License: MIT+NIGGER. - -This utility provides a HTTP `/processes` GET endpoint that returns a list of -processes, and if they are currently running or not. - -There are currently three output formats available: JSON, XML, and plain text. - -JSON is a default format if `Accept` header didn't provided, or did with -value `application/json`. Its form is `{"process":true|false, ...}`. - -XML is provided if `Accept: application/xml` header was given. Its form is -`true|false...`. - -Plain text is provided if `Accept: text/plain` header was given. Its form is a -comma separated list of ONLY running process' names. - -Configuration file is a simple `key = value` storage consisting of -`listen_address` string field in form `"[]:"`. `indented_output` -boolean in form `true|false`, to enable indentation of JSON and XML output. -And `processes` is a space separated array of process names. - -## Installation - -### Manually - -Run these commands one after the other. - -```console -$ make -$ make install -``` - -In order to uninstall run these commands: - -```console -# systemctl stop httpprocprobed -# systemctl disable httpprocprobed -$ make uninstall -# systemctl daemon-reload -``` - -### For ArchLinux - -You can take a [PKGBUILD](/Arav/httpprocprobed/raw/branch/master/build/archlinux/PKGBUILD) file and in a directory with it run `makepkg -i`. \ No newline at end of file diff --git a/build/archlinux/PKGBUILD b/build/archlinux/PKGBUILD index 943d78a..524d85e 100644 --- a/build/archlinux/PKGBUILD +++ b/build/archlinux/PKGBUILD @@ -1,21 +1,14 @@ -# Maintainer: Alexander "Arav" Andreev +# Maintainer: Alexander "Arav" Andreev pkgname=httpprocprobed -pkgver=2.0.1 +pkgver=3.0.0 pkgrel=1 pkgdesc="HTTPProcProbeD hands out an HTTP endpoint to get if processes are running." arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64') -url="https://git.arav.top/Arav/httpprocprobed" +url="https://git.arav.su/Arav/httpprocprobed" license=('MIT') -groups=() -depends=() -makedepends=('go') -provides=('httpprocprobed') -conflicts=('httpprocprobed') -replaces=() -backup=('etc/httpprocprobed.conf') -options=() -install= -source=('https://git.arav.top/Arav/httpprocprobed/archive/2.0.1.tar.gz') +makedepends=('go>=1.17') +backup=('etc/httpprocprobed.json') +source=("https://git.arav.su/Arav/httpprocprobed/archive/$pkgver.tar.gz") noextract=() md5sums=('SKIP') diff --git a/configs/config.example.conf b/configs/config.example.conf deleted file mode 100644 index 76e8d40..0000000 --- a/configs/config.example.conf +++ /dev/null @@ -1,3 +0,0 @@ -listen_address = :28010 -indented_output = false -processes = \ No newline at end of file diff --git a/configs/config.example.json b/configs/config.example.json new file mode 100644 index 0000000..891d91e --- /dev/null +++ b/configs/config.example.json @@ -0,0 +1,6 @@ +{ + "listen-address": ":28010", + "processes": [ + {"alias": "minecraft", "process": "fabric-server-mc"} + ] +} \ No newline at end of file diff --git a/configuration.go b/configuration.go index 06fc0fb..81a15d5 100644 --- a/configuration.go +++ b/configuration.go @@ -1,98 +1,27 @@ package main import ( - "bufio" - "errors" - "log" + "encoding/json" "os" - "strconv" - "strings" ) -// Configuration holds a configuration for the service. type Configuration struct { - ListenAddress string - IndentedOutput bool - Processes []string + ListenAddress string `json:"listen-address"` + Processes []Process `json:"processes"` } -// LoadConfiguration loads configuration from a file. func LoadConfiguration(path string) (conf *Configuration, err error) { - conf = &Configuration{} - - file, err := os.Open(path) + f, err := os.Open(path) if err != nil { return nil, err } - defer file.Close() + defer f.Close() - s := bufio.NewScanner(file) - s.Split(bufio.ScanLines) + conf = &Configuration{} - for s.Scan() { - kv := strings.Split(s.Text(), " = ") - switch kv[0] { - case "listen_address": - conf.ListenAddress = kv[1] - case "indented_output": - v, err := strconv.ParseBool(kv[1]) - if err != nil { - log.Printf("[WARN] could not parse \"indented_output\", valid values are true or false. Defaulted to false.\n") - } - conf.IndentedOutput = v - case "processes": - if kv[1] != "" { - conf.Processes = append(conf.Processes, strings.Split(kv[1], " ")...) - } else { - log.Printf("[WARN] \"processes\" list is empty.\n") - } - } + if err := json.NewDecoder(f).Decode(conf); err != nil { + return nil, err } return conf, nil } - -// StoreConfiguration writes configuration into a file. -func (conf *Configuration) StoreConfiguration(path string) (err error) { - var config strings.Builder - - config.WriteString("listen_address = ") - config.WriteString(conf.ListenAddress) - config.WriteByte('\n') - config.WriteString("indented_output = ") - config.WriteString(strconv.FormatBool(conf.IndentedOutput)) - config.WriteByte('\n') - config.WriteString("processes = ") - config.WriteString(strings.Join(conf.Processes, " ")) - if err := os.WriteFile(path, []byte(config.String()), 0644); err != nil { - return err - } - - return nil -} - -// AddProcess appends a new given process into a configuration file. -func (conf *Configuration) AddProcess(process string, configPath string) error { - for _, v := range conf.Processes { - if v == process { - return errors.New("process is already on list") - } - } - - conf.Processes = append(conf.Processes, process) - return nil -} - -// RemoveProcess removes a given process from a configuration file. -func (conf *Configuration) RemoveProcess(process string, configPath string) error { - for k, v := range conf.Processes { - if v == process { - newlist := make([]string, len(conf.Processes)-1) - newlist = append(conf.Processes[:k], conf.Processes[k+1:]...) - conf.Processes = newlist - return nil - } - } - - return errors.New("process is not on list") -} diff --git a/go.mod b/go.mod index 3afa6aa..3a53e42 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module httpprocprobed -go 1.20 +go 1.17 diff --git a/http.go b/http.go new file mode 100644 index 0000000..33e15f0 --- /dev/null +++ b/http.go @@ -0,0 +1,24 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" +) + +// AreProcessesUp sends back status of watched processes. +func AreProcessesUp(processes *[]Process) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Header().Add("Allow", "GET") + return + } + + w.Header().Add("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(GetProcessesState(processes)); err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Printf("Failed to encode a process list: %s\n", err) + } + } +} diff --git a/httpserver.go b/httpserver.go deleted file mode 100644 index d162ec0..0000000 --- a/httpserver.go +++ /dev/null @@ -1,86 +0,0 @@ -package main - -import ( - "context" - "encoding/json" - "encoding/xml" - "log" - "net/http" - "strings" - "time" -) - -func CreateAndStartHTTPServer(conf *Configuration) *http.Server { - router := http.NewServeMux() - router.HandleFunc("/processes", AreProcessesUp(&conf.Processes, conf.IndentedOutput)) - - srv := &http.Server{ - Addr: conf.ListenAddress, - Handler: router, - ReadTimeout: 5 * time.Second, - WriteTimeout: 5 * time.Second, - IdleTimeout: 10 * time.Second, - } - - go func() { - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("ListenAndServe: %s\n", err) - } - }() - - return srv -} - -func ShutdownHTTPServer(srv *http.Server) { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - srv.SetKeepAlivesEnabled(false) - if err := srv.Shutdown(ctx); err != nil { - log.Fatalf("%s\n", err) - } -} - -// AreProcessesUp handles a GET /processes request and sends back status of given -// processes. -func AreProcessesUp(processes *[]string, indented bool) func(http.ResponseWriter, *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodGet { - proclist := make(ProcessList) - - for _, proc := range *processes { - pids, err := GetProcessPIDs(proc) - proclist[proc] = err == nil && len(pids) > 0 - } - - switch r.Header.Get("Accept") { - case "application/xml": - w.Header().Add("Content-Type", "application/xml") - enc := xml.NewEncoder(w) - if indented { - enc.Indent("", "\t") - } - enc.Encode(proclist) - case "text/plain": - w.Header().Add("Content-Type", "text/plain") - var s []string - for k, v := range proclist { - if v { - s = append(s, k) - } - } - w.Write([]byte(strings.Join(s, ","))) - default: - w.Header().Add("Content-Type", "application/json") - enc := json.NewEncoder(w) - if indented { - enc.SetIndent("", "\t") - } - enc.Encode(proclist) - } - } else { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Header().Add("Allow", "GET") - } - } -} diff --git a/main.go b/main.go index 5e2f518..cdebd0a 100644 --- a/main.go +++ b/main.go @@ -1,85 +1,52 @@ package main import ( + "context" "flag" "fmt" "log" + "net/http" "os" "os/signal" "syscall" + "time" ) -var configPath *string = flag.String("c", "config.conf", "path to configuration file") +var configPath *string = flag.String("c", "config.json", "path to configuration file") var showVersion *bool = flag.Bool("v", false, "show version") -var listProcesses *bool = flag.Bool("l", false, "list watched processes") -var addProcess *string = flag.String("a", "", "add process to list") -var removeProcess *string = flag.String("r", "", "remove process from list") +var version string func main() { log.SetFlags(0) flag.Parse() if *showVersion { - fmt.Println("httpprocprobed ver. 2.0.1") - fmt.Println("Copyright (c) 2021-2023 Alexander \"Arav\" Andreev ") - fmt.Println("This program is licensed under terms of MIT+NIGGER license.") + fmt.Println("httpprocprobed ver.", version, "Copyright (c) 2021-2023 Alexander \"Arav\" Andreev ") os.Exit(0) } conf, err := LoadConfiguration(*configPath) if err != nil { - log.Fatalf("[ERR] Cannot load configuration file: %s\n", err) + log.Fatalf("Cannot load configuration file: %s\n", err) } - if *listProcesses { - for _, v := range conf.Processes { - fmt.Printf("%s, ", v) - } - fmt.Println() - os.Exit(0) + router := http.NewServeMux() + router.HandleFunc("/processes", AreProcessesUp(&conf.Processes)) + + srv := &http.Server{ + Addr: conf.ListenAddress, + Handler: router, + ReadTimeout: 5 * time.Second, + WriteTimeout: 5 * time.Second, + IdleTimeout: 10 * time.Second, } - if *addProcess != "" { - err := conf.AddProcess(*addProcess, *configPath) - if err != nil { - log.Fatalf("[ERR] Cannot add process: %s\n", err) + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("ListenAndServe: %s\n", err) } - } - - if *removeProcess != "" { - err := conf.RemoveProcess(*removeProcess, *configPath) - if err != nil { - log.Fatalf("[ERR] Cannot remove process: %s\n", err) - } - } - - // If we modified a list then let's look for a running program and - // send SIGHUP to it to reload a list. Here we assume that there - // is only one process running, so we just filter our PID. - if *addProcess != "" || *removeProcess != "" { - if err := conf.StoreConfiguration(*configPath); err != nil { - log.Fatalf("[ERR] Cannot write configuration. Error: %s\n", err) - } - - pids, _ := GetProcessPIDs("httpprocprobed") - if len(pids) > 1 { - var trgt_pid int - if pids[0] == os.Getpid() { - trgt_pid = pids[1] - } else { - trgt_pid = pids[0] - } - - if proc, err := os.FindProcess(trgt_pid); err == nil { - proc.Signal(syscall.SIGHUP) - } - } - - os.Exit(0) - } - - srv := CreateAndStartHTTPServer(conf) + }() syssignal := make(chan os.Signal, 1) signal.Notify(syssignal, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) @@ -91,7 +58,10 @@ func main() { case os.Interrupt: fallthrough case syscall.SIGINT | syscall.SIGTERM: - ShutdownHTTPServer(srv) + srv.SetKeepAlivesEnabled(false) + if err := srv.Shutdown(context.Background()); err != nil { + log.Fatalf("%s\n", err) + } log.Println("Server shutted down.") os.Exit(0) case syscall.SIGHUP: diff --git a/proc.go b/process.go similarity index 61% rename from proc.go rename to process.go index 00dcc73..b5108a1 100644 --- a/proc.go +++ b/process.go @@ -1,5 +1,3 @@ -//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd - package main import ( @@ -8,6 +6,27 @@ import ( "strings" ) +// Process contains an alias that will be returned when queries, and a process +// name to look for. +type Process struct { + Alias string `json:"alias"` + Process string `json:"process"` +} + +// ProcessesState is a map of processes' aliases and its statuses. +type ProcessesState map[string]bool + +func GetProcessesState(procs *[]Process) (ps ProcessesState) { + ps = make(ProcessesState) + + for _, proc := range *procs { + pids, err := GetProcessPIDs(proc.Process) + ps[proc.Alias] = err == nil && len(pids) > 0 + } + + return +} + // GetProcessPIDs returns a list of PIDs found for a process. func GetProcessPIDs(name string) (pids []int, err error) { dir, err := os.ReadDir("/proc/") diff --git a/processlist.go b/processlist.go deleted file mode 100644 index 96152fe..0000000 --- a/processlist.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "encoding/xml" -) - -// ProcessList is a map of processes' names and its statuses. -type ProcessList map[string]bool - -// MarshalXML implements XML Marshaler interface for a ProcessList. -func (l *ProcessList) MarshalXML(e *xml.Encoder, start xml.StartElement) error { - if len(*l) == 0 { - return nil - } - - if err := e.EncodeToken(start); err != nil { - return err - } - - for key, val := range *l { - e.Encode(struct { - XMLName xml.Name - Name string `xml:"name,attr"` - IsUp bool `xml:",chardata"` - }{ - XMLName: xml.Name{Local: "Process"}, - Name: key, - IsUp: val}) - } - - return e.EncodeToken(start.End()) -}