diff --git a/DEVLOG.md b/DEVLOG.md index e791907..93ad5b7 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1453,3 +1453,143 @@ Keine Commits in dieser Session. - templates/global_settings.html | 93 ++++++++++++++++++++++++++++++++++++++++++ --- +## 2026-07-12 12:54 – 13:03 (9m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +- 2212141 Clarify server nav labels (Default Server Interface vs All Servers) + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:13 – 13:24 (11m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:25 – 13:25 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:26 – 13:27 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:29 – 13:29 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:30 – 13:30 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:33 – 13:35 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 13:38 – 13:38 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 +- +- templates/server.html | 6 +-- +- templates/servers.html | 4 +- + +--- +## 2026-07-12 16:20 – 16:20 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 ++-- +- templates/server.html | 6 +++--- +- templates/servers.html | 4 ++-- + +--- +## 2026-07-12 16:20 – 16:21 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** wireguard-ui-multi + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- templates/base.html | 4 ++-- +- templates/server.html | 6 +++--- +- templates/servers.html | 4 ++-- + +--- diff --git a/handler/routes.go b/handler/routes.go index 1c917d8..4ed65a6 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -35,8 +35,8 @@ var usernameRegexp = regexp.MustCompile("^\\w[\\w\\-.]*$") // resolveServerID returns the server ID a request targets: the :id route // param for /servers/:id/... routes, or util.DefaultServerID for the -// legacy bare routes (/, /new-client, /wg-server, ...) which always -// operate on the migrated default server. +// legacy bare routes (/, /new-client, ...) which always operate on the +// migrated default server. func resolveServerID(c echo.Context) string { if id := c.Param("id"); id != "" { return id @@ -1168,27 +1168,22 @@ func RemoveClient(db store.IStore) echo.HandlerFunc { } } -// WireGuardServer handler -func WireGuardServer(db store.IStore) echo.HandlerFunc { +// UpdateServerInterfaceHandler updates the WireGuard interface settings +// (addresses, listen port, up/down scripts) for a single server, identified +// by :id. This is the multi-server-aware handler surfaced from the "All +// Servers" page; the per-server registry (servers/.json) is the single +// source of truth for every server, including the default one. +func UpdateServerInterfaceHandler(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - server, err := db.GetServer() - if err != nil { - log.Error("Cannot get server config: ", err) + serverID := c.Param("id") + if _, err := db.GetServerByID(serverID); err != nil { + return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server not found"}) } - return c.Render(http.StatusOK, "server.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c), Admin: isAdmin(c)}, - "serverInterface": server.Interface, - "serverKeyPair": server.KeyPair, - }) - } -} - -// WireGuardServerInterfaces handler -func WireGuardServerInterfaces(db store.IStore) echo.HandlerFunc { - return func(c echo.Context) error { var serverInterface model.ServerInterface - c.Bind(&serverInterface) + if err := c.Bind(&serverInterface); err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) + } // validate the input addresses if util.ValidateServerAddresses(serverInterface.Addresses) == false { @@ -1198,31 +1193,27 @@ func WireGuardServerInterfaces(db store.IStore) echo.HandlerFunc { serverInterface.UpdatedAt = time.Now().UTC() - // write config to the database (legacy single-server collection) - - if err := db.SaveServerInterface(serverInterface); err != nil { - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Interface IP address must be in CIDR format"}) + if err := db.UpdateServerInterface(serverID, serverInterface); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot update server interface: %v", err)}) } - // keep the new per-server registry record for the default server in - // sync, since this legacy route is the only way to edit it today - if serverInterface.Name == "" { - serverInterface.Name = util.DefaultServerID - } - if err := db.UpdateServerInterface(util.DefaultServerID, serverInterface); err != nil { - log.Warnf("Could not sync default server registry entry: %v", err) - } - - log.Infof("Updated wireguard server interfaces settings: %v", serverInterface) + log.Infof("Updated wireguard server interface settings for server %s: %v", serverID, serverInterface) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated interface addresses successfully"}) } } -// WireGuardServerKeyPair handler to generate private and public keys -func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc { +// UpdateServerKeyPairHandler generates a fresh WireGuard key pair for a +// single server, identified by :id. The per-server registry +// (servers/.json) is the single source of truth for every server, +// including the default one. +func UpdateServerKeyPairHandler(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - // gen Wireguard key pair + serverID := c.Param("id") + if _, err := db.GetServerByID(serverID); err != nil { + return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server not found"}) + } + key, err := wgtypes.GeneratePrivateKey() if err != nil { log.Error("Cannot generate wireguard key pair: ", err) @@ -1234,17 +1225,11 @@ func WireGuardServerKeyPair(db store.IStore) echo.HandlerFunc { serverKeyPair.PublicKey = key.PublicKey().String() serverKeyPair.UpdatedAt = time.Now().UTC() - if err := db.SaveServerKeyPair(serverKeyPair); err != nil { - return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) + if err := db.UpdateServerKeyPair(serverID, serverKeyPair); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, fmt.Sprintf("Cannot update server key pair: %v", err)}) } - // keep the new per-server registry record for the default server in - // sync, since this legacy route is the only way to edit it today - if err := db.UpdateServerKeyPair(util.DefaultServerID, serverKeyPair); err != nil { - log.Warnf("Could not sync default server registry entry: %v", err) - } - - log.Infof("Updated wireguard server interfaces settings: %v", serverKeyPair) + log.Infof("Updated wireguard server key pair for server %s", serverID) return c.JSON(http.StatusOK, serverKeyPair) } @@ -1379,26 +1364,14 @@ func GlobalSettingSubmit(db store.IStore) echo.HandlerFunc { globalSettings.UpdatedAt = time.Now().UTC() - // write config to the database (legacy global collection) + // write the app-wide settings (DNS/MTU/PersistentKeepalive); per-server + // concerns (EndpointAddress/Table/FirewallMark/ConfigFilePath) live + // solely in the per-server registry's ServerSetting record, edited via + // the per-server settings editor, and are not duplicated here. if err := db.SaveGlobalSettings(globalSettings); err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot generate Wireguard key pair"}) } - // keep the new per-server registry's ServerSetting record for the - // default server in sync (EndpointAddress/Table/FirewallMark/ - // ConfigFilePath are per-server concerns in the new model), since - // this legacy route is the only way to edit it today - serverSettings := model.ServerSetting{ - EndpointAddress: globalSettings.EndpointAddress, - FirewallMark: globalSettings.FirewallMark, - Table: globalSettings.Table, - ConfigFilePath: globalSettings.ConfigFilePath, - UpdatedAt: globalSettings.UpdatedAt, - } - if err := db.SaveServerSettings(util.DefaultServerID, serverSettings); err != nil { - log.Warnf("Could not sync default server settings registry entry: %v", err) - } - log.Infof("Updated global settings: %v", globalSettings) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated global settings successfully"}) @@ -1438,7 +1411,7 @@ func GetOrderedSubnetRanges() echo.HandlerFunc { // SuggestIPAllocation handler to get the list of ip address for client func SuggestIPAllocation(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - server, err := db.GetServer() + server, err := db.GetServerByID(util.DefaultServerID) if err != nil { log.Error("Cannot fetch server config from database: ", err) return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, err.Error()}) @@ -1506,16 +1479,7 @@ func ApplyServerConfig(db store.IStore, tmplDir fs.FS) echo.HandlerFunc { return func(c echo.Context) error { serverID := resolveServerID(c) - var server model.Server - var err error - if serverID == util.DefaultServerID { - // legacy bare route: keep reading from the legacy single-server - // collection, which is still the source of truth an operator - // may have just edited via /wg-server - server, err = db.GetServer() - } else { - server, err = db.GetServerByID(serverID) - } + server, err := db.GetServerByID(serverID) if err != nil { log.Error("Cannot get server config: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get server config"}) diff --git a/main.go b/main.go index 246f6fa..e5596c6 100644 --- a/main.go +++ b/main.go @@ -245,9 +245,6 @@ func main() { app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) - app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession, handler.RefreshSession, handler.NeedsAdmin) - app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) - app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.RefreshSession, handler.NeedsAdmin) app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession, handler.RefreshSession) @@ -265,6 +262,8 @@ func main() { app.POST(util.BasePath+"/servers/:id/api/apply-wg-config", handler.ApplyServerConfig(db, tmplDir), handler.ValidSession, handler.ContentTypeJson, handler.RequireServerAccess(db)) app.GET(util.BasePath+"/servers/:id/settings", handler.GetServerSettings(db), handler.ValidSession, handler.RequireServerAccess(db)) app.POST(util.BasePath+"/servers/:id/settings", handler.SaveServerSettingsHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.POST(util.BasePath+"/servers/:id/interface", handler.UpdateServerInterfaceHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.POST(util.BasePath+"/servers/:id/keypair", handler.UpdateServerKeyPairHandler(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.POST(util.BasePath+"/backup/download", handler.DownloadBackup(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession) app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession) @@ -321,7 +320,7 @@ func initServerConfig(db store.IStore, tmplDir fs.FS) { return } - server, err := db.GetServer() + server, err := db.GetServerByID(util.DefaultServerID) if err != nil { log.Fatalf("Cannot get server config: %v", err) } diff --git a/router/router.go b/router/router.go index 7fbb70e..30cb203 100644 --- a/router/router.go +++ b/router/router.go @@ -81,11 +81,6 @@ func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo log.Fatal(err) } - tmplServerString, err := util.StringFromEmbedFile(tmplDir, "server.html") - if err != nil { - log.Fatal(err) - } - tmplGlobalSettingsString, err := util.StringFromEmbedFile(tmplDir, "global_settings.html") if err != nil { log.Fatal(err) @@ -129,7 +124,6 @@ func New(tmplDir fs.FS, extraData map[string]interface{}, secret [64]byte) *echo templates["login.html"] = template.Must(template.New("login").Funcs(funcs).Parse(tmplLoginString)) templates["profile.html"] = template.Must(template.New("profile").Funcs(funcs).Parse(tmplBaseString + tmplProfileString)) templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString)) - templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString)) templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString)) templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString)) templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString)) diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index 5be7a2a..e2bb9cc 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -325,11 +325,13 @@ func (o *JsonDB) migrateLegacyServer() error { } // NOTE: the legacy "server" directory is intentionally left in place - // (not renamed/removed) at this stage. Existing store methods (GetServer, - // GetGlobalSettings, SaveServerInterface, ...) still read/write it - // directly until they are switched over to the new per-server layout; - // removing it now would break the running app. The rename-to-backup - // step happens once those methods are migrated. + // (not renamed/removed). GetGlobalSettings/SaveGlobalSettings still read + // and write the app-wide settings record there (DNS/MTU/ + // PersistentKeepalive are not yet per-server concerns); every other + // server-scoped read/write now goes exclusively through the per-server + // registry (servers/.json via GetServerByID/UpdateServerInterface/ + // UpdateServerKeyPair/GetServerSettings), so this directory is kept only + // as a landing spot for one-time migration and for global settings. log.Printf("Migrated legacy single-server DB to multi-server layout (server id: %s); legacy db/server/ kept in place for now", serverID) return nil } @@ -387,27 +389,6 @@ func (o *JsonDB) GetGlobalSettings() (model.GlobalSetting, error) { return settings, o.conn.Read("server", "global_settings", &settings) } -// GetServer func to query Server settings from the database -func (o *JsonDB) GetServer() (model.Server, error) { - server := model.Server{} - // read server interface information - serverInterface := model.ServerInterface{} - if err := o.conn.Read("server", "interfaces", &serverInterface); err != nil { - return server, err - } - - // read server key pair information - serverKeyPair := model.ServerKeypair{} - if err := o.conn.Read("server", "keypair", &serverKeyPair); err != nil { - return server, err - } - - // create Server object and return - server.Interface = &serverInterface - server.KeyPair = &serverKeyPair - return server, nil -} - func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) { var clients []model.ClientData @@ -429,7 +410,7 @@ func (o *JsonDB) GetClients(hasQRCode bool) ([]model.ClientData, error) { // generate client qrcode image in base64 if hasQRCode && client.PrivateKey != "" { - server, _ := o.GetServer() + server, _ := o.GetServerByID(util.DefaultServerID) globalSettings, _ := o.GetGlobalSettings() png, err := qrcode.Encode(util.BuildClientConfig(client, server, globalSettings), qrcode.Medium, 256) @@ -459,7 +440,7 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti // generate client qrcode image in base64 if qrCodeSettings.Enabled && client.PrivateKey != "" { - server, _ := o.GetServer() + server, _ := o.GetServerByID(util.DefaultServerID) globalSettings, _ := o.GetGlobalSettings() client := client if !qrCodeSettings.IncludeDNS { @@ -508,26 +489,6 @@ func (o *JsonDB) DeleteClient(clientID string) error { return o.conn.Delete("clients", clientID) } -func (o *JsonDB) SaveServerInterface(serverInterface model.ServerInterface) error { - serverInterfacePath := path.Join(path.Join(o.dbPath, "server"), "interfaces.json") - output := o.conn.Write("server", "interfaces", serverInterface) - err := util.ManagePerms(serverInterfacePath) - if err != nil { - return err - } - return output -} - -func (o *JsonDB) SaveServerKeyPair(serverKeyPair model.ServerKeypair) error { - serverKeyPairPath := path.Join(path.Join(o.dbPath, "server"), "keypair.json") - output := o.conn.Write("server", "keypair", serverKeyPair) - err := util.ManagePerms(serverKeyPairPath) - if err != nil { - return err - } - return output -} - func (o *JsonDB) SaveGlobalSettings(globalSettings model.GlobalSetting) error { globalSettingsPath := path.Join(path.Join(o.dbPath, "server"), "global_settings.json") output := o.conn.Write("server", "global_settings", globalSettings) diff --git a/store/store.go b/store/store.go index f1f0b27..f39d1ce 100644 --- a/store/store.go +++ b/store/store.go @@ -11,13 +11,10 @@ type IStore interface { SaveUser(user model.User) error DeleteUser(username string) error GetGlobalSettings() (model.GlobalSetting, error) - GetServer() (model.Server, error) GetClients(hasQRCode bool) ([]model.ClientData, error) GetClientByID(clientID string, qrCode model.QRCodeSettings) (model.ClientData, error) SaveClient(client model.Client) error DeleteClient(clientID string) error - SaveServerInterface(serverInterface model.ServerInterface) error - SaveServerKeyPair(serverKeyPair model.ServerKeypair) error SaveGlobalSettings(globalSettings model.GlobalSetting) error GetWakeOnLanHosts() ([]model.WakeOnLanHost, error) GetWakeOnLanHost(macAddress string) (*model.WakeOnLanHost, error) diff --git a/templates/base.html b/templates/base.html index d5aed11..32aafff 100644 --- a/templates/base.html +++ b/templates/base.html @@ -128,16 +128,6 @@ {{if .baseData.Admin}} - - -