Remove legacy default-server page, unify on multi-server registry

Deletes /wg-server (route, handlers, template) entirely and adds generic
/servers/:id/interface and /servers/:id/keypair endpoints + UI in the
All Servers page, so every server (including the default one) is managed
through the same per-server registry. Drops the write-through dual-write
hacks that kept the old single-server collection in sync - the registry
is now the single source of truth. One-time legacy-install migration path
is untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-07-12 16:27:17 +02:00
co-authored by Claude Sonnet 5
parent 2212141ba3
commit 58db6839c3
10 changed files with 367 additions and 400 deletions
+36 -72
View File
@@ -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/<id>.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/<id>.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"})