fix(system): render-configs rendert jetzt ALLE Dienste, nicht nur HAProxy

- 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 <noreply@anthropic.com>
This commit is contained in:
Debian
2026-05-24 22:29:00 +02:00
parent 8d7a43bc8c
commit 73619c17f8
8 changed files with 71 additions and 24 deletions

View File

@@ -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.