deploy: auto-fill password prompts from Proton Pass
Every deploy stopped at a password prompt. pass-cli is installed, so read the passwords from the HomeLab vault instead, per command: switch/boot/test darman@<config> -> nixos-rebuild's sudo prompt kexec/install root@<host> -> the target's ssh password nixos-rebuild prompts via getpass(), which reads /dev/tty and ignores a piped stdin, so that one runs under setsid: no controlling terminal means getpass falls back to stdin. kexec wraps the master ssh in `sshpass -e` (scp rides the control socket) and pins password auth so a key can't fall through into a second prompt; install uses nixos-anywhere's own --env-password. Missing pass-cli, a logged-out session, or an absent item all yield an empty string and the original interactive prompt -- nothing becomes mandatory. Passwords never reach a command line.
This commit is contained in:
+85
-11
@@ -19,6 +19,13 @@
|
|||||||
# SSH host key must be at ~/.config/homelab/<config>/ssh_host_ed25519_key.
|
# SSH host key must be at ~/.config/homelab/<config>/ssh_host_ed25519_key.
|
||||||
#
|
#
|
||||||
# Runs from a non-NixOS host too (nixos-rebuild / nixos-anywhere via `nix run`).
|
# Runs from a non-NixOS host too (nixos-rebuild / nixos-anywhere via `nix run`).
|
||||||
|
#
|
||||||
|
# Password prompts are auto-filled from the "HomeLab" Proton Pass vault when
|
||||||
|
# `pass-cli` is installed and logged in; otherwise every command prompts exactly
|
||||||
|
# as before. Items:
|
||||||
|
# darman@<config> darman's sudo password (switch/boot/test)
|
||||||
|
# root@<host> root's ssh password (kexec/install)
|
||||||
|
# Override with HOMELAB_PASS_ITEM / HOMELAB_PASS_ROOT_ITEM / HOMELAB_PASS_VAULT.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Locate the repo root (flake dir) regardless of where this script lives on disk.
|
# Locate the repo root (flake dir) regardless of where this script lives on disk.
|
||||||
@@ -29,6 +36,26 @@ export PATH="/nix/var/nix/profiles/default/bin:$PATH"
|
|||||||
|
|
||||||
die() { echo "error: $*" >&2; exit 1; }
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
# The password field of a Proton Pass item ("--field password" prints the bare
|
||||||
|
# value, one line), or empty if pass-cli is missing / logged out / has no such
|
||||||
|
# item — every caller then falls back to the normal interactive prompt.
|
||||||
|
proton_pass_password() {
|
||||||
|
local title="$1"
|
||||||
|
command -v pass-cli >/dev/null 2>&1 || return 0
|
||||||
|
pass-cli item view \
|
||||||
|
--vault-name "${HOMELAB_PASS_VAULT:-HomeLab}" \
|
||||||
|
--item-title "$title" \
|
||||||
|
--field password --output human 2>/dev/null | head -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Path to an sshpass binary (system one, else built from nixpkgs). Empty if
|
||||||
|
# neither is available.
|
||||||
|
sshpass_bin() {
|
||||||
|
command -v sshpass 2>/dev/null && return 0
|
||||||
|
nix build --no-link --print-out-paths nixpkgs#sshpass 2>/dev/null \
|
||||||
|
| sed 's|$|/bin/sshpass|'
|
||||||
|
}
|
||||||
|
|
||||||
cmd="${1:-}"; [ -n "$cmd" ] || die "usage: ./deploy <kexec|install|switch|boot|test> ..."
|
cmd="${1:-}"; [ -n "$cmd" ] || die "usage: ./deploy <kexec|install|switch|boot|test> ..."
|
||||||
|
|
||||||
case "$cmd" in
|
case "$cmd" in
|
||||||
@@ -49,10 +76,32 @@ case "$cmd" in
|
|||||||
o=(-o ControlMaster=auto -o "ControlPath=$cm" -o ControlPersist=300 \
|
o=(-o ControlMaster=auto -o "ControlPath=$cm" -o ControlPersist=300 \
|
||||||
-o StrictHostKeyChecking=accept-new)
|
-o StrictHostKeyChecking=accept-new)
|
||||||
|
|
||||||
echo ">> connecting to root@$host (enter the root password once)"
|
# Root's password from Proton Pass, fed to ssh/scp via sshpass -e. Only the
|
||||||
ssh "${o[@]}" "root@$host" 'mkdir -p /tmp/bin'
|
# first (master) connection authenticates; the rest ride the control socket.
|
||||||
|
# SSHPASS is exported into the sshpass child only, never onto a command line.
|
||||||
|
sp=()
|
||||||
|
root_pw="$(proton_pass_password "${HOMELAB_PASS_ROOT_ITEM:-root@$host}" || true)"
|
||||||
|
if [ -n "$root_pw" ]; then
|
||||||
|
sshpass="$(sshpass_bin || true)"
|
||||||
|
if [ -n "$sshpass" ]; then
|
||||||
|
sp=(env "SSHPASS=$root_pw" "$sshpass" -e)
|
||||||
|
# sshpass drives the password prompt; don't let a key/agent short-circuit
|
||||||
|
# into an interactive one for a host that only accepts passwords.
|
||||||
|
o+=(-o PreferredAuthentications=password -o PubkeyAuthentication=no)
|
||||||
|
else
|
||||||
|
echo ">> sshpass unavailable — falling back to the interactive prompt" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#sp[@]} -gt 0 ]; then
|
||||||
|
echo ">> connecting to root@$host (password from Proton Pass)"
|
||||||
|
else
|
||||||
|
echo ">> connecting to root@$host (enter the root password once)"
|
||||||
|
fi
|
||||||
|
"${sp[@]}" ssh "${o[@]}" "root@$host" 'mkdir -p /tmp/bin'
|
||||||
scp "${o[@]}" "$cpio" "root@$host:/tmp/bin/cpio"
|
scp "${o[@]}" "$cpio" "root@$host:/tmp/bin/cpio"
|
||||||
scp "${o[@]}" "$bbox" "root@$host:/tmp/bin/gzip" # busybox as gzip (argv0)
|
scp "${o[@]}" "$bbox" "root@$host:/tmp/bin/gzip" # busybox as gzip (argv0)
|
||||||
|
unset root_pw
|
||||||
|
|
||||||
echo ">> streaming installer + kexec-ing. SSH drops as the box jumps into the"
|
echo ">> streaming installer + kexec-ing. SSH drops as the box jumps into the"
|
||||||
echo " RAM installer. Disks are untouched."
|
echo " RAM installer. Disks are untouched."
|
||||||
@@ -79,11 +128,22 @@ case "$cmd" in
|
|||||||
install -Dm644 "$hostkey.pub" "$stage/etc/ssh/ssh_host_ed25519_key.pub"
|
install -Dm644 "$hostkey.pub" "$stage/etc/ssh/ssh_host_ed25519_key.pub"
|
||||||
|
|
||||||
echo ">> nixos-anywhere .#$config onto root@$host (OS disk WILL be wiped)"
|
echo ">> nixos-anywhere .#$config onto root@$host (OS disk WILL be wiped)"
|
||||||
nix run github:nix-community/nixos-anywhere -- \
|
anywhere=(--flake ".#$config"
|
||||||
--flake ".#$config" \
|
--extra-files "$stage"
|
||||||
--extra-files "$stage" \
|
--generate-hardware-config nixos-generate-config "./hosts/$config/hardware-configuration.nix"
|
||||||
--generate-hardware-config nixos-generate-config "./hosts/$config/hardware-configuration.nix" \
|
--target-host "root@$host")
|
||||||
--target-host "root@$host"
|
|
||||||
|
# nixos-anywhere's --env-password reads root's ssh password from $SSHPASS
|
||||||
|
# (it ships its own sshpass), so a vault hit skips the ssh-copy-id prompt.
|
||||||
|
root_pw="$(proton_pass_password "${HOMELAB_PASS_ROOT_ITEM:-root@$host}" || true)"
|
||||||
|
if [ -n "$root_pw" ]; then
|
||||||
|
echo ">> root ssh password from Proton Pass"
|
||||||
|
env "SSHPASS=$root_pw" nix run github:nix-community/nixos-anywhere -- \
|
||||||
|
--env-password "${anywhere[@]}"
|
||||||
|
else
|
||||||
|
nix run github:nix-community/nixos-anywhere -- "${anywhere[@]}"
|
||||||
|
fi
|
||||||
|
unset root_pw
|
||||||
;;
|
;;
|
||||||
|
|
||||||
switch|boot|test)
|
switch|boot|test)
|
||||||
@@ -95,10 +155,24 @@ case "$cmd" in
|
|||||||
# security.sudo.wheelNeedsPassword = true, and --use-remote-sudo only
|
# security.sudo.wheelNeedsPassword = true, and --use-remote-sudo only
|
||||||
# prefixes with sudo without ever prompting. Asks for darman's password
|
# prefixes with sudo without ever prompting. Asks for darman's password
|
||||||
# (the darman_password hash in each host's sops file).
|
# (the darman_password hash in each host's sops file).
|
||||||
nix run nixpkgs#nixos-rebuild -- "$cmd" \
|
rebuild=(nix run nixpkgs#nixos-rebuild -- "$cmd"
|
||||||
--flake ".#$config" \
|
--flake ".#$config"
|
||||||
--target-host "darman@$host" \
|
--target-host "darman@$host"
|
||||||
--ask-sudo-password
|
--ask-sudo-password)
|
||||||
|
|
||||||
|
item="${HOMELAB_PASS_ITEM:-darman@$config}"
|
||||||
|
pw="$(proton_pass_password "$item" || true)"
|
||||||
|
if [ -n "$pw" ] && command -v setsid >/dev/null 2>&1; then
|
||||||
|
# nixos-rebuild prompts with getpass(), which reads /dev/tty and ignores a
|
||||||
|
# piped stdin. setsid drops the controlling terminal, so getpass falls back
|
||||||
|
# to stdin and takes the vault password (it warns about echo — harmless,
|
||||||
|
# nothing is echoed since the password never reaches the terminal).
|
||||||
|
echo ">> sudo password from Proton Pass ($item)"
|
||||||
|
printf '%s\n' "$pw" | setsid -w "${rebuild[@]}"
|
||||||
|
else
|
||||||
|
"${rebuild[@]}"
|
||||||
|
fi
|
||||||
|
unset pw
|
||||||
;;
|
;;
|
||||||
|
|
||||||
image|flash)
|
image|flash)
|
||||||
|
|||||||
Reference in New Issue
Block a user