#!/usr/bin/env bash # Detects and fixes drift between the ed25519 SSH host key nix-cache is # actually serving right now and vars.nixCacheHostKey (variables.nix) -- # the value modules/nix-cache/remote-builder-client.nix bakes into every # client's declarative programs.ssh.knownHosts, and # scripts/proxmox/configure-nix-cache-client.sh hardcodes as its own # default for non-NixOS clients. # # This value has no automatic source of truth: nix-cache's host key is # generated once (first boot / container recreate) and never touches this # repo again unless someone remembers to update it by hand afterwards. It # drifted silently once already -- confirmed live: variables.nix recorded # a key that no longer matched what nix-cache actually presented, which # would fail every real client's SSH host-key verification for # distributed builds without ever producing an obvious error pointing # back here (a client just sees "Host key verification failed" against # *some* key, with no hint that the trusted value itself was stale). # # codex-maintenance.sh runs this in --check mode on every invocation so # that drift surfaces as a warning instead of a future debugging session. # # Usage: # scripts/secrets/sync-nix-cache-host-key.sh [--check] [--dry-run] [--host ] # # --check Only report drift (exit 1 if found, 2 if nix-cache is # unreachable); never writes. For CI/maintenance use. # --dry-run Show what would change; never writes. # --host Override the hostname to scan (default: variables.nix's # nixCacheHost / env.sh's NIX_CACHE_HOST). set -euo pipefail repo_root="$(cd "$(dirname "$0")/../.." && pwd)" variables_nix="${repo_root}/variables.nix" client_script="${repo_root}/scripts/proxmox/configure-nix-cache-client.sh" # shellcheck source=../env.sh source "${repo_root}/scripts/env.sh" check_only=0 dry_run=0 host="${NIX_CACHE_HOST}" while [[ $# -gt 0 ]]; do case "$1" in --check) check_only=1; shift ;; --dry-run) dry_run=1; shift ;; --host) host="${2:?--host requires a hostname}" shift 2 ;; -h|--help) sed -n '2,23p' "$0" exit 0 ;; *) echo "ERROR: unknown argument: $1" >&2 exit 1 ;; esac done current_value="$(grep -oE 'nixCacheHostKey = "[^"]+"' "$variables_nix" | sed -E 's/nixCacheHostKey = "(.*)"/\1/')" if [[ -z "$current_value" ]]; then echo "ERROR: couldn't find nixCacheHostKey in $variables_nix" >&2 exit 1 fi current_type_blob="$(awk '{print $1, $2}' <<<"$current_value")" current_label="$(awk '{print $3}' <<<"$current_value")" echo "Scanning ${host} for its current ed25519 SSH host key..." nix_extra_opts scanned="$(nix-shell "${NIX_OPTS[@]}" -p openssh --run "ssh-keyscan -t ed25519 -T 5 '${host}'" 2>/dev/null | grep -v '^#' | head -1 || true)" if [[ -z "$scanned" ]]; then echo "ERROR: couldn't reach ${host} (or got no ed25519 host key back) via ssh-keyscan." >&2 exit 2 fi scanned_type_blob="$(awk '{print $2, $3}' <<<"$scanned")" if [[ "$current_type_blob" == "$scanned_type_blob" ]]; then echo "Up to date: ${host}'s host key matches variables.nix's nixCacheHostKey." exit 0 fi echo "DRIFT DETECTED:" echo " variables.nix has: $current_type_blob" echo " ${host} is now: $scanned_type_blob" if [[ "$check_only" -eq 1 ]]; then echo echo "Run 'scripts/secrets/sync-nix-cache-host-key.sh' (no flags) to fix." >&2 exit 1 fi new_value="${scanned_type_blob} ${current_label}" if [[ "$dry_run" -eq 1 ]]; then echo "(--dry-run: would update variables.nix and ${client_script##*/} to:)" echo " $new_value" exit 0 fi sed -i "s|nixCacheHostKey = \"[^\"]*\"|nixCacheHostKey = \"${new_value}\"|" "$variables_nix" sed -i "s|NIX_CACHE_HOST_KEY:=[^}]*}|NIX_CACHE_HOST_KEY:=${new_value}}|" "$client_script" echo "Updated variables.nix and ${client_script##*/} to:" echo " $new_value" echo echo "This only takes effect on already-deployed NixOS clients after their" echo "next rebuild (programs.ssh.knownHosts is declarative). Review with" echo "'git diff', then run 'bash scripts/codex-maintenance.sh' before committing."