diff --git a/src/workflows/bootstrap/steps/bootstrap.rs b/src/workflows/bootstrap/steps/bootstrap.rs index 3ab0b014..916b4b18 100644 --- a/src/workflows/bootstrap/steps/bootstrap.rs +++ b/src/workflows/bootstrap/steps/bootstrap.rs @@ -325,8 +325,8 @@ impl StepBody for ConfigureOIDC { } // Create new OIDC auth source - let oidc_id = k::kube_get_secret_field("lasuite", "oidc-gitea", "CLIENT_ID").await; - let oidc_secret = k::kube_get_secret_field("lasuite", "oidc-gitea", "CLIENT_SECRET").await; + let oidc_id = k::kube_get_secret_field("devtools", "oidc-gitea", "CLIENT_ID").await; + let oidc_secret = k::kube_get_secret_field("devtools", "oidc-gitea", "CLIENT_SECRET").await; match (oidc_id, oidc_secret) { (Ok(oidc_id), Ok(oidc_sec)) => { diff --git a/src/workflows/primitives/kv_service_configs.rs b/src/workflows/primitives/kv_service_configs.rs index a0ecbd4b..2a480a0a 100644 --- a/src/workflows/primitives/kv_service_configs.rs +++ b/src/workflows/primitives/kv_service_configs.rs @@ -26,58 +26,14 @@ pub fn all_service_configs() -> Vec { {"key":"admin-username","generator":"gitea_admin"}, {"key":"admin-password","generator":"rand_token"} ]}), - json!({"service":"hive","fields":[ - {"key":"oidc-client-id","generator":"static:hive-local"}, - {"key":"oidc-client-secret","generator":"rand_token"} - ]}), json!({"service":"livekit","fields":[ {"key":"api-key","generator":"static:devkey"}, {"key":"api-secret","generator":"rand_token"} ]}), - json!({"service":"people","fields":[ - {"key":"django-secret-key","generator":"rand_token"} - ]}), json!({"service":"login-ui","fields":[ {"key":"cookie-secret","generator":"rand_token"}, {"key":"csrf-cookie-secret","generator":"rand_token"} ]}), - json!({"service":"docs","fields":[ - {"key":"django-secret-key","generator":"rand_token"}, - {"key":"collaboration-secret","generator":"rand_token"} - ]}), - json!({"service":"meet","fields":[ - {"key":"django-secret-key","generator":"rand_token"}, - {"key":"application-jwt-secret-key","generator":"rand_token"} - ]}), - json!({"service":"drive","fields":[ - {"key":"django-secret-key","generator":"rand_token"} - ]}), - json!({"service":"projects","fields":[ - {"key":"secret-key","generator":"rand_token"} - ]}), - json!({"service":"calendars","fields":[ - {"key":"django-secret-key","generator":"rand_token_50"}, - {"key":"salt-key","generator":"rand_token"}, - {"key":"caldav-inbound-api-key","generator":"rand_token"}, - {"key":"caldav-outbound-api-key","generator":"rand_token"}, - {"key":"caldav-internal-api-key","generator":"rand_token"} - ]}), - json!({"service":"messages","fields":[ - {"key":"django-secret-key","generator":"rand_token"}, - {"key":"salt-key","generator":"rand_token"}, - {"key":"mda-api-secret","generator":"rand_token"}, - {"key":"oidc-refresh-token-key","generator":"fernet_key"}, - {"key":"dkim-private-key","generator":"dkim_private"}, - {"key":"dkim-public-key","generator":"dkim_public"}, - {"key":"rspamd-password","generator":"rand_token"}, - {"key":"socks-proxy-users","generator":"socks_proxy"}, - {"key":"mta-out-smtp-username","generator":"static:sunbeam"}, - {"key":"mta-out-smtp-password","generator":"rand_token"} - ]}), - json!({"service":"collabora","fields":[ - {"key":"username","generator":"static:admin"}, - {"key":"password","generator":"rand_token"} - ]}), json!({"service":"tuwunel","fields":[ {"key":"oidc-client-id","generator":"static:"}, {"key":"oidc-client-secret","generator":"static:"}, @@ -117,10 +73,17 @@ pub fn kratos_admin_config() -> Value { /// All service names (for WriteKVPath branches). pub fn all_service_names() -> Vec<&'static str> { vec![ - "hydra", "kratos", "seaweedfs", "gitea", "hive", "livekit", - "people", "login-ui", "kratos-admin", "docs", "meet", "drive", - "projects", "calendars", "messages", "collabora", "tuwunel", - "grafana", "scaleway-s3", "headscale", + "hydra", + "kratos", + "seaweedfs", + "gitea", + "livekit", + "login-ui", + "kratos-admin", + "tuwunel", + "grafana", + "scaleway-s3", + "headscale", ] } @@ -132,22 +95,28 @@ mod tests { fn all_configs_have_service_and_fields() { for cfg in all_service_configs() { assert!(cfg.get("service").is_some(), "missing service in {cfg}"); - assert!(cfg.get("fields").and_then(|f| f.as_array()).is_some(), "missing fields in {cfg}"); + assert!( + cfg.get("fields").and_then(|f| f.as_array()).is_some(), + "missing fields in {cfg}" + ); } } #[test] fn service_count() { - // 19 independent + 1 kratos-admin (dependent) - assert_eq!(all_service_configs().len(), 19); - assert_eq!(all_service_names().len(), 20); + // 10 independent + 1 kratos-admin (dependent) + assert_eq!(all_service_configs().len(), 10); + assert_eq!(all_service_names().len(), 11); } #[test] fn kratos_admin_has_from_creds() { let cfg = kratos_admin_config(); let fields = cfg["fields"].as_array().unwrap(); - let s3_field = fields.iter().find(|f| f["key"] == "s3-access-key").unwrap(); + let s3_field = fields + .iter() + .find(|f| f["key"] == "s3-access-key") + .unwrap(); assert!(s3_field["generator"].as_str().unwrap().starts_with("from_creds:")); } } diff --git a/src/workflows/seed/definition.rs b/src/workflows/seed/definition.rs index 40d8d249..c667d574 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(), 16, "should have 16 CreatePGRole steps"); - assert_eq!(db_steps.len(), 16, "should have 16 CreatePGDatabase steps"); + assert_eq!(role_steps.len(), 7, "should have 7 CreatePGRole steps"); + assert_eq!(db_steps.len(), 7, "should have 7 CreatePGDatabase steps"); } #[test] diff --git a/src/workflows/seed/steps/postgres.rs b/src/workflows/seed/steps/postgres.rs index 5d6ccf10..e6a6fede 100644 --- a/src/workflows/seed/steps/postgres.rs +++ b/src/workflows/seed/steps/postgres.rs @@ -32,15 +32,16 @@ fn json_str(data: &serde_json::Value, key: &str) -> Option { /// Build the user to database mapping used by EnsurePGRolesAndDatabases. pub(crate) fn pg_db_map() -> HashMap<&'static str, &'static str> { [ - ("kratos", "kratos_db"), ("hydra", "hydra_db"), ("gitea", "gitea_db"), - ("hive", "hive_db"), ("docs", "docs_db"), ("meet", "meet_db"), - ("drive", "drive_db"), ("messages", "messages_db"), - ("conversations", "conversations_db"), ("people", "people_db"), - ("find", "find_db"), ("calendars", "calendars_db"), ("projects", "projects_db"), + ("kratos", "kratos_db"), + ("hydra", "hydra_db"), + ("gitea", "gitea_db"), ("penpot", "penpot_db"), ("stalwart", "stalwart_db"), ("headscale", "headscale_db"), - ].into_iter().collect() + ("wfe", "wfe_db"), + ] + .into_iter() + .collect() } /// SQL to idempotently create a postgres user if it does not exist. @@ -219,7 +220,7 @@ mod tests { #[test] fn test_pg_db_map_contains_all_users() { let map = pg_db_map(); - assert_eq!(map.len(), 16); + assert_eq!(map.len(), 7); for user in crate::secrets::PG_USERS { assert!(map.contains_key(user), "pg_db_map missing key for: {user}"); } diff --git a/src/workflows/up/definition.rs b/src/workflows/up/definition.rs index 08bfa9e3..037af569 100644 --- a/src/workflows/up/definition.rs +++ b/src/workflows/up/definition.rs @@ -102,7 +102,7 @@ pub fn build() -> WorkflowDefinition { .name("write-vso-role") .config(json!({"mount": "kubernetes", "role": "vso", "config": { "bound_service_account_names": "default", - "bound_service_account_namespaces": "ory,devtools,storage,lasuite,stalwart,matrix,media,data,monitoring,cert-manager", + "bound_service_account_namespaces": "ory,devtools,storage,stalwart,matrix,media,data,monitoring,cert-manager,vpn,wfe", "policies": "vso-reader", "ttl": "1h" }})) @@ -159,6 +159,17 @@ pub fn build() -> WorkflowDefinition { b.add_step_typed::("apply-media", Some(json!({"namespace": "media"}))); }) + .branch(|b| { + b.add_step_typed::("apply-stalwart", + Some(json!({"namespace": "stalwart"}))); + }) + .branch(|b| { + // VPN bootstrap path — headscale needs cert-manager + the + // headscale_db postgres role from Phase 4. Both are done by + // the time this branch fires. + b.add_step_typed::("apply-vpn", + Some(json!({"namespace": "vpn"}))); + }) ) // ── Phase 6: K8s secrets (parallel by namespace) ────────────── @@ -211,27 +222,6 @@ pub fn build() -> WorkflowDefinition { b.wire_outcome(ns, s1, None); b.wire_outcome(s1, s2, None); }) - .branch(|b| { - let ns = b.add_step_typed::("ensure-ns-lasuite", - Some(json!({"namespace": "lasuite"}))); - let s1 = b.add_step_typed::("secret-lasuite-s3", - Some(json!({"namespace":"lasuite","name":"seaweedfs-s3-credentials","data":{ - "S3_ACCESS_KEY":"s3-access-key", - "S3_SECRET_KEY":"s3-secret-key" - }}))); - let s2 = b.add_step_typed::("secret-hive-oidc", - Some(json!({"namespace":"lasuite","name":"hive-oidc","data":{ - "client-id":"hive-oidc-client-id", - "client-secret":"hive-oidc-client-secret" - }}))); - let s3 = b.add_step_typed::("secret-people-django", - Some(json!({"namespace":"lasuite","name":"people-django-secret","data":{ - "DJANGO_SECRET_KEY":"people-django-secret" - }}))); - b.wire_outcome(ns, s1, None); - b.wire_outcome(s1, s2, None); - b.wire_outcome(s2, s3, None); - }) .branch(|b| { b.add_step_typed::("ensure-ns-matrix", Some(json!({"namespace": "matrix"}))); @@ -253,14 +243,16 @@ pub fn build() -> WorkflowDefinition { // ── Phase 7: Application manifests ──────────────────────────── .parallel(|p| p - .branch(|b| { - b.add_step_typed::("apply-lasuite", - Some(json!({"namespace": "lasuite"}))); - }) .branch(|b| { b.add_step_typed::("apply-matrix", Some(json!({"namespace": "matrix"}))); }) + .branch(|b| { + // wfe-server pulls its image from src.DOMAIN_SUFFIX (built + // locally), so it has to come *after* gitea bootstrap. + b.add_step_typed::("apply-wfe", + Some(json!({"namespace": "wfe"}))); + }) ) // ── Phase 8: Core rollouts + OpenSearch ML (parallel) ─────────