From 78bb78426550b071887b83f414876b4c6166e16f Mon Sep 17 00:00:00 2001 From: beatzaplenty Date: Sat, 25 Jul 2026 17:14:25 +1000 Subject: [PATCH] fix(provision): block build until sops changes are committed, guard missing host keys Three ordering-related fixes to the Proxmox provisioning flow: 1. prepare-host-key.sh: make idempotent -- if the key already exists, print a note and exit 0 instead of erroring. The caller (create-proxmox-resource.sh) already guards standalone calls, but the script itself should be safe to run directly on a host that was already keyed. 2. create-proxmox-resource.sh: after sync-host-keys.sh updates .sops.yaml / secrets/, detect uncommitted changes and block with a prompt until the operator confirms they've committed and pushed. The PVE node's git pull only picks up committed+pushed state; without this gate, a new host's sops recipient is missing from the secrets files the image build uses, so the host can't decrypt secrets on first boot. 3. create-proxmox-resource.sh: add an explicit existence check for the host key in both the LXC and VM remote build heredocs, before it's passed as --pre-format-files / NIXOS_HOST_KEYS_DIR input. Gives a clear error pointing at sync-host-keys.sh instead of a raw `cp: cannot stat` from disko deep in the build. Co-Authored-By: Claude Sonnet 4.6 --- scripts/proxmox/create-proxmox-resource.sh | 35 ++++++++++++++++++++++ scripts/secrets/prepare-host-key.sh | 5 ++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/scripts/proxmox/create-proxmox-resource.sh b/scripts/proxmox/create-proxmox-resource.sh index 84903f4..35d6aa1 100755 --- a/scripts/proxmox/create-proxmox-resource.sh +++ b/scripts/proxmox/create-proxmox-resource.sh @@ -502,6 +502,29 @@ sync_args=("$flake_target") [[ "$dry_run" -eq 1 ]] && sync_args+=(--dry-run) bash "$sync_keys" "${sync_args[@]}" +# If sync-host-keys.sh changed .sops.yaml or secrets/, those changes must be +# committed and pushed before the remote `git pull` below picks them up -- +# the PVE node builds from whatever HEAD is checked out there, not the local +# working tree. Detect uncommitted changes and block until the operator +# confirms they've pushed, so the build never runs against a stale flake. +if [[ "$dry_run" -eq 0 ]]; then + _sops_dirty="$(git -C "$repo_root" status --porcelain -- .sops.yaml secrets/ 2>/dev/null || true)" + if [[ -n "$_sops_dirty" ]]; then + echo + echo "==> COMMIT + PUSH REQUIRED before the remote build can succeed:" + echo " sync-host-keys.sh modified .sops.yaml / secrets/ to register the" + echo " new host's sops recipient. The PVE node builds from the git-tracked" + echo " flake, so these changes must be committed and pushed first -- otherwise" + echo " the image build will succeed but the host cannot decrypt secrets on" + echo " first boot (its age key isn't in the encrypted secrets files yet)." + echo + git -C "$repo_root" status --short -- .sops.yaml secrets/ || true + echo + read -rp " Commit and push those changes, then press Enter to continue (Ctrl-C to abort): " + fi + unset _sops_dirty +fi + # --- VMID: pick one, and refuse to touch anything that already exists --- echo if [[ -z "$vmid" ]]; then @@ -707,6 +730,12 @@ cd "$repo_dir" # right after a successful install. . scripts/lib/nix-bootstrap.sh ensure_nix_profile +if [[ ! -f "host-keys/${target}_ssh_host_ed25519_key" ]]; then + echo "ERROR: host-keys/${target}_ssh_host_ed25519_key not found in ${repo_dir}." >&2 + echo "Generate the key locally (scripts/secrets/sync-host-keys.sh ${target})" >&2 + echo "and ensure it was synced here before starting the build." >&2 + exit 1 +fi NIXOS_HOST_KEYS_DIR="$(pwd)/host-keys" nix build --impure \ --no-use-registries --no-accept-flake-config "${NIX_OPTS[@]}" \ ".#nixosConfigurations.${target}.config.system.build.tarball" \ @@ -747,6 +776,12 @@ declare -a NIX_OPTS=() cd "$repo_dir" . scripts/lib/nix-bootstrap.sh ensure_nix_profile +if [[ ! -f "host-keys/${target}_ssh_host_ed25519_key" ]]; then + echo "ERROR: host-keys/${target}_ssh_host_ed25519_key not found in ${repo_dir}." >&2 + echo "Generate the key locally (scripts/secrets/sync-host-keys.sh ${target})" >&2 + echo "and ensure it was synced here before starting the build." >&2 + exit 1 +fi nix build --no-use-registries --no-accept-flake-config "${NIX_OPTS[@]}" \ ".#nixosConfigurations.${target}.config.system.build.diskoImagesScript" \ --out-link "result-${target}" diff --git a/scripts/secrets/prepare-host-key.sh b/scripts/secrets/prepare-host-key.sh index a148cf0..a72e192 100755 --- a/scripts/secrets/prepare-host-key.sh +++ b/scripts/secrets/prepare-host-key.sh @@ -41,8 +41,9 @@ mkdir -p "$keydir" keyfile="${keydir}/${hostname}_ssh_host_ed25519_key" if [[ -f "$keyfile" ]]; then - echo "ERROR: $keyfile already exists. Remove it first if you want to regenerate." >&2 - exit 1 + echo "Key already exists: ${keyfile}" + echo "Reusing the existing key. Remove it first if you want to regenerate." + exit 0 fi nix_extra_opts