fix(push-host-keys): prompt sudo password once, pass via sudo -S
Check NixOS configurations / eval-hosts (push) Successful in 10m22s

Instead of ssh -t (requires PTY on both sides), prompt for the sudo
password once at startup and pipe it to each remote invocation via
sudo -S. This works from any context -- interactive terminal, background
agent, or script -- with no PTY needed on either end.

Also accepts SUDO_PASS from the environment for non-interactive callers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 15:49:27 +10:00
co-authored by Claude Sonnet 4.6
parent 0c29c6a93d
commit e021b49412
+29 -12
View File
@@ -19,8 +19,9 @@
# already has the current .sops.yaml/secrets/*.yaml. # already has the current .sops.yaml/secrets/*.yaml.
# #
# SSH: connects as SSH_USER@<hostname> (default: nixos, the user with the # SSH: connects as SSH_USER@<hostname> (default: nixos, the user with the
# admin authorized key), then installs files via sudo. You will be prompted # admin authorized key), then installs files via sudo -S (reads the sudo
# for the sudo password once per host. # password from stdin). The password is prompted once at startup and reused
# for every host -- no PTY or terminal required on the remote side.
# Hosts are reached at their bare hostname (relies on LAN DNS/mDNS). # Hosts are reached at their bare hostname (relies on LAN DNS/mDNS).
set -euo pipefail set -euo pipefail
@@ -37,6 +38,7 @@ SSH_OPTS=(-o StrictHostKeyChecking=no -o BatchMode=yes -o ConnectTimeout=5)
dry_run=0 dry_run=0
skip_git_check=0 skip_git_check=0
sudo_password=""
usage() { usage() {
cat <<EOF cat <<EOF
@@ -51,9 +53,23 @@ Usage: $0 [--all | <target>] [--dry-run] [--skip-git-check]
Environment: Environment:
SSH_USER SSH username (default: nixos). SSH_USER SSH username (default: nixos).
SUDO_PASS Sudo password (skips the interactive prompt; useful
when calling from another script).
EOF EOF
} }
# Prompt for the sudo password once; store it for all _do_push calls.
# Accepts SUDO_PASS from the environment to allow non-interactive callers.
prompt_sudo_password() {
[[ "$dry_run" -eq 1 ]] && return
if [[ -n "${SUDO_PASS:-}" ]]; then
sudo_password="$SUDO_PASS"
return
fi
read -r -s -p "sudo password for ${SSH_USER} on remote hosts: " sudo_password < /dev/tty
echo >&2
}
locally_managed_hosts() { locally_managed_hosts() {
for f in "${keydir}"/*_ssh_host_ed25519_key.pub; do for f in "${keydir}"/*_ssh_host_ed25519_key.pub; do
[[ -e "$f" ]] || continue [[ -e "$f" ]] || continue
@@ -135,7 +151,7 @@ _do_push() {
if [[ "$dry_run" -eq 1 ]]; then if [[ "$dry_run" -eq 1 ]]; then
echo " [dry-run] would scp host-keys/${target}_ssh_host_ed25519_key{,.pub} to /tmp/" echo " [dry-run] would scp host-keys/${target}_ssh_host_ed25519_key{,.pub} to /tmp/"
echo " [dry-run] would: sudo install -m 0600/0644 to /etc/ssh/ and rm /tmp copies" echo " [dry-run] would: sudo -S install -m 0600/0644 to /etc/ssh/ and rm /tmp copies"
return return
fi fi
@@ -145,15 +161,15 @@ _do_push() {
scp -o StrictHostKeyChecking=no \ scp -o StrictHostKeyChecking=no \
"$pubfile" "${SSH_USER}@${hostname}:/tmp/push_ed25519_key.pub" "$pubfile" "${SSH_USER}@${hostname}:/tmp/push_ed25519_key.pub"
# Install with correct permissions via sudo. Commands are passed as an # Install via sudo -S: the password is piped via herestring so no PTY is
# argument string (not heredoc) so stdin stays free and -t can allocate # needed on either side. -p '' suppresses sudo's own prompt string.
# a PTY for the sudo password prompt. ssh -o StrictHostKeyChecking=no "${SSH_USER}@${hostname}" \
echo " (sudo password may be required)" "sudo -S -p '' bash -c '
ssh -t -o StrictHostKeyChecking=no "${SSH_USER}@${hostname}" \ install -m 0600 /tmp/push_ed25519_key /etc/ssh/ssh_host_ed25519_key
"sudo install -m 0600 /tmp/push_ed25519_key /etc/ssh/ssh_host_ed25519_key && install -m 0644 /tmp/push_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub
sudo install -m 0644 /tmp/push_ed25519_key.pub /etc/ssh/ssh_host_ed25519_key.pub && rm -f /tmp/push_ed25519_key /tmp/push_ed25519_key.pub
sudo rm -f /tmp/push_ed25519_key /tmp/push_ed25519_key.pub && echo \" [ok] host key installed\"
echo ' [ok] host key installed'" '" <<< "$sudo_password"
# Drop the stale known_hosts entry for this host (public key just changed) # Drop the stale known_hosts entry for this host (public key just changed)
ssh-keygen -R "$hostname" 2>/dev/null || true ssh-keygen -R "$hostname" 2>/dev/null || true
@@ -278,6 +294,7 @@ fi
nix_extra_opts nix_extra_opts
ensure_remote_current ensure_remote_current
prompt_sudo_password
if [[ "$mode" == "single" ]]; then if [[ "$mode" == "single" ]]; then
push_target "$target_arg" push_target "$target_arg"