diff --git a/handler/routes.go b/handler/routes.go index 63711cc..741bc84 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -1203,7 +1203,7 @@ func ServerServiceStop(db store.IStore) echo.HandlerFunc { // ServerServiceRestart restarts a server's WireGuard interface via // `systemctl restart wg-quick@.service`. Admin-only. -func ServerServiceRestart(db store.IStore) echo.HandlerFunc { +func ServerServiceRestart(db store.IStore, tmplDir fs.FS) echo.HandlerFunc { return func(c echo.Context) error { serverID := c.Param("id") server, err := db.GetServerByID(serverID) @@ -1211,6 +1211,13 @@ func ServerServiceRestart(db store.IStore) echo.HandlerFunc { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "Server not found"}) } + if err := writeServerConfigToDisk(db, tmplDir, serverID); err != nil { + log.Errorf("Failed to write config before restarting server %s: %v", serverID, err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ + false, fmt.Sprintf("Cannot write server config: %v", err), + }) + } + if err := wireguard.Restart(c.Request().Context(), server.Interface.Name); err != nil { log.Errorf("Failed to restart service for server %s: %v", serverID, err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) diff --git a/main.go b/main.go index 0458ecd..77405b8 100644 --- a/main.go +++ b/main.go @@ -299,7 +299,7 @@ func main() { app.GET(util.BasePath+"/servers/:id/service-status", handler.ServerServiceStatus(db), handler.ValidSession, handler.RequireServerAccess(db)) app.POST(util.BasePath+"/servers/:id/service/start", handler.ServerServiceStart(db, tmplDir), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.POST(util.BasePath+"/servers/:id/service/stop", handler.ServerServiceStop(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) - app.POST(util.BasePath+"/servers/:id/service/restart", handler.ServerServiceRestart(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.POST(util.BasePath+"/servers/:id/service/restart", handler.ServerServiceRestart(db, tmplDir), 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) diff --git a/templates/wg.conf b/templates/wg.conf index 3bb3b44..f0b573e 100644 --- a/templates/wg.conf +++ b/templates/wg.conf @@ -9,10 +9,18 @@ ListenPort = {{ .serverConfig.Interface.ListenPort }} PrivateKey = {{ .serverConfig.KeyPair.PrivateKey }} {{if .globalSettings.MTU}}MTU = {{ .globalSettings.MTU }}{{end}} PreUp = {{ .serverConfig.Interface.PreUp }} -PostUp = {{ .serverConfig.Interface.PostUp }} +# Table is forced to "off" and routes are (re)installed explicitly below via +# `ip route replace` instead of letting wg-quick manage them with `ip route +# add`. Multiple servers on this box can legitimately share an overlapping +# AllowedIPs entry (e.g. the same /32 reachable via more than one tunnel); +# wg-quick's own route handling hard-fails the whole interface with +# "RTNETLINK: File exists" the moment two servers claim the same route, +# which used to take down every server started after the first. `replace` +# is idempotent and never fails on a pre-existing route. +PostUp = {{ .serverConfig.Interface.PostUp }}{{range .clientDataList}}{{if eq .Client.Enabled true}}{{range .Client.AllocatedIPs}}ip route replace {{.}} dev %i; {{end}}{{range .Client.ExtraAllowedIPs}}ip route replace {{.}} dev %i; {{end}}{{end}}{{end}} PreDown = {{ .serverConfig.Interface.PreDown }} PostDown = {{ .serverConfig.Interface.PostDown }} -Table = {{ .globalSettings.Table }} +Table = off {{range .clientDataList}}{{if eq .Client.Enabled true}} # ID: {{ .Client.ID }}