refactor(cluster): promote auf Logical-Replication umgestellt + internal/proxy-Stub entfernt — v1.2.99
Code-Altlasten aus dem Architektur-Audit bereinigt: - internal/proxy: leerer .gitkeep-Stub (geplanter Write-Proxy nie implementiert) entfernt — keine Go-Referenzen. - promote.go: war reines Physical-Replication-Failover (standby.signal + pg_ctlcluster promote + pg_is_in_recovery) und damit auf dem Logical-Setup TOT (ein Subscriber hat kein standby.signal / ist nie in recovery → Abbruch bei Schritt 1). Neu Logical-aware: Idempotenz-Check (schon Publisher ohne Subscription → fertig) → Subscription lösen (DISABLE+slot_name=NONE+DROP, hängt nicht am toten Publisher) → setupReplicationPrimary (Publisher werden) → ha_nodes.pg_role=primary → keepalived MASTER. Toter KeyDB-Update (cluster:pg-primary-url, wurde nie gelesen) entfernt. - setupReplicationPrimary + dropSubscriptionIfExists aus cluster-init-replication/cluster-setup-standby extrahiert (DRY, bewährte SQL wiederverwendet). WICHTIG: setupReplicationPrimary stellt jetzt sicher dass wal_level=logical AKTIV ist — PG-RESTART falls nötig (reload reicht für wal_level/max_wal_senders nicht; Secondary hat wal_level=replica). Idempotent: Restart nur wenn wal_level != logical. - Doku (CLAUDE.md + architecture.md) auf den bereinigten Stand gezogen. Hinweis: echtes Cross-Node-Failover ist nur im Drill testbar; Build/vet/Tests grün, Bausteine sind die bereits produktiv genutzten SQL-Primitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -115,11 +115,29 @@ func cmdClusterInitReplication(args []string) int {
|
||||
}
|
||||
fmt.Printf("→ PostgreSQL %s/%s erkannt\n", pg.Version, pg.Cluster)
|
||||
|
||||
if err := setupReplicationPrimary(pg); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication:", err)
|
||||
return 1
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Nächste Schritte:")
|
||||
fmt.Println(" 1) Auf dem Secondary: edgeguard-ctl cluster-setup-standby <primary-ip>")
|
||||
fmt.Println(" 2) Cluster-Settings (VIP) auf BEIDEN Nodes separat konfigurieren")
|
||||
fmt.Println(" → Settings → Cluster → VIP/Keepalived")
|
||||
return 0
|
||||
}
|
||||
|
||||
// setupReplicationPrimary konfiguriert die lokale PG-Instanz als Logical-
|
||||
// Replication-Primary: Replikations-Rolle + Secret, conf.d (wal_level=logical),
|
||||
// pg_hba, SELECT-Grants, PUBLICATION. Stellt sicher dass wal_level=logical
|
||||
// AKTIV ist (Restart nur falls nötig — für wal_level reicht reload nicht).
|
||||
// Idempotent. Gemeinsam genutzt von cluster-init-replication und promote.
|
||||
func setupReplicationPrimary(pg pgConfig) error {
|
||||
// 1. Passwort generieren
|
||||
pass, err := generatePassword(32)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: generate password:", err)
|
||||
return 1
|
||||
return fmt.Errorf("generate password: %w", err)
|
||||
}
|
||||
|
||||
// 2. edgeguard_replicator-Rolle anlegen/updaten
|
||||
@@ -133,21 +151,17 @@ BEGIN
|
||||
END
|
||||
$$`, egReplUser, egReplUser, pass, egReplUser, pass)
|
||||
if err := psqlExec(roleSQL); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: create replication role:", err)
|
||||
return 1
|
||||
return fmt.Errorf("create replication role: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ Replication-Rolle %q angelegt/aktualisiert\n", egReplUser)
|
||||
|
||||
// 3. Passwort speichern
|
||||
// 3. Passwort speichern (Ownership an edgeguard-User, damit die API liest)
|
||||
if err := os.MkdirAll(filepath.Dir(egReplSecret), 0o750); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: mkdir:", err)
|
||||
return 1
|
||||
return fmt.Errorf("mkdir: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(egReplSecret, []byte(pass), 0o600); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: write secret:", err)
|
||||
return 1
|
||||
return fmt.Errorf("write secret: %w", err)
|
||||
}
|
||||
// Ownership an edgeguard-api-User übergeben damit die API lesen kann
|
||||
if u, err := user.Lookup("edgeguard"); err == nil {
|
||||
uid, _ := strconv.Atoi(u.Uid)
|
||||
gid, _ := strconv.Atoi(u.Gid)
|
||||
@@ -156,71 +170,88 @@ $$`, egReplUser, egReplUser, pass, egReplUser, pass)
|
||||
fmt.Printf("✓ Replication-Secret gespeichert: %s\n", egReplSecret)
|
||||
|
||||
// 4. conf.d/edgeguard-replication.conf schreiben
|
||||
// wal_level=logical ist eine Obermenge von replica — unterstützt
|
||||
// sowohl Logical Replication als auch ggfs. physisches WAL-Archiving.
|
||||
if err := os.MkdirAll(pg.ConfD, 0o755); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: conf.d mkdir:", err)
|
||||
return 1
|
||||
return fmt.Errorf("conf.d mkdir: %w", err)
|
||||
}
|
||||
replConf := `# EdgeGuard Logical Replication — automatisch generiert
|
||||
# Nicht manuell bearbeiten; wird von edgeguard-ctl cluster-init-replication verwaltet.
|
||||
# Nicht manuell bearbeiten; wird von edgeguard-ctl verwaltet.
|
||||
wal_level = logical
|
||||
max_wal_senders = 10
|
||||
max_replication_slots = 20
|
||||
max_logical_replication_workers = 4
|
||||
wal_keep_size = 512MB
|
||||
# Lausche auf localhost + alle konfigurierten Interfaces damit Cluster-Peers
|
||||
# sich verbinden können. '*' ist sicher weil pg_hba.conf den Zugriff auf
|
||||
# bekannte Replikations-User beschränkt.
|
||||
# '*' ist sicher weil pg_hba.conf den Zugriff auf bekannte Replikations-User beschränkt.
|
||||
listen_addresses = '*'
|
||||
`
|
||||
confPath := filepath.Join(pg.ConfD, "edgeguard-replication.conf")
|
||||
if err := os.WriteFile(confPath, []byte(replConf), 0o644); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: write postgresql conf:", err)
|
||||
return 1
|
||||
return fmt.Errorf("write postgresql conf: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ %s geschrieben (wal_level=logical)\n", confPath)
|
||||
|
||||
// 5. pg_hba.conf aktualisieren
|
||||
if err := ensureHBAReplication(pg.HBAPath); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: pg_hba.conf:", err)
|
||||
return 1
|
||||
return fmt.Errorf("pg_hba.conf: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ %s aktualisiert\n", pg.HBAPath)
|
||||
|
||||
// 6. PG reload (damit wal_level + pg_hba aktiv werden)
|
||||
// 6. PG reload (pg_hba aktiv). wal_level/max_wal_senders sind aber
|
||||
// postmaster-Parameter → nur per RESTART aktiv. Nur restarten wenn nötig.
|
||||
if out, err := exec.Command("pg_ctlcluster", pg.Version, pg.Cluster, "reload").CombinedOutput(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "cluster-init-replication: pg reload failed: %v\n%s\n", err, out)
|
||||
return 1
|
||||
return fmt.Errorf("pg reload: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
fmt.Printf("✓ PostgreSQL %s/%s neu geladen\n", pg.Version, pg.Cluster)
|
||||
if cur, _ := psqlRun([]string{"-tA", "-c", "SHOW wal_level;"}); strings.TrimSpace(string(cur)) != "logical" {
|
||||
fmt.Println("→ wal_level wechselt auf 'logical' — PostgreSQL-Restart nötig...")
|
||||
if out, err := exec.Command("pg_ctlcluster", pg.Version, pg.Cluster, "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("pg restart: %w: %s", err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
ready := false
|
||||
deadline := time.Now().Add(60 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if _, err := psqlRun([]string{"-tA", "-c", "SELECT 1;"}); err == nil {
|
||||
ready = true
|
||||
break
|
||||
}
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
if !ready {
|
||||
return fmt.Errorf("PostgreSQL kam nach Restart binnen 60s nicht zurück — prüfe PG-Logs")
|
||||
}
|
||||
fmt.Println("✓ PostgreSQL neu gestartet (wal_level=logical aktiv)")
|
||||
}
|
||||
|
||||
// 7. SELECT-Grants: edgeguard_replicator muss alle zu replizierenden
|
||||
// Tabellen lesen können. DEFAULT PRIVILEGES sichert zukünftige Tabellen.
|
||||
// 7. SELECT-Grants (DEFAULT PRIVILEGES sichert zukünftige Tabellen)
|
||||
grantSQL := fmt.Sprintf(`
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA public TO %s;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO %s;
|
||||
`, egReplUser, egReplUser)
|
||||
if err := psqlDBExec("edgeguard", grantSQL); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: grant SELECT:", err)
|
||||
return 1
|
||||
return fmt.Errorf("grant SELECT: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ SELECT auf alle Tabellen für %q gewährt\n", egReplUser)
|
||||
|
||||
// 8. PUBLICATION erstellen — alle public-Tabellen außer localOnlyTables.
|
||||
// Idempotent: DROP IF EXISTS + CREATE.
|
||||
// 8. PUBLICATION (idempotent: DROP IF EXISTS + CREATE)
|
||||
if err := createPublication(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cluster-init-replication: create publication:", err)
|
||||
return 1
|
||||
return fmt.Errorf("create publication: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ PUBLICATION %q erstellt\n", egPubName)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Nächste Schritte:")
|
||||
fmt.Println(" 1) Auf dem Secondary: edgeguard-ctl cluster-setup-standby <primary-ip>")
|
||||
fmt.Println(" 2) Cluster-Settings (VIP) auf BEIDEN Nodes separat konfigurieren")
|
||||
fmt.Println(" → Settings → Cluster → VIP/Keepalived")
|
||||
return 0
|
||||
// dropSubscriptionIfExists entfernt die lokale Logical-Replication-Subscription
|
||||
// idempotent. DISABLE + slot_name=NONE VOR DROP, damit DROP nicht versucht den
|
||||
// Slot auf dem (beim Failover evtl. toten) Publisher zu löschen → kein Hängen.
|
||||
func dropSubscriptionIfExists() error {
|
||||
dropSQL := fmt.Sprintf(`
|
||||
DO $$ BEGIN
|
||||
IF EXISTS (SELECT FROM pg_subscription WHERE subname = '%s') THEN
|
||||
ALTER SUBSCRIPTION %s DISABLE;
|
||||
ALTER SUBSCRIPTION %s SET (slot_name = NONE);
|
||||
DROP SUBSCRIPTION %s;
|
||||
END IF;
|
||||
END $$;`, egSubName, egSubName, egSubName, egSubName)
|
||||
return psqlDBExec("edgeguard", dropSQL)
|
||||
}
|
||||
|
||||
// createPublication baut die PUBLICATION dynamisch aus allen Tabellen
|
||||
@@ -334,15 +365,7 @@ func cmdClusterSetupStandby(args []string) int {
|
||||
fmt.Printf("✓ Replication-Credentials von %s:%d erhalten\n", primaryHost, *agentPort)
|
||||
|
||||
// 2. Bestehende Subscription löschen (idempotent)
|
||||
dropSQL := fmt.Sprintf(`
|
||||
DO $$ BEGIN
|
||||
IF EXISTS (SELECT FROM pg_subscription WHERE subname = '%s') THEN
|
||||
ALTER SUBSCRIPTION %s DISABLE;
|
||||
ALTER SUBSCRIPTION %s SET (slot_name = NONE);
|
||||
DROP SUBSCRIPTION %s;
|
||||
END IF;
|
||||
END $$;`, egSubName, egSubName, egSubName, egSubName)
|
||||
if err := psqlDBExec("edgeguard", dropSQL); err != nil {
|
||||
if err := dropSubscriptionIfExists(); err != nil {
|
||||
// Nicht fatal — wenn PG noch keine Subscription kennt ist das OK
|
||||
fmt.Printf(" → keine bestehende Subscription gefunden (ok)\n")
|
||||
} else {
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -14,18 +12,22 @@ import (
|
||||
"git.netcell-it.de/projekte/edgeguard-native/internal/keepalived"
|
||||
)
|
||||
|
||||
// cmdPromote promotes this node's PostgreSQL instance from Hot-Standby
|
||||
// to Primary. Manual failover — keine automatische Promotion, um Split-Brain
|
||||
// in 2-Node-Clustern ohne externen Quorum zu verhindern.
|
||||
// cmdPromote befördert diese Node zum Logical-Replication-Primary. Manuelles
|
||||
// Failover — keine automatische Promotion, um Split-Brain in 2-Node-Clustern
|
||||
// ohne externes Quorum zu verhindern.
|
||||
//
|
||||
// Hintergrund: Die Replikation ist LOGICAL (Publication/Subscription), nicht
|
||||
// physisch. Ein Subscriber ist eine normale beschreibbare PG-Instanz (nie „in
|
||||
// recovery", kein standby.signal). „Promote" heißt darum: Subscription zum
|
||||
// (toten/alten) Primary lösen und selbst Publisher werden.
|
||||
//
|
||||
// Ablauf:
|
||||
// 1. Prüfen ob standby.signal vorhanden (wir sind wirklich Standby)
|
||||
// 2. pg_ctlcluster promote → PG wird Primary
|
||||
// 3. Warten bis pg_is_in_recovery() = false
|
||||
// 4. ha_nodes.pg_role auf 'primary' setzen
|
||||
// 5. KeyDB cluster:pg-primary-url auf lokal setzen
|
||||
// 6. keepalived.conf neu rendern (Primary bekommt Priorität 200)
|
||||
// 7. keepalived reload
|
||||
// 1. Idempotenz-Check: schon Publisher ohne Subscription → fertig
|
||||
// 2. Subscription lösen (DISABLE + slot_name=NONE + DROP)
|
||||
// 3. setupReplicationPrimary: Rolle/Secret/conf.d/pg_hba/Grants/Publication
|
||||
// + sicherstellen dass wal_level=logical aktiv ist (PG-Restart falls nötig)
|
||||
// 4. ha_nodes.pg_role/role = 'primary'
|
||||
// 5. keepalived neu rendern (Primary = Priorität 200 = MASTER → übernimmt VIP)
|
||||
func cmdPromote(args []string) int {
|
||||
pg, err := detectPGConfig()
|
||||
if err != nil {
|
||||
@@ -33,51 +35,43 @@ func cmdPromote(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// 1. Standby-Signal prüfen
|
||||
signalPath := filepath.Join(pg.DataDir, "standby.signal")
|
||||
if _, err := os.Stat(signalPath); os.IsNotExist(err) {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"promote: %s nicht gefunden — diese Node ist kein PG-Standby oder wurde bereits promoted.\n",
|
||||
signalPath)
|
||||
return 1
|
||||
// 1. Idempotenz: bereits Publisher (Primary) ohne Subscription?
|
||||
pubOut, _ := psqlDBRun("edgeguard", []string{"-tA", "-c",
|
||||
fmt.Sprintf("SELECT count(*) FROM pg_publication WHERE pubname='%s';", egPubName)})
|
||||
subOut, _ := psqlDBRun("edgeguard", []string{"-tA", "-c",
|
||||
fmt.Sprintf("SELECT count(*) FROM pg_subscription WHERE subname='%s';", egSubName)})
|
||||
hasPub := strings.TrimSpace(string(pubOut)) == "1"
|
||||
hasSub := strings.TrimSpace(string(subOut)) == "1"
|
||||
if hasPub && !hasSub {
|
||||
fmt.Println("✓ Diese Node ist bereits Logical-Replication-Primary (Publication vorhanden, keine Subscription). Nichts zu tun.")
|
||||
return 0
|
||||
}
|
||||
|
||||
fmt.Printf("→ Promoting PostgreSQL %s/%s zu Primary...\n", pg.Version, pg.Cluster)
|
||||
if out, err := exec.Command("pg_ctlcluster", pg.Version, pg.Cluster, "promote").
|
||||
CombinedOutput(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "promote: pg_ctlcluster promote: %v\n%s\n", err, out)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("✓ pg_ctlcluster promote gesendet")
|
||||
fmt.Printf("→ Promote zu Logical-Replication-Primary (PostgreSQL %s/%s)...\n", pg.Version, pg.Cluster)
|
||||
|
||||
// 2. Warten bis PG wirklich Primary ist (pg_is_in_recovery = false)
|
||||
fmt.Print("→ Warte auf PG Primary-Mode")
|
||||
deadline := time.Now().Add(60 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
out, err := psqlRun([]string{"-tA", "-c", "SELECT pg_is_in_recovery();"})
|
||||
if err == nil && strings.TrimSpace(string(out)) == "f" {
|
||||
break
|
||||
// 2. Subscription zum alten/toten Primary lösen
|
||||
if hasSub {
|
||||
if err := dropSubscriptionIfExists(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "promote: Subscription lösen:", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Print(".")
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("✓ Subscription zum alten Primary entfernt")
|
||||
}
|
||||
fmt.Println()
|
||||
// Nochmal prüfen
|
||||
out, err := psqlRun([]string{"-tA", "-c", "SELECT pg_is_in_recovery();"})
|
||||
if err != nil || strings.TrimSpace(string(out)) != "f" {
|
||||
fmt.Fprintln(os.Stderr, "promote: PG ist nach 60s noch in recovery — prüfe PG-Logs")
|
||||
|
||||
// 3. Diese Node als Publisher einrichten (inkl. wal_level=logical + Restart)
|
||||
if err := setupReplicationPrimary(pg); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "promote:", err)
|
||||
return 1
|
||||
}
|
||||
fmt.Println("✓ PostgreSQL ist jetzt Primary")
|
||||
|
||||
// 3. ha_nodes.pg_role + role aktualisieren
|
||||
// 4. ha_nodes-Rolle aktualisieren
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pool, err := database.Open(ctx, database.ConnStringFromEnv())
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "promote: db connect:", err)
|
||||
fmt.Println(" → ha_nodes manuell updaten: UPDATE ha_nodes SET pg_role='primary', role='primary' WHERE id='<local-id>';")
|
||||
fmt.Println(" → ha_nodes manuell: UPDATE ha_nodes SET pg_role='primary', role='primary' WHERE id='<local-id>';")
|
||||
} else {
|
||||
defer pool.Close()
|
||||
localID, err := loadLocalID()
|
||||
@@ -93,15 +87,7 @@ func cmdPromote(args []string) int {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. KeyDB cluster:pg-primary-url updaten
|
||||
if err := updateKeyDBPrimaryURL(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "promote: KeyDB update: %v\n", err)
|
||||
fmt.Println(" → Manuell: redis-cli SET cluster:pg-primary-url 'postgres://edgeguard@/edgeguard'")
|
||||
} else {
|
||||
fmt.Println("✓ KeyDB cluster:pg-primary-url aktualisiert")
|
||||
}
|
||||
|
||||
// 5. Keepalived.conf neu rendern (Primary = Priorität 200)
|
||||
// 5. keepalived.conf neu rendern (Primary = MASTER, Priority 200 → VIP)
|
||||
if pool != nil {
|
||||
localID, _ := loadLocalID()
|
||||
kg := keepalived.New(pool, localID)
|
||||
@@ -109,9 +95,9 @@ func cmdPromote(args []string) int {
|
||||
defer renderCancel()
|
||||
if err := kg.Render(renderCtx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "promote: keepalived render: %v\n", err)
|
||||
fmt.Println(" → Manuell: edgeguard-ctl render-config --only=keepalived")
|
||||
fmt.Println(" → Manuell: sudo -u edgeguard edgeguard-ctl render-config --only=keepalived")
|
||||
} else {
|
||||
fmt.Println("✓ keepalived.conf neu gerendert (Priority 200)")
|
||||
fmt.Println("✓ keepalived.conf neu gerendert (MASTER, Priority 200)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,8 +105,8 @@ func cmdPromote(args []string) int {
|
||||
fmt.Println("✓ Promotion abgeschlossen. Diese Node ist jetzt der primäre EdgeGuard-Knoten.")
|
||||
fmt.Println()
|
||||
fmt.Println("Empfohlene Nachschritte:")
|
||||
fmt.Println(" 1) sudo systemctl restart edgeguard-api (falls noch nicht laufend)")
|
||||
fmt.Println(" 2) Alte Primary-Node nach Recovery als neuen Standby einrichten:")
|
||||
fmt.Println(" 1) sudo systemctl restart edgeguard-api")
|
||||
fmt.Println(" 2) Übrige/erholte Nodes als Standby auf DIESE Node zeigen lassen:")
|
||||
fmt.Println(" edgeguard-ctl cluster-setup-standby <diese-node-ip>")
|
||||
return 0
|
||||
}
|
||||
@@ -136,25 +122,3 @@ func loadLocalID() (string, error) {
|
||||
}
|
||||
return c.NodeID, nil
|
||||
}
|
||||
|
||||
// updateKeyDBPrimaryURL schreibt den lokalen PG-DSN als cluster:pg-primary-url
|
||||
// in KeyDB, damit alle Nodes im Cluster Writes an diese Node schicken.
|
||||
func updateKeyDBPrimaryURL() error {
|
||||
// edgeguard-api nutzt Unix-Socket-Auth, der DSN ist immer lokal.
|
||||
const localDSN = "postgres://edgeguard@/edgeguard?host=/var/run/postgresql"
|
||||
out, err := exec.Command("redis-cli",
|
||||
"-s", "/var/run/keydb/keydb.sock",
|
||||
"SET", "cluster:pg-primary-url", localDSN,
|
||||
).CombinedOutput()
|
||||
if err != nil {
|
||||
// Fallback: Standard-Port
|
||||
out2, err2 := exec.Command("redis-cli",
|
||||
"-p", "6379",
|
||||
"SET", "cluster:pg-primary-url", localDSN,
|
||||
).CombinedOutput()
|
||||
if err2 != nil {
|
||||
return fmt.Errorf("%v: %s / %v: %s", err, out, err2, out2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user