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:
@@ -60,7 +60,7 @@ import (
|
|||||||
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.95"
|
var version = "1.1.96"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
addr := os.Getenv("EDGEGUARD_API_ADDR")
|
||||||
@@ -401,6 +401,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
handlers.NewNTPHandler(ntpRepo, auditRepo, nodeID, withFW(chronyReloader)).Register(authed)
|
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
|
// License — node-local key store + DB-mirror of last verify
|
||||||
// result. Real verify runs against license.netcell-it.com via
|
// result. Real verify runs against license.netcell-it.com via
|
||||||
// internal/license; the scheduler triggers daily re-verify.
|
// internal/license; the scheduler triggers daily re-verify.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
"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
|
const usage = `edgeguard-ctl — EdgeGuard CLI
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.1.95"
|
var version = "1.1.96"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// renewTickInterval — how often we re-evaluate expiring certs.
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ type SystemHandler struct {
|
|||||||
// in main.go after generators are constructed. Read-only access —
|
// in main.go after generators are constructed. Read-only access —
|
||||||
// no writes, no reloads.
|
// no writes, no reloads.
|
||||||
ConfigPreviewers map[string]func(stdcontext.Context) (string, error)
|
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 {
|
func NewSystemHandler(version string) *SystemHandler {
|
||||||
@@ -75,6 +78,14 @@ func (h *SystemHandler) WithConfigPreviewers(previewers map[string]func(stdconte
|
|||||||
return h
|
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) {
|
func (h *SystemHandler) Register(rg *gin.RouterGroup) {
|
||||||
g := rg.Group("/system")
|
g := rg.Group("/system")
|
||||||
g.GET("/health", h.Health)
|
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
|
// RenderConfigs erzwingt ein Re-Render aller Service-Configs aus dem
|
||||||
// aktuellen DB-State. Wenn der HAProxyReloader gesetzt ist, läuft der
|
// aktuellen DB-State. Läuft haproxy + alle ExtraReloaders (nftables,
|
||||||
// (rendert haproxy.cfg + reload). Praktisch wenn ein Operator denkt
|
// wireguard, squid, unbound, chrony) durch. Fehler werden gesammelt
|
||||||
// dass die generierte Config nicht mehr mit der DB übereinstimmt
|
// und als partial-Antwort zurückgegeben — erfolgreich gerenderte
|
||||||
// (Drift, Manual-Edit, etc.).
|
// Dienste stehen in "rendered", fehlgeschlagene in "errors".
|
||||||
//
|
|
||||||
// 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".
|
|
||||||
func (h *SystemHandler) RenderConfigs(c *gin.Context) {
|
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"))
|
response.Err(c, http.StatusServiceUnavailable, simpleErr("renderer not wired"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx, cancel := stdcontext.WithTimeout(c.Request.Context(), 10*time.Second)
|
ctx, cancel := stdcontext.WithTimeout(c.Request.Context(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
if err := h.HAProxyReloader(ctx); err != nil {
|
|
||||||
response.Internal(c, err)
|
rendered := []string{}
|
||||||
return
|
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 {
|
if h.Audit != nil {
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "system.render_configs",
|
_ = 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.
|
// UpgradeStatus liefert den Status des letzten Self-Upgrade-Versuchs.
|
||||||
|
|||||||
@@ -531,9 +531,10 @@
|
|||||||
"haproxyReloadBtn": "HAProxy reload",
|
"haproxyReloadBtn": "HAProxy reload",
|
||||||
"haproxyReloadOk": "HAProxy neu geladen.",
|
"haproxyReloadOk": "HAProxy neu geladen.",
|
||||||
"haproxyReloadFailed": "HAProxy-Reload fehlgeschlagen",
|
"haproxyReloadFailed": "HAProxy-Reload fehlgeschlagen",
|
||||||
"renderConfigsBtn": "Configs neu rendern (HAProxy)",
|
"renderConfigsBtn": "Alle Configs neu rendern",
|
||||||
"renderConfigsOk": "Configs neu gerendert + reloaded.",
|
"renderConfigsOk": "Alle Configs neu gerendert und reloaded.",
|
||||||
"renderConfigsFailed": "Config-Render fehlgeschlagen",
|
"renderConfigsFailed": "Config-Render fehlgeschlagen",
|
||||||
|
"renderConfigsPartial": "Partieller Render — einige Dienste fehlgeschlagen: {{failed}}",
|
||||||
"backupNowBtn": "Backup jetzt erstellen",
|
"backupNowBtn": "Backup jetzt erstellen",
|
||||||
"backupNowOk": "Backup ausgelöst — Status in der Backups-Seite verfolgen.",
|
"backupNowOk": "Backup ausgelöst — Status in der Backups-Seite verfolgen.",
|
||||||
"backupNowFailed": "Backup-Trigger fehlgeschlagen",
|
"backupNowFailed": "Backup-Trigger fehlgeschlagen",
|
||||||
|
|||||||
@@ -531,9 +531,10 @@
|
|||||||
"haproxyReloadBtn": "HAProxy reload",
|
"haproxyReloadBtn": "HAProxy reload",
|
||||||
"haproxyReloadOk": "HAProxy reloaded.",
|
"haproxyReloadOk": "HAProxy reloaded.",
|
||||||
"haproxyReloadFailed": "HAProxy reload failed",
|
"haproxyReloadFailed": "HAProxy reload failed",
|
||||||
"renderConfigsBtn": "Re-render configs (HAProxy)",
|
"renderConfigsBtn": "Re-render all configs",
|
||||||
"renderConfigsOk": "Configs re-rendered + reloaded.",
|
"renderConfigsOk": "All configs re-rendered and reloaded.",
|
||||||
"renderConfigsFailed": "Config render failed",
|
"renderConfigsFailed": "Config render failed",
|
||||||
|
"renderConfigsPartial": "Partial render — some services failed: {{failed}}",
|
||||||
"backupNowBtn": "Backup now",
|
"backupNowBtn": "Backup now",
|
||||||
"serviceRestartCardTitle": "Restart services",
|
"serviceRestartCardTitle": "Restart services",
|
||||||
"serviceRestartBtn": "Restart",
|
"serviceRestartBtn": "Restart",
|
||||||
|
|||||||
@@ -125,7 +125,15 @@ export default function SettingsPage() {
|
|||||||
})
|
})
|
||||||
const renderConfigs = useMutation({
|
const renderConfigs = useMutation({
|
||||||
mutationFn: async () => apiClient.post('/system/render-configs'),
|
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<string, string> } | 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),
|
onError: (e: Error) => msg.error(t('settings.renderConfigsFailed') + ': ' + e.message),
|
||||||
})
|
})
|
||||||
const triggerBackup = useMutation({
|
const triggerBackup = useMutation({
|
||||||
|
|||||||
Reference in New Issue
Block a user