From 73619c17f8193c88f8d0323b35426912572dea32 Mon Sep 17 00:00:00 2001 From: Debian Date: Sun, 24 May 2026 22:29:00 +0200 Subject: [PATCH] fix(system): render-configs rendert jetzt ALLE Dienste, nicht nur HAProxy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SystemHandler.ExtraReloaders: neues Feld für nftables, wireguard, squid, unbound, chrony Reloader - WithAllReloaders(): Wire-Methode; main.go bindet alle Reloader nach Initialisierung ein - RenderConfigs: läuft alle Reloader durch, sammelt Fehler, antwortet mit {ok, rendered[], errors{}} — partiell OK wird als warning gemeldet - UI: Settings-Button-Label "Re-render all configs"; partial-error Toast zeigt welche Dienste fehlschlugen - Timeout: 30s statt 10s (alle Dienste brauchen mehr Zeit) Co-Authored-By: Claude Sonnet 4.6 --- VERSION | 2 +- cmd/edgeguard-api/main.go | 12 +++- cmd/edgeguard-ctl/main.go | 2 +- cmd/edgeguard-scheduler/main.go | 2 +- internal/handlers/system.go | 57 ++++++++++++++----- management-ui/src/i18n/locales/de/common.json | 5 +- management-ui/src/i18n/locales/en/common.json | 5 +- management-ui/src/pages/Settings/index.tsx | 10 +++- 8 files changed, 71 insertions(+), 24 deletions(-) diff --git a/VERSION b/VERSION index 08435f6..748d107 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.95 +1.1.96 diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go index b4ab18c..31f6d0d 100644 --- a/cmd/edgeguard-api/main.go +++ b/cmd/edgeguard-api/main.go @@ -60,7 +60,7 @@ import ( usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users" ) -var version = "1.1.95" +var version = "1.1.96" func main() { addr := os.Getenv("EDGEGUARD_API_ADDR") @@ -401,6 +401,16 @@ func main() { } handlers.NewNTPHandler(ntpRepo, auditRepo, nodeID, withFW(chronyReloader)).Register(authed) + // Wire all service reloaders into systemHdl so RenderConfigs + // re-renders every service from DB state in one shot. + systemHdl.WithAllReloaders(map[string]func(context.Context) error{ + "nftables": fwReloader, + "wireguard": wgReloader, + "squid": squidReloader, + "unbound": unboundReloader, + "chrony": chronyReloader, + }) + // License — node-local key store + DB-mirror of last verify // result. Real verify runs against license.netcell-it.com via // internal/license; the scheduler triggers daily re-verify. diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go index 2ec2151..a29c847 100644 --- a/cmd/edgeguard-ctl/main.go +++ b/cmd/edgeguard-ctl/main.go @@ -11,7 +11,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/setup" ) -var version = "1.1.95" +var version = "1.1.96" const usage = `edgeguard-ctl — EdgeGuard CLI diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 641cb6e..b49393c 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -35,7 +35,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) -var version = "1.1.95" +var version = "1.1.96" const ( // renewTickInterval — how often we re-evaluate expiring certs. diff --git a/internal/handlers/system.go b/internal/handlers/system.go index b32635d..4ed5866 100644 --- a/internal/handlers/system.go +++ b/internal/handlers/system.go @@ -40,6 +40,9 @@ type SystemHandler struct { // in main.go after generators are constructed. Read-only access — // no writes, no reloads. ConfigPreviewers map[string]func(stdcontext.Context) (string, error) + // ExtraReloaders: additional service renderers triggered by + // RenderConfigs. Keyed by service name (nftables, wireguard, etc.). + ExtraReloaders map[string]func(stdcontext.Context) error } func NewSystemHandler(version string) *SystemHandler { @@ -75,6 +78,14 @@ func (h *SystemHandler) WithConfigPreviewers(previewers map[string]func(stdconte return h } +// WithAllReloaders injectet alle Service-Reloader für RenderConfigs. +// Reihenfolge: haproxy ist bereits in HAProxyReloader; extras sind die +// restlichen Dienste (nftables, wireguard, squid, unbound, chrony). +func (h *SystemHandler) WithAllReloaders(extras map[string]func(stdcontext.Context) error) *SystemHandler { + h.ExtraReloaders = extras + return h +} + func (h *SystemHandler) Register(rg *gin.RouterGroup) { g := rg.Group("/system") g.GET("/health", h.Health) @@ -565,30 +576,46 @@ func (h *SystemHandler) ServiceRestart(c *gin.Context) { } // RenderConfigs erzwingt ein Re-Render aller Service-Configs aus dem -// aktuellen DB-State. Wenn der HAProxyReloader gesetzt ist, läuft der -// (rendert haproxy.cfg + reload). Praktisch wenn ein Operator denkt -// dass die generierte Config nicht mehr mit der DB übereinstimmt -// (Drift, Manual-Edit, etc.). -// -// v1 macht NUR haproxy — weitere Renderer (firewall, dns, ntp, wg, -// squid) sind per Handler an die jeweiligen Mutations-Endpoints -// gekoppelt; für die fehlt aktuell ein generisches "render all". +// aktuellen DB-State. Läuft haproxy + alle ExtraReloaders (nftables, +// wireguard, squid, unbound, chrony) durch. Fehler werden gesammelt +// und als partial-Antwort zurückgegeben — erfolgreich gerenderte +// Dienste stehen in "rendered", fehlgeschlagene in "errors". func (h *SystemHandler) RenderConfigs(c *gin.Context) { - if h.HAProxyReloader == nil { + if h.HAProxyReloader == nil && len(h.ExtraReloaders) == 0 { response.Err(c, http.StatusServiceUnavailable, simpleErr("renderer not wired")) return } - ctx, cancel := stdcontext.WithTimeout(c.Request.Context(), 10*time.Second) + ctx, cancel := stdcontext.WithTimeout(c.Request.Context(), 30*time.Second) defer cancel() - if err := h.HAProxyReloader(ctx); err != nil { - response.Internal(c, err) - return + + rendered := []string{} + errs := map[string]string{} + + if h.HAProxyReloader != nil { + if err := h.HAProxyReloader(ctx); err != nil { + errs["haproxy"] = err.Error() + } else { + rendered = append(rendered, "haproxy") + } + } + // Defined order so the audit log is deterministic. + order := []string{"nftables", "wireguard", "squid", "unbound", "chrony"} + for _, name := range order { + fn, ok := h.ExtraReloaders[name] + if !ok { + continue + } + if err := fn(ctx); err != nil { + errs[name] = err.Error() + } else { + rendered = append(rendered, name) + } } if h.Audit != nil { _ = h.Audit.Log(c.Request.Context(), actorOf(c), "system.render_configs", - "", gin.H{}, h.NodeID) + "", gin.H{"rendered": rendered, "errors": errs}, h.NodeID) } - response.OK(c, gin.H{"ok": true, "rendered": []string{"haproxy"}}) + response.OK(c, gin.H{"ok": len(errs) == 0, "rendered": rendered, "errors": errs}) } // UpgradeStatus liefert den Status des letzten Self-Upgrade-Versuchs. diff --git a/management-ui/src/i18n/locales/de/common.json b/management-ui/src/i18n/locales/de/common.json index 3caef33..fd8f8b5 100644 --- a/management-ui/src/i18n/locales/de/common.json +++ b/management-ui/src/i18n/locales/de/common.json @@ -531,9 +531,10 @@ "haproxyReloadBtn": "HAProxy reload", "haproxyReloadOk": "HAProxy neu geladen.", "haproxyReloadFailed": "HAProxy-Reload fehlgeschlagen", - "renderConfigsBtn": "Configs neu rendern (HAProxy)", - "renderConfigsOk": "Configs neu gerendert + reloaded.", + "renderConfigsBtn": "Alle Configs neu rendern", + "renderConfigsOk": "Alle Configs neu gerendert und reloaded.", "renderConfigsFailed": "Config-Render fehlgeschlagen", + "renderConfigsPartial": "Partieller Render — einige Dienste fehlgeschlagen: {{failed}}", "backupNowBtn": "Backup jetzt erstellen", "backupNowOk": "Backup ausgelöst — Status in der Backups-Seite verfolgen.", "backupNowFailed": "Backup-Trigger fehlgeschlagen", diff --git a/management-ui/src/i18n/locales/en/common.json b/management-ui/src/i18n/locales/en/common.json index d209f09..bb66194 100644 --- a/management-ui/src/i18n/locales/en/common.json +++ b/management-ui/src/i18n/locales/en/common.json @@ -531,9 +531,10 @@ "haproxyReloadBtn": "HAProxy reload", "haproxyReloadOk": "HAProxy reloaded.", "haproxyReloadFailed": "HAProxy reload failed", - "renderConfigsBtn": "Re-render configs (HAProxy)", - "renderConfigsOk": "Configs re-rendered + reloaded.", + "renderConfigsBtn": "Re-render all configs", + "renderConfigsOk": "All configs re-rendered and reloaded.", "renderConfigsFailed": "Config render failed", + "renderConfigsPartial": "Partial render — some services failed: {{failed}}", "backupNowBtn": "Backup now", "serviceRestartCardTitle": "Restart services", "serviceRestartBtn": "Restart", diff --git a/management-ui/src/pages/Settings/index.tsx b/management-ui/src/pages/Settings/index.tsx index 0a1c47e..b5aa0c8 100644 --- a/management-ui/src/pages/Settings/index.tsx +++ b/management-ui/src/pages/Settings/index.tsx @@ -125,7 +125,15 @@ export default function SettingsPage() { }) const renderConfigs = useMutation({ mutationFn: async () => apiClient.post('/system/render-configs'), - onSuccess: () => msg.success(t('settings.renderConfigsOk')), + onSuccess: (r) => { + const d = r.data?.data as { ok: boolean; rendered: string[]; errors: Record } | undefined + if (d && !d.ok && d.errors && Object.keys(d.errors).length > 0) { + const failed = Object.entries(d.errors).map(([k, v]) => `${k}: ${v}`).join('; ') + msg.warning(t('settings.renderConfigsPartial', { failed })) + } else { + msg.success(t('settings.renderConfigsOk')) + } + }, onError: (e: Error) => msg.error(t('settings.renderConfigsFailed') + ': ' + e.message), }) const triggerBackup = useMutation({