From 6a5aff69d950c81eb335e2ff2ad35fdc827cd405 Mon Sep 17 00:00:00 2001 From: Debian Date: Fri, 29 May 2026 17:21:17 +0200 Subject: [PATCH] =?UTF-8?q?fix(setup):=20CompleteAsNode=20idempotent=20?= =?UTF-8?q?=E2=80=94=20konvertiert=20Standalone=20zu=20Cluster-Node?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wenn setup bereits abgeschlossen war (z.B. zuvor als Standalone eingerichtet), wird der Node jetzt ohne Fehler auf is_cluster_node=true umgestellt statt mit "setup already completed" zu fehlschlagen. Co-Authored-By: Claude Sonnet 4.6 --- VERSION | 2 +- cmd/edgeguard-api/main.go | 2 +- cmd/edgeguard-ctl/main.go | 2 +- cmd/edgeguard-scheduler/main.go | 2 +- internal/services/setup/setup.go | 18 ++++++++++++++---- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/VERSION b/VERSION index 3d370b4..4b7ce3d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.151 +1.1.152 diff --git a/cmd/edgeguard-api/main.go b/cmd/edgeguard-api/main.go index 0523620..4254c91 100644 --- a/cmd/edgeguard-api/main.go +++ b/cmd/edgeguard-api/main.go @@ -60,7 +60,7 @@ import ( usersvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/users" ) -var version = "1.1.151" +var version = "1.1.152" func main() { addr := os.Getenv("EDGEGUARD_API_ADDR") diff --git a/cmd/edgeguard-ctl/main.go b/cmd/edgeguard-ctl/main.go index 0d3e993..ce3568d 100644 --- a/cmd/edgeguard-ctl/main.go +++ b/cmd/edgeguard-ctl/main.go @@ -11,7 +11,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/setup" ) -var version = "1.1.151" +var version = "1.1.152" const usage = `edgeguard-ctl — EdgeGuard CLI diff --git a/cmd/edgeguard-scheduler/main.go b/cmd/edgeguard-scheduler/main.go index 3a62d46..602dfe5 100644 --- a/cmd/edgeguard-scheduler/main.go +++ b/cmd/edgeguard-scheduler/main.go @@ -41,7 +41,7 @@ import ( "git.netcell-it.de/projekte/edgeguard-native/internal/services/tlscerts" ) -var version = "1.1.151" +var version = "1.1.152" const ( // renewTickInterval — how often we re-evaluate expiring certs. diff --git a/internal/services/setup/setup.go b/internal/services/setup/setup.go index 59cbd96..5890abb 100644 --- a/internal/services/setup/setup.go +++ b/internal/services/setup/setup.go @@ -168,6 +168,10 @@ func (s *Store) Complete(req Request) (*State, error) { // CompleteAsNode configures this node as a secondary cluster member. // No admin credentials are created; login becomes available after PG // replication brings users from the primary. +// +// Idempotent: if setup was already completed (e.g. the node was +// previously configured as standalone), it is converted to cluster-node +// mode. Calling again after a successful join is a no-op. func (s *Store) CompleteAsNode(req NodeRequest) (*State, error) { if !looksLikeFQDN(req.FQDN) { return nil, fmt.Errorf("fqdn %q does not look like a fully-qualified hostname", req.FQDN) @@ -179,16 +183,22 @@ func (s *Store) CompleteAsNode(req NodeRequest) (*State, error) { if err != nil { return nil, err } - if prev.Completed { - return nil, errors.New("setup already completed") - } now := time.Now().UTC() + completedAt := &now + if prev.CompletedAt != nil { + completedAt = prev.CompletedAt + } st := &State{ FQDN: strings.TrimSpace(req.FQDN), ACMEEmail: strings.ToLower(strings.TrimSpace(req.ACMEEmail)), IsClusterNode: true, Completed: true, - CompletedAt: &now, + CompletedAt: completedAt, + // Carry over non-auth fields from previous state. + LicenseKey: prev.LicenseKey, + IPv6Enabled: prev.IPv6Enabled, + BackupRetentionKeep: prev.BackupRetentionKeep, + AuditRetentionDays: prev.AuditRetentionDays, } if err := s.Save(st); err != nil { return nil, err