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 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 17:14:25 +10:00
co-authored by Claude Sonnet 4.6
parent 784e064fa2
commit 78bb784265
2 changed files with 38 additions and 2 deletions
@@ -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}"
+3 -2
View File
@@ -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