From be381205be02c18598a837a444b2153ef92cfab7 Mon Sep 17 00:00:00 2001 From: Matthias Hinrichs Date: Sat, 22 Nov 2025 02:24:02 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20f=C3=BCge=20Unterst=C3=BCtzung=20f?= =?UTF-8?q?=C3=BCr=20das=20Hinzuf=C3=BCgen=20und=20L=C3=B6schen=20von=20Se?= =?UTF-8?q?rvern=20hinzu,=20aktualisiere=20die=20Konfiguration=20und=20ver?= =?UTF-8?q?bessere=20die=20Benutzeroberfl=C3=A4che?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yml | 2 + go.mod | 16 ++++- go.sum | 25 ++++++++ index.html | 137 +++++++++++++++++++++++++++++++++++++---- main.go | 35 +++++++---- webserver/webserver.go | 80 +++++++++++++++++++++++- 6 files changed, 265 insertions(+), 30 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index cf82310..a884f1d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,8 @@ services: - "traefik.http.routers.manage-servers.tls=true" - "traefik.http.routers.manage-servers.tls.certresolver=cloudflare" - "traefik.http.services.manage-servers.loadbalancer.server.port=8080" + volumes: + - /volume1/docker-data/manage-servers:/config networks: - my-container-macvlan-200 diff --git a/go.mod b/go.mod index 5c2f9d8..c2781cd 100644 --- a/go.mod +++ b/go.mod @@ -4,4 +4,18 @@ go 1.24.6 require golang.org/x/crypto v0.45.0 -require golang.org/x/sys v0.38.0 // indirect +require ( + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/spf13/viper v1.21.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect +) diff --git a/go.sum b/go.sum index 0f82070..9093fbd 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,31 @@ +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/index.html b/index.html index ed771af..de2a497 100644 --- a/index.html +++ b/index.html @@ -8,7 +8,7 @@

Server Management

-
+
@@ -24,35 +24,131 @@
+ + +
+ +
+ +
diff --git a/main.go b/main.go index 1fefdde..6570e47 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,17 @@ package main import ( - "encoding/json" "fmt" "os" serveractions "manage-servers/server-actions" "manage-servers/webserver" + + "github.com/spf13/viper" ) func main() { - servers, err := loadServers("./config/servers.json") + servers, err := loadConfig() if err != nil { fmt.Printf("Error loading servers: %v\n", err) return @@ -73,18 +74,26 @@ func printUsage() { fmt.Println(" serve - Start a web server to manage servers") } -func loadServers(filename string) ([]serveractions.Server, error) { - var servers []serveractions.Server - - file, err := os.ReadFile(filename) - if err != nil { - fmt.Println("No servers configuration found, please create a servers.json file in the config folder.") - } else { +func loadConfig() ([]serveractions.Server, error) { + viper.SetConfigName("servers") // name of config file (without extension) + viper.SetConfigType("json") // or viper.SetConfigType("YAML") + viper.AddConfigPath("./config") // path to look for the config file in + viper.AddConfigPath(".") // optionally look for config in the working directory + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + // Check if the error is that the file doesn't exist + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found; ignore error and return empty server list + return []serveractions.Server{}, nil + } + return nil, fmt.Errorf("fatal error config file: %w", err) + } - err = json.Unmarshal(file, &servers) + var servers []serveractions.Server + err = viper.UnmarshalKey("servers", &servers) if err != nil { - return nil, err - }} + return nil, fmt.Errorf("unable to decode into struct, %v", err) + } return servers, nil } @@ -92,6 +101,6 @@ func loadServers(filename string) ([]serveractions.Server, error) { func listServers(servers []serveractions.Server) { fmt.Println("Configured Servers:") for _, s := range servers { - fmt.Printf(" - %s (%s) - %s\n", s.Name, s.IP, s.Mac) + fmt.Printf(" - %s (%s) - %s - User: %s\n", s.Name, s.IP, s.Mac, s.SSHUser) } } diff --git a/webserver/webserver.go b/webserver/webserver.go index b4b5576..b9ea9ce 100644 --- a/webserver/webserver.go +++ b/webserver/webserver.go @@ -5,7 +5,9 @@ import ( "fmt" "net/http" - "manage-servers/server-actions" + serveractions "manage-servers/server-actions" + + "github.com/spf13/viper" ) type WebServer struct { @@ -17,7 +19,7 @@ func StartWebServer(servers []serveractions.Server) { fmt.Println("Starting web server on :8080...") http.HandleFunc("/", ws.handleRoot) - http.HandleFunc("/servers", ws.handleGetServers) + http.HandleFunc("/servers", ws.handleServers) http.HandleFunc("/wake", ws.handleWakeServer) http.HandleFunc("/shutdown", ws.handleShutdownServer) http.HandleFunc("/reboot", ws.handleRebootServer) @@ -32,11 +34,83 @@ func (ws *WebServer) handleRoot(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html") } -func (ws *WebServer) handleGetServers(w http.ResponseWriter, r *http.Request) { +func (ws *WebServer) handleServers(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + ws.getServers(w, r) + case http.MethodPost: + ws.addServer(w, r) + case http.MethodDelete: + ws.deleteServer(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func (ws *WebServer) getServers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(ws.servers) } +func (ws *WebServer) addServer(w http.ResponseWriter, r *http.Request) { + var newServer serveractions.Server + if err := json.NewDecoder(r.Body).Decode(&newServer); err != nil { + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + + ws.servers = append(ws.servers, newServer) + + viper.Set("servers", ws.servers) + if err := viper.WriteConfig(); err != nil { + // If the file does not exist, create it + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + if err = viper.SafeWriteConfig(); err != nil { + http.Error(w, fmt.Sprintf("Error creating config file: %v", err), http.StatusInternalServerError) + return + } + } else { + http.Error(w, fmt.Sprintf("Error writing config file: %v", err), http.StatusInternalServerError) + return + } + } + + w.WriteHeader(http.StatusCreated) +} + +func (ws *WebServer) deleteServer(w http.ResponseWriter, r *http.Request) { + serverName := r.URL.Query().Get("name") + if serverName == "" { + http.Error(w, "Missing server name", http.StatusBadRequest) + return + } + + var found bool + var updatedServers []serveractions.Server + for _, server := range ws.servers { + if server.Name != serverName { + updatedServers = append(updatedServers, server) + } else { + found = true + } + } + + if !found { + http.Error(w, "Server not found", http.StatusNotFound) + return + } + + ws.servers = updatedServers + viper.Set("servers", ws.servers) + + if err := viper.WriteConfig(); err != nil { + http.Error(w, fmt.Sprintf("Error writing config file: %v", err), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) +} + func (ws *WebServer) handleWakeServer(w http.ResponseWriter, r *http.Request) { serverName := r.URL.Query().Get("name") if serverName == "" {