From 5f97d063cb2c9f82ba1b931ddc504c5bcabca362 Mon Sep 17 00:00:00 2001 From: Sienna Meridian Satterwhite Date: Tue, 7 Apr 2026 17:35:17 +0100 Subject: [PATCH] feat(seed): provision postgres role + KV slot for headscale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `headscale` to the lists that drive the seed workflow so the existing CNPG role/database creation and OpenBao KV path provisioning pick up the new VPN coordination service alongside everything else: - src/secrets.rs: PG_USERS list grows from 15 → 16 (test asserts the full ordered list, so it's updated to match) - src/workflows/seed/steps/postgres.rs: pg_db_map adds headscale → headscale_db - src/workflows/seed/definition.rs: bumps the role/db step count assertions from 15 → 16 - src/workflows/primitives/kv_service_configs.rs: new headscale entry with a single `api-key` field generated as `static:` (placeholder). The user runs `kubectl exec -n vpn deploy/headscale -- headscale apikeys create` and pastes the result into vault before calling `sunbeam vpn create-key`. Bumps service_count test from 18 → 19. - src/constants.rs: add `vpn` to MANAGED_NS so the legacy namespace list includes the new namespace. --- src/constants.rs | 2 ++ src/secrets.rs | 4 +++- src/workflows/primitives/kv_service_configs.rs | 16 ++++++++++++---- src/workflows/seed/definition.rs | 4 ++-- src/workflows/seed/steps/postgres.rs | 3 ++- 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 5ab992e0..bef676b9 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -2,6 +2,7 @@ pub const GITEA_ADMIN_USER: &str = "gitea_admin"; +/// Deprecated: prefer `registry::discover()` → `ServiceRegistry::namespaces()`. pub const MANAGED_NS: &[&str] = &[ "data", "devtools", @@ -13,4 +14,5 @@ pub const MANAGED_NS: &[&str] = &[ "ory", "storage", "vault-secrets-operator", + "vpn", ]; diff --git a/src/secrets.rs b/src/secrets.rs index 704292eb..1e61d1a2 100644 --- a/src/secrets.rs +++ b/src/secrets.rs @@ -37,6 +37,7 @@ pub(crate) const PG_USERS: &[&str] = &[ "projects", "penpot", "stalwart", + "headscale", ]; pub(crate) const SMTP_URI: &str = "smtp://postfix.lasuite.svc.cluster.local:25/?skip_ssl_verify=true"; @@ -468,7 +469,7 @@ mod tests { fn test_constants() { assert_eq!(ADMIN_USERNAME, "estudio-admin"); assert_eq!(GITEA_ADMIN_USER, "gitea_admin"); - assert_eq!(PG_USERS.len(), 15); + assert_eq!(PG_USERS.len(), 16); assert!(PG_USERS.contains(&"kratos")); assert!(PG_USERS.contains(&"projects")); } @@ -518,6 +519,7 @@ mod tests { "projects", "penpot", "stalwart", + "headscale", ]; assert_eq!(PG_USERS, &expected[..]); } diff --git a/src/workflows/primitives/kv_service_configs.rs b/src/workflows/primitives/kv_service_configs.rs index db43139c..a0ecbd4b 100644 --- a/src/workflows/primitives/kv_service_configs.rs +++ b/src/workflows/primitives/kv_service_configs.rs @@ -91,6 +91,14 @@ pub fn all_service_configs() -> Vec { {"key":"access-key-id","generator":"scw_config_access"}, {"key":"secret-access-key","generator":"scw_config_secret"} ]}), + // Headscale API key — generated *out of band* via + // `kubectl exec -n vpn deploy/headscale -- headscale apikeys create` + // and pasted into vault. Seed leaves a placeholder slot so the + // path exists; replace with the real key before running + // `sunbeam vpn create-key`. + json!({"service":"headscale","fields":[ + {"key":"api-key","generator":"static:"} + ]}), ] } @@ -112,7 +120,7 @@ pub fn all_service_names() -> Vec<&'static str> { "hydra", "kratos", "seaweedfs", "gitea", "hive", "livekit", "people", "login-ui", "kratos-admin", "docs", "meet", "drive", "projects", "calendars", "messages", "collabora", "tuwunel", - "grafana", "scaleway-s3", + "grafana", "scaleway-s3", "headscale", ] } @@ -130,9 +138,9 @@ mod tests { #[test] fn service_count() { - // 18 independent + 1 kratos-admin (dependent) - assert_eq!(all_service_configs().len(), 18); - assert_eq!(all_service_names().len(), 19); + // 19 independent + 1 kratos-admin (dependent) + assert_eq!(all_service_configs().len(), 19); + assert_eq!(all_service_names().len(), 20); } #[test] diff --git a/src/workflows/seed/definition.rs b/src/workflows/seed/definition.rs index 0f4ac2dc..40d8d249 100644 --- a/src/workflows/seed/definition.rs +++ b/src/workflows/seed/definition.rs @@ -206,8 +206,8 @@ mod tests { let db_steps: Vec<_> = def.steps.iter() .filter(|s| s.step_type.contains("CreatePGDatabase")) .collect(); - assert_eq!(role_steps.len(), 15, "should have 15 CreatePGRole steps"); - assert_eq!(db_steps.len(), 15, "should have 15 CreatePGDatabase steps"); + assert_eq!(role_steps.len(), 16, "should have 16 CreatePGRole steps"); + assert_eq!(db_steps.len(), 16, "should have 16 CreatePGDatabase steps"); } #[test] diff --git a/src/workflows/seed/steps/postgres.rs b/src/workflows/seed/steps/postgres.rs index d080d1a7..5d6ccf10 100644 --- a/src/workflows/seed/steps/postgres.rs +++ b/src/workflows/seed/steps/postgres.rs @@ -39,6 +39,7 @@ pub(crate) fn pg_db_map() -> HashMap<&'static str, &'static str> { ("find", "find_db"), ("calendars", "calendars_db"), ("projects", "projects_db"), ("penpot", "penpot_db"), ("stalwart", "stalwart_db"), + ("headscale", "headscale_db"), ].into_iter().collect() } @@ -218,7 +219,7 @@ mod tests { #[test] fn test_pg_db_map_contains_all_users() { let map = pg_db_map(); - assert_eq!(map.len(), 15); + assert_eq!(map.len(), 16); for user in crate::secrets::PG_USERS { assert!(map.contains_key(user), "pg_db_map missing key for: {user}"); }