feat(cluster): (c) Phase-3 MVP — stable node-id + self-register + Cluster-Page
Minimal-Slice für Phase-3-Cluster: * internal/cluster/node_id.go — stable UUID 'n-<16hex>' in /var/lib/edgeguard/node-id, idempotent über reboots. * internal/cluster/store.go — ha_nodes-Repo (List/Get/UpsertSelf) via pgxpool. EnsureSelfRegistered upsertet die lokale Row beim Boot mit FQDN aus setup.json. * internal/handlers/cluster.go — GET /api/v1/cluster/nodes liefert alle ha_nodes plus local_id (für UI-Highlighting). * main.go: nach DB-Pool-Open wird EnsureSelfRegistered (nur wenn setup.completed) ausgeführt, ClusterHandler registriert. * management-ui/src/pages/Cluster/index.tsx — Tabelle mit Node-ID, FQDN, Rolle, Beitrittszeit; eigene Node mit "diese Node"-Tag markiert. Sidebar-Eintrag + i18n de/en. Bewusst NICHT in dieser Runde: cluster-init/cluster-join CLIs, KeyDB Active-Active config-gen, PG streaming replication, mTLS zwischen Peers, License-Leader-Election. Diese kommen mit dem ersten echten Multi-Node-Test (Phase 3.1) — sonst Code ohne Smoke-Möglichkeit. End-to-end-Smoke: setup → restart → ha_nodes hat 1 Row mit fqdn=eg.example.com, /cluster/nodes liefert sie korrekt mit local_id-Markierung. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/database"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers"
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response"
|
||||
@@ -93,7 +94,25 @@ func main() {
|
||||
"error", err)
|
||||
} else {
|
||||
slog.Info("DB pool open, registering CRUD handlers")
|
||||
nodeID := nodeIDOrHostname()
|
||||
|
||||
nodeID, nodeErr := cluster.EnsureNodeID("")
|
||||
if nodeErr != nil {
|
||||
slog.Warn("node-id not persisted, using ephemeral",
|
||||
"id", nodeID, "error", nodeErr)
|
||||
}
|
||||
clusterStore := cluster.NewStore(pool)
|
||||
|
||||
// Self-register in ha_nodes — only if setup is complete
|
||||
// (we want the operator-defined FQDN, not the OS hostname,
|
||||
// to land in api_url). Failures are logged but non-fatal.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
st, _ := setupStore.Load()
|
||||
if st != nil && st.Completed {
|
||||
if _, err := cluster.EnsureSelfRegistered(ctx, clusterStore, st.FQDN, "primary"); err != nil {
|
||||
slog.Warn("self-register in ha_nodes failed", "error", err)
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
|
||||
auditRepo := audit.New(pool)
|
||||
domainsRepo := domains.New(pool)
|
||||
@@ -105,6 +124,7 @@ func main() {
|
||||
handlers.NewDomainsHandler(domainsRepo, routingRepo, auditRepo, nodeID).Register(authed)
|
||||
handlers.NewBackendsHandler(backendsRepo, auditRepo, nodeID).Register(authed)
|
||||
handlers.NewRoutingRulesHandler(routingRepo, auditRepo, nodeID).Register(authed)
|
||||
handlers.NewClusterHandler(clusterStore, nodeID).Register(authed)
|
||||
}
|
||||
|
||||
mountUI(r)
|
||||
|
||||
Reference in New Issue
Block a user