Bisher war der zweite Node nach dem Join zwar im Cluster registriert und in der UI sichtbar, replizierte aber keine einzige geteilte Tabelle — dafuer musste jemand manuell `edgeguard-ctl cluster-setup-standby` ausfuehren. Wer das uebersah, merkte es erst beim Failover: der neue Primary stand ohne Domains, Backends, Firewall-Regeln und WireGuard-Keys da. Das ist jetzt Teil des Join-Vorgangs. Beide Seiten muessen dafuer vorbereitet sein: 1) Primary, beim Erzeugen des Join-Tokens: ein frisch installierter Single-Node hat weder Replikations-Rolle noch PUBLICATION noch wal_level=logical. Ohne das liefe das spaetere CREATE SUBSCRIPTION in ein 404. Der Token wird deshalb erst ausgegeben, nachdem die Publisher-Seite steht — inklusive des einmaligen PG-Restarts (wal_level ist ein postmaster-Parameter), der bewusst hier passiert, solange der Admin danebensteht und noch kein Peer Traffic erwartet. WICHTIG dabei: setupReplicationPrimary rotiert bei jedem Lauf das Replikations-Passwort (ALTER ROLE … PASSWORD). Auf einem Cluster mit bereits angebundenem Subscriber wuerde ein zweiter Token-Klick dessen Connection-String ungueltig machen und die Replikation still anhalten. Deshalb laeuft die Initialisierung nur, wenn PUBLICATION und Secret nicht bereits existieren. 2) Neuer Node, nach erfolgreichem Join: cluster-setup-standby laeuft detached (die Initialkopie dauert je nach Datenmenge Minuten), der Wizard pollt GET /setup/replication-status und zeigt running/done/ failed an. Schlaegt es fehl, steht das manuelle Kommando inkl. Primary-Host direkt daneben statt nur einer Fehlermeldung. Beides braucht root (psql als postgres, pg_hba, PG-Restart), die API laeuft als unprivilegierter edgeguard → Aufruf via sudo mit gepinnten Regeln. Das einzige variable Argument (Primary-Host) wird vorher gegen Hostname/IP-Syntax geprueft; der Aufruf laeuft ohne Shell. Test dafuer liegt bei. Ausserdem zwei Doku-Korrekturen: architecture.md behauptete, cluster-join richte die Replikation gleich mit ein (tut es nicht, clusterjoin.Join macht nur Cert + Registrierung), und der Hinweistext von cluster-join verwies noch auf "PG-Basebackup + KeyDB, Phase 3.5" — beides laut Doku laengst verworfen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.2 KiB
Go
64 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/cluster/clustertls"
|
|
"git.netcell-it.de/projekte/edgeguard-native/internal/services/clusterjoin"
|
|
)
|
|
|
|
func cmdClusterJoin(args []string) int {
|
|
fs := flag.NewFlagSet("cluster-join", flag.ContinueOnError)
|
|
tokenFlag := fs.String("token", "", "cluster join token (eg-join-v1.…)")
|
|
insecure := fs.Bool("insecure", false, "skip TLS verification on the primary (bootstrap mode)")
|
|
cn := fs.String("cn", "", "subject common name (default: hostname)")
|
|
clusterTLSDir := fs.String("tls-dir", clustertls.DefaultDir, "where to write ca.crt + peer.{crt,key}")
|
|
fs.SetOutput(os.Stderr)
|
|
if err := fs.Parse(args); err != nil {
|
|
return 2
|
|
}
|
|
if fs.NArg() < 1 {
|
|
fmt.Fprintln(os.Stderr, "usage: edgeguard-ctl cluster-join <primary-fqdn-or-url> --token <…>")
|
|
return 2
|
|
}
|
|
if *tokenFlag == "" {
|
|
fmt.Fprintln(os.Stderr, "edgeguard-ctl cluster-join: --token required")
|
|
return 2
|
|
}
|
|
|
|
commonName := *cn
|
|
if commonName == "" {
|
|
h, _ := os.Hostname()
|
|
commonName = h
|
|
}
|
|
|
|
if err := clusterjoin.Join(clusterjoin.Request{
|
|
PrimaryFQDN: fs.Arg(0),
|
|
Token: *tokenFlag,
|
|
CommonName: commonName,
|
|
Insecure: *insecure,
|
|
TLSDir: *clusterTLSDir,
|
|
Version: version,
|
|
}); err != nil {
|
|
fmt.Fprintf(os.Stderr, "edgeguard-ctl cluster-join: %v\n", err)
|
|
return 1
|
|
}
|
|
|
|
primary, _ := clusterjoin.NormalizePrimaryURL(fs.Arg(0))
|
|
fmt.Printf("Cluster-Join erfolgreich.\n")
|
|
fmt.Printf(" Primary: %s\n", primary)
|
|
fmt.Printf(" CN: %s\n", commonName)
|
|
fmt.Printf(" Files: %s/{ca.crt,peer.crt,peer.key}\n", *clusterTLSDir)
|
|
fmt.Printf("\nNächste Schritte:\n")
|
|
fmt.Printf(" 1) sudo edgeguard-ctl cluster-setup-standby %s\n", primary)
|
|
fmt.Printf(" → richtet die Logical Replication ein. OHNE diesen Schritt ist der\n")
|
|
fmt.Printf(" Node zwar im Cluster, bekommt aber KEINE geteilte Config.\n")
|
|
fmt.Printf(" 2) sudo systemctl restart edgeguard-api # lädt das neue Cert in den mTLS-Agent-Listener\n")
|
|
fmt.Printf(" 3) Auf dem Primary in der Cluster-UI prüfen ob der neue Peer auftaucht\n")
|
|
fmt.Printf("\nHinweis: Beim Join über den Setup-Wizard passiert Schritt 1 automatisch;\n")
|
|
fmt.Printf("dieser CLI-Pfad ist der manuelle Weg und braucht ihn explizit.\n")
|
|
return 0
|
|
}
|