#!/usr/bin/env nu # Prepare: decrypt SOPS SMB credentials and deploy one k8s Secret per declared # StorageClass before install. Each storage_classes[].secret_name must have a # matching .sops.yaml with 'username' and 'password' keys — # provisioned out-of-band, never generated by this script. # Runs on the provisioning machine. SSH to server to create the secrets. let vars_path = ($env.PROVISIONING_VARS? | default "") if ($vars_path | is-empty) or not ($vars_path | path exists) { exit 0 } let wk_data = (open $vars_path) let storage_classes = ($wk_data | get -o taskserv.storage_classes | default []) let hostname = ($wk_data | get -o server.hostname | default "") # Public IP first; fall back to the private IP for VPN-only control planes # (libre-wuji-cp-0 has no public_ip — reachable only over the WireGuard subnet). let server_ip = do { let p = ($wk_data | get -o server.ip_addresses.pub | default "") if ($p | is-not-empty) { $p } else { ($wk_data | get -o server.ip_addresses.priv | default "") } } if ($storage_classes | is-empty) { print "⚠ prepare: no storage_classes declared — nothing to deploy" exit 0 } # The infra path (secrets live at /secrets/). The runner passes this # RELATIVE ("infra/libre-wuji") resolved from CWD=workspace root; `path expand` # makes it absolute. Do NOT derive it from PROVISIONING_WORKSPACES via # `path dirname` — that env is relative ("infra") and dirname collapses to "", # which silently skipped every secret deploy this component ever ran. let infra_path = do { let src = ($env.PROVISIONING_SETTINGS_SRC_PATH? | default "") if ($src | is-not-empty) { $src | path expand } else { let kloud = ($env.PROVISIONING_KLOUD_PATH? | default "") if ($kloud | is-not-empty) { $kloud | path expand } else { "" } } } if ($infra_path | is-empty) or ($server_ip | is-empty) { print $"⚠ prepare: cannot resolve infra path or server IP — skipping secret deploy" exit 0 } let kage_path = if ($env.SOPS_AGE_KEY_FILE? | default "" | is-not-empty) { $env.SOPS_AGE_KEY_FILE } else { let kloud_path = ($env.PROVISIONING_KLOUD_PATH? | default "") if ($kloud_path | is-not-empty) and ($kloud_path | path join ".kage" | path exists) { $kloud_path | path join ".kage" } else { "" } } let settings_src = ($env.PROVISIONING_SETTINGS_SRC_PATH? | default "") let ssh_key = if ($settings_src | is-not-empty) { let key = ($env.PROVISIONING_SSH_KEY? | default "~/.ssh/htz_ops") $key | str replace "~" $env.HOME } else { "" } let ssh_opts = "-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10" let ssh_opts = if ($ssh_key | is-not-empty) and ($ssh_key | path exists) { $"($ssh_opts) -i ($ssh_key)" } else { $ssh_opts } for sc in $storage_classes { let secret_name = ($sc | get -o secret_name | default "") let namespace = ($sc | get -o secret_namespace | default "kube-system") if ($secret_name | is-empty) { continue } let sops_file = ($infra_path | path join "secrets" | path join $"($secret_name).sops.yaml") if not ($sops_file | path exists) { print $"⚠ prepare: ($sops_file) not found — secret must exist on cluster" continue } let decrypted = if ($kage_path | is-not-empty) { (do -i { with-env { SOPS_AGE_KEY_FILE: $kage_path } { ^sops -d $sops_file } } | default "") } else { (do -i { ^sops -d $sops_file } | default "") } if ($decrypted | is-empty) { print $"⚠ prepare: failed to decrypt ($sops_file)" continue } let creds = ($decrypted | from yaml) let username = ($creds | get -o username | default "") let password = ($creds | get -o password | default "") if ($username | is-empty) or ($password | is-empty) { print $"⚠ prepare: username/password not found in ($sops_file)" continue } # Detect the kubectl entrypoint on the target (libre-wuji is kubeadm → plain # kubectl; k0s clusters expose `k0s kubectl`), instead of assuming k0s. let kube = do { let probe = (do -i { ^ssh ...($ssh_opts | split row " ") $"root@($server_ip)" "command -v k0s >/dev/null 2>&1 && echo k0s || echo kubectl" } | complete) if (($probe.stdout | str trim) == "k0s") { "k0s kubectl" } else { "kubectl" } } let cmd = $"($kube) create namespace ($namespace) --dry-run=client -o yaml | ($kube) apply -f - && ($kube) -n ($namespace) create secret generic ($secret_name) --from-literal=username=($username) --from-literal=password=($password) --dry-run=client -o yaml | ($kube) apply -f -" let res = (do -i { ^ssh ...($ssh_opts | split row " ") $"root@($server_ip)" $cmd } | complete) if $res.exit_code == 0 { print $"✓ prepare: ($secret_name) secret deployed to ($namespace) on ($hostname)" } else { print $"⚠ prepare: failed to deploy ($secret_name) — ($res.stderr)" } }