diff --git a/setup/arch-autoinstall.sh b/setup/arch-autoinstall.sh index ef0540a..cc16119 100755 --- a/setup/arch-autoinstall.sh +++ b/setup/arch-autoinstall.sh @@ -5,7 +5,12 @@ # all prompts are answered from it. Missing fields fall back to interactive prompts. # # Answerfile fields: drive, kernel, keymap, hostname, username, encrypt, fido2_root, -# fido2_user, run_tui (password always prompted interactively) +# fido2_user, run_tui, password, luks_password +# The user "password" (and "luks_password" for encrypted installs) may be supplied +# in the answerfile for fully unattended deployment; when omitted they are prompted +# interactively. Storing secrets in the answerfile is a convenience/secret-handling +# tradeoff — keep the file access-restricted (it is embedded world-readable in the +# ISO by build.sh, so only bake passwords into images you control). # -E: propagate ERR trap into functions and subshells; -e: abort on any error; # -u: treat unset variables as errors; -o pipefail: catch failures inside pipes. @@ -254,8 +259,16 @@ else read -rp "Enable FIDO2 authentication for user login? (YES/NO): " FIDO_USER fi -# Password always prompted — never stored in answerfile -read -rp "Enter password for $USERNAME: " USERPASS +# User password: use the answerfile's "password" field when present (enables a +# fully unattended install); otherwise prompt interactively. Read via af_get so a +# missing/empty field cleanly falls through to the prompt. +USERPASS="" +if $AF_MODE; then USERPASS="$(af_get '.password')"; fi +if [[ -n "$USERPASS" ]]; then + echo "Password for $USERNAME: taken from answerfile." +else + read -rp "Enter password for $USERNAME: " USERPASS +fi [[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; } # In interactive mode, decide now whether to run the dotfiles TUI inside chroot. @@ -328,11 +341,23 @@ LUKS_BACKUP_KEY="" # path to key file, set only when encryption is active if [[ "$ENCRYPT_DISK" == "YES" ]]; then echo "Encrypting root partition..." + # LUKS passphrase: from the answerfile's "luks_password" when present (so an + # encrypted install can run unattended), otherwise prompt interactively. + LUKS_PASS="" + if $AF_MODE; then LUKS_PASS="$(af_get '.luks_password')"; fi # --type luks2 is required for systemd-cryptenroll (FIDO2 token enrollment). - # -v (verbose) shows progress; prompts for the primary passphrase interactively. - cryptsetup -v luksFormat "$ROOT_PART" --type luks2 - # Open the container and expose it as /dev/mapper/cryptroot for formatting. - cryptsetup open "$ROOT_PART" cryptroot + if [[ -n "$LUKS_PASS" ]]; then + echo "LUKS passphrase: taken from answerfile." + # --batch-mode skips the interactive "Are you sure" + passphrase prompts; + # --key-file=- reads the passphrase from stdin (the piped value). + printf '%s' "$LUKS_PASS" | cryptsetup -v luksFormat "$ROOT_PART" --type luks2 --batch-mode --key-file=- + printf '%s' "$LUKS_PASS" | cryptsetup open "$ROOT_PART" cryptroot --key-file=- + else + # -v (verbose) shows progress; prompts for the primary passphrase interactively. + cryptsetup -v luksFormat "$ROOT_PART" --type luks2 + # Open the container and expose it as /dev/mapper/cryptroot for formatting. + cryptsetup open "$ROOT_PART" cryptroot + fi # ── Auto-generate backup LUKS key ────────────────────────────────────────── # A random key is enrolled as a second LUKS slot so recovery is possible @@ -343,9 +368,14 @@ if [[ "$ENCRYPT_DISK" == "YES" ]]; then # wrapping) to produce a printable key with high entropy. dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY" echo "Enrolling auto-generated backup LUKS key..." - # luksAddKey reads the existing passphrase interactively to authorise adding - # the new key file into a free LUKS slot. - cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY" + # luksAddKey needs the existing passphrase to authorise adding the new key file + # into a free LUKS slot. With an answerfile passphrase we pipe it in on stdin; + # otherwise cryptsetup prompts for it interactively. + if [[ -n "$LUKS_PASS" ]]; then + printf '%s' "$LUKS_PASS" | cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY" + else + cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY" + fi # ── Optional FIDO2 enrollment ───────────────────────────────────────────── if [[ "$FIDO_ROOT" == "YES" ]]; then diff --git a/setup/generate-answerfile.sh b/setup/generate-answerfile.sh index b561ac7..9f845e0 100644 --- a/setup/generate-answerfile.sh +++ b/setup/generate-answerfile.sh @@ -140,11 +140,28 @@ AF_USERNAME=$(dialog --backtitle "$BACKTITLE" \ 9 54 "" \ 3>&1 1>&2 2>&3) || AF_USERNAME="" -# NOTE: passwords are intentionally NOT stored in the answerfile. -dialog --backtitle "$BACKTITLE" \ - --title " Password " \ - --msgbox "\n Passwords are NOT stored in the answerfile.\n\n You will be prompted for the user password\n at install time even in automated mode.\n" \ - 10 56 +# ── User password (optional) ────────────────────────────────────────────────── +# Storing the password makes the install fully unattended, but it is saved as +# plain text in the answerfile (and world-readable once embedded in an ISO). If +# the user declines, the field is left empty and the installer prompts at runtime. +AF_PASSWORD="" +# ask_pw → echoes a confirmed password, or empty if cancelled/declined. +ask_pw() { + local who="$1" p1 p2 + while true; do + p1=$(dialog --backtitle "$BACKTITLE" --title " Password " --insecure \ + --passwordbox "\n Enter password for ${who}\n (leave blank to be prompted at install time):" 11 60 \ + 3>&1 1>&2 2>&3) || return 1 + [[ -z "$p1" ]] && return 1 # blank = decline, prompt at install + p2=$(dialog --backtitle "$BACKTITLE" --title " Confirm Password " --insecure \ + --passwordbox "\n Re-enter the password to confirm:" 10 60 \ + 3>&1 1>&2 2>&3) || return 1 + if [[ "$p1" == "$p2" ]]; then printf '%s' "$p1"; return 0; fi + dialog --backtitle "$BACKTITLE" --title " Mismatch " \ + --msgbox "\n Passwords did not match. Try again.\n" 8 50 + done +} +AF_PASSWORD=$(ask_pw "the user account") || AF_PASSWORD="" # ── Disk encryption ─────────────────────────────────────────────────────────── dialog --backtitle "$BACKTITLE" \ @@ -154,7 +171,10 @@ dialog --backtitle "$BACKTITLE" \ AF_FIDO2_ROOT="false" AF_FIDO2_USER="false" +AF_LUKS_PASSWORD="" if [[ "$AF_ENCRYPT" == "true" ]]; then + # Optional LUKS passphrase so an encrypted install can also run unattended. + AF_LUKS_PASSWORD=$(ask_pw "LUKS disk encryption") || AF_LUKS_PASSWORD="" dialog --backtitle "$BACKTITLE" \ --title " FIDO2 Root Unlock " \ --yesno "\n Enable FIDO2 hardware key for LUKS root unlock?\n" \ @@ -403,6 +423,10 @@ mkdir -p "$(dirname "$OUTPUT")" printf ' "keymap": %s,\n' "$(json_str "$AF_KEYMAP")" printf ' "hostname": %s,\n' "$(json_str "$AF_HOSTNAME")" printf ' "username": %s,\n' "$(json_str "$AF_USERNAME")" + # Passwords are emitted as JSON null when not supplied; the installer treats + # null/absent as "prompt interactively". + printf ' "password": %s,\n' "$(json_str "$AF_PASSWORD")" + printf ' "luks_password": %s,\n' "$(json_str "$AF_LUKS_PASSWORD")" printf ' "encrypt": %s,\n' "$AF_ENCRYPT" printf ' "fido2_root": %s,\n' "$AF_FIDO2_ROOT" printf ' "fido2_user": %s,\n' "$AF_FIDO2_USER"