package config import "strings" // applyDefaults runs one orchestration or CLI step. // Signature: (c *Config) applyDefaults(). // Why: keeps behavior explicit so startup/shutdown workflows remain maintainable as services evolve. func (c *Config) applyDefaults() { if c.ExpectedFluxBranch == "" { c.ExpectedFluxBranch = "main" } if c.IACRepoPath == "" { c.IACRepoPath = "/opt/titan-iac" } if c.ExpectedFluxSource == "" { c.ExpectedFluxSource = "ssh://git@scm.bstein.dev:2242/bstein/titan-iac.git" } if c.Startup.APIWaitSeconds <= 0 { c.Startup.APIWaitSeconds = 1200 } if c.Startup.APIPollSeconds <= 0 { c.Startup.APIPollSeconds = 2 } if c.Startup.ShutdownCooldownSeconds <= 0 { c.Startup.ShutdownCooldownSeconds = 45 } if c.Startup.MinimumBatteryPercent <= 0 { c.Startup.MinimumBatteryPercent = 20 } if c.Startup.NodeInventoryReachWaitSeconds <= 0 { c.Startup.NodeInventoryReachWaitSeconds = 300 } if c.Startup.NodeInventoryReachPollSeconds <= 0 { c.Startup.NodeInventoryReachPollSeconds = 5 } if c.Startup.RequiredNodeLabels == nil { c.Startup.RequiredNodeLabels = map[string]map[string]string{ "titan-09": { "ananke.bstein.dev/harbor-bootstrap": "true", }, } } if c.Startup.TimeSyncWaitSeconds <= 0 { c.Startup.TimeSyncWaitSeconds = 240 } if c.Startup.TimeSyncPollSeconds <= 0 { c.Startup.TimeSyncPollSeconds = 5 } if c.Startup.TimeSyncMode == "" { c.Startup.TimeSyncMode = "quorum" } if c.Startup.TimeSyncQuorum <= 0 { c.Startup.TimeSyncQuorum = 2 } if c.Startup.TimeSyncQuorum > len(c.ControlPlanes) && len(c.ControlPlanes) > 0 { c.Startup.TimeSyncQuorum = len(c.ControlPlanes) } if c.Startup.EtcdRestoreControlPlane == "" && len(c.ControlPlanes) > 0 { c.Startup.EtcdRestoreControlPlane = c.ControlPlanes[0] } if c.Startup.StorageReadyWaitSeconds <= 0 { c.Startup.StorageReadyWaitSeconds = 420 } if c.Startup.StorageReadyPollSeconds <= 0 { c.Startup.StorageReadyPollSeconds = 5 } if c.Startup.StorageMinReadyNodes <= 0 { c.Startup.StorageMinReadyNodes = 2 } if len(c.Startup.StorageCriticalPVCs) == 0 { c.Startup.StorageCriticalPVCs = []string{ "vault/data-vault-0", "postgres/postgres-data-postgres-0", "gitea/gitea-data", "sso/keycloak-data", } } if c.Startup.PostStartProbeWaitSeconds <= 0 { c.Startup.PostStartProbeWaitSeconds = 240 } if c.Startup.PostStartProbePollSeconds <= 0 { c.Startup.PostStartProbePollSeconds = 5 } if len(c.Startup.PostStartProbes) == 0 { c.Startup.PostStartProbes = []string{ "https://sso.bstein.dev/realms/atlas/.well-known/openid-configuration", "https://scm.bstein.dev/api/healthz", "https://metrics.bstein.dev/api/health", } } if c.Startup.ServiceChecklistWaitSeconds <= 0 { c.Startup.ServiceChecklistWaitSeconds = 420 } if c.Startup.ServiceChecklistPollSeconds <= 0 { c.Startup.ServiceChecklistPollSeconds = 5 } if c.Startup.ServiceChecklistStabilitySec < 0 { c.Startup.ServiceChecklistStabilitySec = 0 } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.Mode) == "" { c.Startup.ServiceChecklistAuth.Mode = "keycloak_robotuser" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.KeycloakBaseURL) == "" { c.Startup.ServiceChecklistAuth.KeycloakBaseURL = "https://sso.bstein.dev" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.Realm) == "" { c.Startup.ServiceChecklistAuth.Realm = "atlas" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.RobotUsername) == "" { c.Startup.ServiceChecklistAuth.RobotUsername = "robotuser" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.AdminSecretNamespace) == "" { c.Startup.ServiceChecklistAuth.AdminSecretNamespace = "sso" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.AdminSecretName) == "" { c.Startup.ServiceChecklistAuth.AdminSecretName = "keycloak-admin" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.AdminSecretUsernameKey) == "" { c.Startup.ServiceChecklistAuth.AdminSecretUsernameKey = "username" } if strings.TrimSpace(c.Startup.ServiceChecklistAuth.AdminSecretPasswordKey) == "" { c.Startup.ServiceChecklistAuth.AdminSecretPasswordKey = "password" } c.Startup.ServiceChecklist = mergeServiceChecklistDefaults(c.Startup.ServiceChecklist, defaultServiceChecklist()) for i := range c.Startup.ServiceChecklist { if c.Startup.ServiceChecklist[i].TimeoutSeconds <= 0 { c.Startup.ServiceChecklist[i].TimeoutSeconds = 12 } } if c.Startup.CriticalServiceEndpointWaitSec <= 0 { c.Startup.CriticalServiceEndpointWaitSec = 420 } if c.Startup.CriticalServiceEndpointPollSec <= 0 { c.Startup.CriticalServiceEndpointPollSec = 5 } c.Startup.CriticalServiceEndpoints = mergeStringDefaults(c.Startup.CriticalServiceEndpoints, defaultCriticalServiceEndpoints()) if c.Startup.IngressChecklistWaitSeconds <= 0 { c.Startup.IngressChecklistWaitSeconds = 420 } if c.Startup.IngressChecklistPollSeconds <= 0 { c.Startup.IngressChecklistPollSeconds = 5 } if len(c.Startup.IngressChecklistAccepted) == 0 { c.Startup.IngressChecklistAccepted = []int{200, 301, 302, 307, 308, 401, 403, 404} } if c.Startup.IngressChecklistIgnoreHosts == nil { c.Startup.IngressChecklistIgnoreHosts = []string{} } if c.Startup.NodeSSHAuthWaitSeconds <= 0 { c.Startup.NodeSSHAuthWaitSeconds = 240 } if c.Startup.NodeSSHAuthPollSeconds <= 0 { c.Startup.NodeSSHAuthPollSeconds = 5 } if c.Startup.FluxHealthWaitSeconds <= 0 { c.Startup.FluxHealthWaitSeconds = 900 } if c.Startup.FluxHealthPollSeconds <= 0 { c.Startup.FluxHealthPollSeconds = 5 } if c.Startup.IgnoreFluxKustomizations == nil { c.Startup.IgnoreFluxKustomizations = []string{} } if c.Startup.WorkloadConvergenceWaitSeconds <= 0 { c.Startup.WorkloadConvergenceWaitSeconds = 900 } if c.Startup.WorkloadConvergencePollSeconds <= 0 { c.Startup.WorkloadConvergencePollSeconds = 5 } if c.Startup.IgnoreWorkloadNamespaces == nil { c.Startup.IgnoreWorkloadNamespaces = []string{} } if c.Startup.IgnoreWorkloads == nil { c.Startup.IgnoreWorkloads = []string{} } if c.Startup.IgnoreUnavailableNodes == nil { c.Startup.IgnoreUnavailableNodes = []string{} } if c.Startup.StuckPodGraceSeconds <= 0 { c.Startup.StuckPodGraceSeconds = 180 } if strings.TrimSpace(c.Startup.VaultUnsealKeyFile) == "" { c.Startup.VaultUnsealKeyFile = "/var/lib/ananke/vault-unseal.key" } if c.Startup.VaultUnsealBreakglassTimeout <= 0 { c.Startup.VaultUnsealBreakglassTimeout = 15 } if c.SSHPort <= 0 { c.SSHPort = 2277 } if c.Shutdown.DefaultBudgetSeconds <= 0 { c.Shutdown.DefaultBudgetSeconds = 1380 } if c.Shutdown.HistoryMinSamples <= 0 { c.Shutdown.HistoryMinSamples = 3 } if c.Shutdown.EmergencyBudgetSec <= 0 { c.Shutdown.EmergencyBudgetSec = 420 } if c.Shutdown.EmergencyMinSamples <= 0 { c.Shutdown.EmergencyMinSamples = 3 } if c.Shutdown.DrainParallelism <= 0 { c.Shutdown.DrainParallelism = 6 } if c.Shutdown.ScaleParallelism <= 0 { c.Shutdown.ScaleParallelism = 8 } if c.Shutdown.SSHParallelism <= 0 { c.Shutdown.SSHParallelism = 8 } if c.UPS.PollSeconds <= 0 { c.UPS.PollSeconds = 5 } if c.UPS.RuntimeSafetyFactor <= 0 { c.UPS.RuntimeSafetyFactor = 1.25 } if c.UPS.DebounceCount <= 0 { c.UPS.DebounceCount = 3 } if c.UPS.TelemetryTimeoutSeconds <= 0 { c.UPS.TelemetryTimeoutSeconds = 90 } if c.Coordination.ForwardShutdownConfig == "" { c.Coordination.ForwardShutdownConfig = "/etc/ananke/ananke.yaml" } if c.Coordination.PeerHosts == nil { c.Coordination.PeerHosts = []string{} } if c.Coordination.CommandTimeoutSeconds <= 0 { c.Coordination.CommandTimeoutSeconds = 25 } if c.Coordination.StartupGuardMaxAgeSec <= 0 { c.Coordination.StartupGuardMaxAgeSec = 900 } if c.Coordination.Role == "" { c.Coordination.Role = "coordinator" } if c.Metrics.BindAddr == "" { c.Metrics.BindAddr = "0.0.0.0:9560" } if c.Metrics.Path == "" { c.Metrics.Path = "/metrics" } if c.State.Dir == "" { c.State.Dir = "/var/lib/ananke" } if c.State.ReportsDir == "" { c.State.ReportsDir = "/var/lib/ananke/reports" } if c.State.RunHistoryPath == "" { c.State.RunHistoryPath = "/var/lib/ananke/runs.json" } if c.State.LockPath == "" { c.State.LockPath = "/var/lib/ananke/ananke.lock" } if c.State.IntentPath == "" { c.State.IntentPath = "/var/lib/ananke/intent.json" } }