Vorher: Dashboard war Counts + statische Cards. Jetzt operativer Überblick — was läuft, was klemmt, was wurde gerade geändert. Backend (4 neue Endpoints): * GET /api/v1/system/services — systemctl is-active für 8 services (edgeguard-api, scheduler, haproxy, nftables, unbound, chrony, squid, postgresql). Inklusive ActiveEnterTimestamp. * GET /api/v1/system/resources — /proc/loadavg, meminfo, statfs(/), nf_conntrack count+max, uptime. * GET /api/v1/audit/recent?limit=N — letzte audit_log entries. audit-Repo bekommt ListRecent + Entry struct. * GET /api/v1/haproxy/stats — parsed haproxy 'show stat' CSV vom /run/haproxy/admin.sock (postinst addet edgeguard zu haproxy- group für socket-read; haproxy-group exists nach apt install). Frontend Dashboard rewrite: * PageHeader + KPI-Strip (6 tiles, wie zuvor) — bleibt. * Resources-Strip: Load (1/5/15) + Mem-Progress + Disk-Progress + Conntrack-Progress + Uptime. * Service-Health-Grid: 8 Karten mit StatusDot + state. * Recent-Activity-Card (audit-log): action-Tag + actor + subject + relative time. * HAProxy-Backends-Card: backend/server + UP/DOWN-Tag + sessions + bytes_in/out + last_change_age. * WireGuard live (handshake-age, traffic) — bleibt aus früherem Stand. * Cluster + Firewall + SSL + Routing Cards — bleiben. * Polling 10s für services/resources/haproxy, 15s für audit. Plus: postinst usermod -a -G haproxy edgeguard für admin.sock read-permission. Version 1.0.43. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
// edgeguard-scheduler runs background jobs that don't belong on the
|
|
// API request path:
|
|
//
|
|
// - ACME cert renewal (every 6h, re-issues anything < 30d to expiry)
|
|
//
|
|
// Future jobs (cluster heartbeat, backup, audit-log retention)
|
|
// hang off the same Tick loop. Stays single-process — no leader
|
|
// election yet (Phase 3).
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"time"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/acme"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/certrenewer"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/setup"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts"
|
|
)
|
|
|
|
var version = "1.0.43"
|
|
|
|
const (
|
|
// renewTickInterval — how often we re-evaluate expiring certs.
|
|
// 6h is enough: LE renewal window is 30 days; missing one tick
|
|
// makes no difference. Hourly would log too much.
|
|
renewTickInterval = 6 * time.Hour
|
|
|
|
// certDir matches handlers.NewTLSCertsHandler default — HAProxy
|
|
// reads from this directory.
|
|
certDir = "/etc/edgeguard/tls"
|
|
)
|
|
|
|
func main() {
|
|
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})))
|
|
slog.Info("edgeguard-scheduler starting", "version", version)
|
|
|
|
ctx := context.Background()
|
|
|
|
pool, err := database.Open(ctx, database.ConnStringFromEnv())
|
|
if err != nil {
|
|
slog.Error("scheduler: DB open failed — sleeping forever", "error", err)
|
|
select {}
|
|
}
|
|
defer pool.Close()
|
|
|
|
tlsRepo := tlscerts.New(pool)
|
|
setupStore := setup.NewStore(setup.DefaultDir)
|
|
st, _ := setupStore.Load()
|
|
|
|
var renewer *certrenewer.Service
|
|
if st != nil && st.ACMEEmail != "" {
|
|
issuer := acme.New(st.ACMEEmail)
|
|
renewer = certrenewer.New(tlsRepo, issuer, certDir, 30*24*time.Hour)
|
|
slog.Info("scheduler: ACME renewer enabled",
|
|
"email", st.ACMEEmail, "tick", renewTickInterval, "threshold", "30d")
|
|
} else {
|
|
slog.Warn("scheduler: setup.acme_email empty — ACME renewal disabled until setup wizard ran")
|
|
}
|
|
|
|
if renewer != nil {
|
|
runRenewer(ctx, renewer)
|
|
}
|
|
tick := time.NewTicker(renewTickInterval)
|
|
defer tick.Stop()
|
|
for range tick.C {
|
|
if renewer != nil {
|
|
runRenewer(ctx, renewer)
|
|
}
|
|
}
|
|
}
|
|
|
|
func runRenewer(ctx context.Context, r *certrenewer.Service) {
|
|
res, err := r.Run(ctx)
|
|
if err != nil {
|
|
slog.Error("scheduler: renewer run failed", "error", err)
|
|
return
|
|
}
|
|
slog.Info("scheduler: renewer pass complete",
|
|
"checked", res.Checked, "renewed", res.Renewed,
|
|
"failed", res.Failed, "skipped", res.Skipped)
|
|
}
|