Archived
fix(push-host-keys): prompt sudo password once, pass via sudo -S
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:
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user