#!/usr/bin/env nu # Prepare: decrypt SOPS garage secret and deploy as k8s secret before install. # Runs on the provisioning machine. SSH to server to create the secret. # Mirrors hetzner_csi/default/prepare — RPC secret and admin token are # provisioned out-of-band (SOPS-managed), never generated by this script. 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 secret_name = ($wk_data | get -o taskserv.secret_name | default "garage-rpc-admin") let namespace = ($wk_data | get -o taskserv.namespace | default "garage") let hostname = ($wk_data | get -o server.hostname | default "") # Public IP first; fall back to the private IP for VPN-only control planes # (e.g. libre-wuji-cp-0 has no public_ip — reachable only over the WireGuard # subnet from the provisioning machine). 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 "") } } # 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 the secret deploy (root cause of manual secret creation). 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 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" 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 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)" exit 0 } let creds = ($decrypted | from yaml) let rpc_secret = ($creds | get -o GARAGE_RPC_SECRET | default "") let admin_token = ($creds | get -o GARAGE_ADMIN_TOKEN | default "") if ($rpc_secret | is-empty) or ($admin_token | is-empty) { print "⚠ prepare: GARAGE_RPC_SECRET / GARAGE_ADMIN_TOKEN not found in decrypted secret" exit 0 } 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 } # 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=rpc_secret=($rpc_secret) --from-literal=admin_token=($admin_token) --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 — ($res.stderr)" }