diff --git a/internal/config/client_helpers_test.go b/internal/config/client_helpers_test.go index e414a05..18b6a28 100644 --- a/internal/config/client_helpers_test.go +++ b/internal/config/client_helpers_test.go @@ -77,3 +77,45 @@ func TestCreateConfigHelpersNormalizeHosts(t *testing.T) { t.Fatalf("expected default port on host built from fields, got %q", fromFields.Host) } } + +func TestStripDefaultPort(t *testing.T) { + tests := []struct { + name string + host string + defaultPort string + want string + }{ + { + name: "removes default PVE port", + host: "https://pve.local:8006", + defaultPort: defaultPVEPort, + want: "https://pve.local", + }, + { + name: "keeps non-default port", + host: "https://pve.local:8443", + defaultPort: defaultPVEPort, + want: "https://pve.local:8443", + }, + { + name: "preserves http scheme when removing port", + host: "http://pve.local:8006", + defaultPort: defaultPVEPort, + want: "http://pve.local", + }, + { + name: "returns trimmed host unchanged when parse fails", + host: "://bad", + defaultPort: defaultPVEPort, + want: "://bad", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := StripDefaultPort(tt.host, tt.defaultPort); got != tt.want { + t.Fatalf("StripDefaultPort(%q, %q) = %q, want %q", tt.host, tt.defaultPort, got, tt.want) + } + }) + } +} diff --git a/internal/monitoring/monitor.go b/internal/monitoring/monitor.go index c0cd442..cd00dfd 100644 --- a/internal/monitoring/monitor.go +++ b/internal/monitoring/monitor.go @@ -5526,12 +5526,40 @@ func (m *Monitor) retryPVEPortFallback(ctx context.Context, instanceName string, // Switch to the working host for the remainder of the poll (and future polls) primaryHost := instanceCfg.Host - instanceCfg.Host = fallbackHost + + // Persist with an explicit port to avoid re-normalization back to :8006 on reloads. + persistHost := fallbackHost + if parsed, err := url.Parse(fallbackHost); err == nil && parsed.Host != "" && parsed.Port() == "" { + port := "443" + if strings.EqualFold(parsed.Scheme, "http") { + port = "80" + } + parsed.Host = net.JoinHostPort(parsed.Hostname(), port) + persistHost = parsed.Scheme + "://" + parsed.Host + } + + instanceCfg.Host = persistHost m.pveClients[instanceName] = fallbackClient + + // Update in-memory config so subsequent polls build clients against the working port. + for i := range m.config.PVEInstances { + if m.config.PVEInstances[i].Name == instanceName { + m.config.PVEInstances[i].Host = persistHost + break + } + } + + // Persist to disk so restarts keep the working endpoint. + if m.persistence != nil { + if err := m.persistence.SaveNodesConfig(m.config.PVEInstances, m.config.PBSInstances, m.config.PMGInstances); err != nil { + log.Warn().Err(err).Str("instance", instanceName).Msg("Failed to persist fallback PVE host") + } + } + log.Warn(). Str("instance", instanceName). Str("primary", primaryHost). - Str("fallback", fallbackHost). + Str("fallback", persistHost). Msg("Primary PVE host failed; using fallback without default port") return fallbackNodes, fallbackClient, nil