feat(installer): global flatpaks, drop redundant TUI hostname, upfront passwords

- Flatpaks now install globally again: ensure_flatpak adds the Flathub remote at
  --system and all 19 app modules use `sudo flatpak install --system`. Running
  via sudo (root) performs the system op directly, avoiding the SystemHelper/
  polkit D-Bus path that caused "The name is not activatable" for non-root users.

- tui-install.sh no longer prompts for or sets the hostname — the base installer
  already configures it. Removed the Hostname section, the MAC-suffix helper, the
  AF_HOSTNAME field and the summary line.

- archbaseos-guided-install.sh now gathers ALL input up front, including
  passwords. New ask_password() prompts in clear text (by request) and requires a
  confirmation entry, looping until the two match — so each password is typed
  exactly twice and never again. The LUKS passphrase is captured once and fed to
  luksFormat/open/luksAddKey (--key-file=-) and cryptenroll ($PASSWORD), instead
  of cryptsetup prompting repeatedly. After all input, a single all-caps "type
  YES" gate replaces the old per-step confirmations (answerfile mode keeps its
  5-second abort window). The run-TUI choice is also asked up front.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-06-26 18:40:49 +02:00
parent 9107b9961a
commit bc13de7508
22 changed files with 118 additions and 101 deletions

View File

@ -55,11 +55,27 @@ trap 'error_handler $LINENO' ERR
# Helper Functions
############################################
confirm() {
# Require the exact string "YES" before erasing $1 — prevents accidental wipes.
echo "WARNING: This will ERASE ALL DATA on $1"
read -rp "Type YES to continue: " ans
[[ $ans == "YES" ]]
ask_password() {
# Prompt for a password and confirm it — BOTH shown in clear text (by request).
# Loops until the two entries are identical and non-empty, so a typo can't
# silently lock the operator out. The password is therefore typed exactly
# twice (once + confirmation) and never again. The result is printed to stdout
# for capture via $(...); the prompts and error messages go to stderr so they
# never pollute the captured value.
local label=$1 _p1 _p2
while true; do
read -rp "$label: " _p1
if [[ -z "$_p1" ]]; then
echo " Password cannot be empty — try again." >&2
continue
fi
read -rp "$label (confirm): " _p2
if [[ "$_p1" == "$_p2" ]]; then
printf '%s' "$_p1"
return 0
fi
echo " Passwords do not match — try again." >&2
done
}
ask() {
@ -157,8 +173,13 @@ KEYMAPS=(
)
############################################
# User input
# User input — gather EVERYTHING up front
############################################
# All operator input (including passwords) is collected here, before any
# destructive action runs. Passwords are shown in clear text by request and are
# each entered twice (once + confirmation) so a typo can't silently lock you out.
# After this block the only remaining interaction is the single final "type YES"
# gate plus any physical FIDO2 key taps.
if $AF_MODE; then
KERNEL=$(af_get '.kernel' 'linux')
RAW_HOSTNAME=$(af_get '.hostname' '')
@ -209,24 +230,62 @@ else
# Out-of-range input silently falls back to the first entry (us).
KEYMAP="${KEYMAPS[0]%%|*}"
fi
# Whether to run the dotfiles TUI inside the chroot — asked now so the whole
# back half of the install can proceed without further interaction.
read -rp "Run dotfiles TUI setup inside chroot after base install? [YES/no]: " _TUI_IN
_TUI_IN="${_TUI_IN:-YES}"
[[ "${_TUI_IN^^}" == "YES" ]] && RUN_TUI="YES" || RUN_TUI="NO"
fi
# Password always prompted interactively — never stored in the answerfile.
read -rp "Password for $USERNAME: " USERPASS
[[ -z "$USERPASS" ]] && { echo "Error: password cannot be empty."; exit 1; }
# Print block device layout so the operator can visually confirm the target drive.
# ── Target drive ──────────────────────────────────────────────────────────────
# Print the block device layout so the operator can identify the correct disk.
lsblk
if $AF_MODE && [[ -n "$(af_get '.drive')" ]]; then
DRIVE=$(af_get '.drive')
echo "Drive (from answerfile): $DRIVE"
echo "WARNING: All data on $DRIVE will be erased. Proceeding in 5 seconds..."
# Give the operator a 5-second window to abort with Ctrl-C before destructive ops.
sleep 5
else
DRIVE=$(ask "Enter install drive (e.g., /dev/sda)")
confirm "$DRIVE" || exit 1
fi
# ── Passwords (always interactive; shown in clear text, each entered twice) ────
# Never read from the answerfile. Captured once here and reused everywhere so the
# operator is never prompted for them again mid-install.
USERPASS=$(ask_password "Password for $USERNAME")
# LUKS passphrase — only needed when encrypting. Reused below for luksFormat /
# open / luksAddKey / cryptenroll so it is typed exactly once (plus confirmation),
# never again during the destructive phase.
LUKS_PASS=""
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
LUKS_PASS=$(ask_password "Disk encryption (LUKS) passphrase")
fi
# ── Final confirmation ────────────────────────────────────────────────────────
# A single gate before anything destructive happens. Summarise the choices so the
# operator can sanity-check them, then require one explicit all-caps YES.
echo ""
echo "──────────────────────────────────────────────"
echo " Review your selections:"
echo " Kernel: $KERNEL"
echo " Hostname: $HOSTNAME"
echo " Username: $USERNAME"
echo " Keymap: $KEYMAP"
echo " Encrypt disk: $ENCRYPT_DISK"
echo " FIDO2 root: $ENABLE_FIDO_ROOT"
echo " FIDO2 user: $ENABLE_FIDO_USER"
echo " Run TUI: $RUN_TUI"
echo " Target drive: $DRIVE"
echo "──────────────────────────────────────────────"
echo " WARNING: ALL DATA on $DRIVE will be PERMANENTLY ERASED."
echo ""
if $AF_MODE; then
# Unattended deployment: no human to type YES — give a short abort window.
echo "Answerfile mode — proceeding in 5 seconds (Ctrl-C to abort)..."
sleep 5
else
read -rp "Type YES (all caps) to begin installation: " _final_ans
[[ "$_final_ans" == "YES" ]] || { echo "Aborted."; exit 1; }
fi
# Required packages — installed into the live environment before partitioning.
@ -285,9 +344,11 @@ LUKS_BACKUP_KEY=""
if [[ "$ENCRYPT_DISK" == "YES" ]]; then
echo "Formatting LUKS2 root..."
# --type luks2 uses the newer LUKS2 format required by systemd-cryptenroll for FIDO2.
cryptsetup luksFormat "$ROOT_PART" --type luks2
# Feed the pre-gathered passphrase on stdin (--key-file=-) and --batch-mode to
# skip cryptsetup's own interactive "type uppercase YES" + passphrase prompts.
printf '%s' "$LUKS_PASS" | cryptsetup luksFormat "$ROOT_PART" --type luks2 --batch-mode --key-file=-
# Open (decrypt) the container; exposes it as /dev/mapper/cryptroot for formatting.
cryptsetup open "$ROOT_PART" cryptroot
printf '%s' "$LUKS_PASS" | cryptsetup open "$ROOT_PART" cryptroot --key-file=-
# ── Auto-generate backup LUKS key ─────────────────────────────────────────
# A random key enrolled in a second LUKS slot enables recovery without
@ -297,8 +358,9 @@ if [[ "$ENCRYPT_DISK" == "YES" ]]; then
# Read 64 bytes of entropy; base64-encode with -w0 to disable line wrapping.
dd if=/dev/urandom bs=64 count=1 2>/dev/null | base64 -w0 > "$LUKS_BACKUP_KEY"
echo "Enrolling auto-generated backup LUKS key..."
# luksAddKey prompts for the existing passphrase to authorise adding the new key file.
cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY"
# --key-file=- supplies the EXISTING passphrase (on stdin) to authorise the
# operation; the positional file is the NEW backup key added to a second slot.
printf '%s' "$LUKS_PASS" | cryptsetup luksAddKey "$ROOT_PART" "$LUKS_BACKUP_KEY" --key-file=-
if [[ "$ENABLE_FIDO_ROOT" == "YES" ]]; then
echo "Enroll FIDO2 key for LUKS2"
@ -306,7 +368,9 @@ if [[ "$ENCRYPT_DISK" == "YES" ]]; then
pause
# systemd-cryptenroll writes the FIDO2 credential into a LUKS2 token slot.
# --fido2-with-client-pin=no: presence tap only, no PIN required at boot.
systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
# $PASSWORD supplies the existing passphrase to unlock a slot for enrollment,
# so only the physical key tap remains interactive here.
PASSWORD="$LUKS_PASS" systemd-cryptenroll "$ROOT_PART" --fido2-device=auto --fido2-with-client-pin=no
fi
# Format btrfs on the decrypted mapper device, not the raw partition.
@ -540,15 +604,9 @@ fi
############################################
# DOTFILES SETUP (in-chroot, optional)
############################################
if $AF_MODE; then
# RUN_TUI was already populated from the answerfile earlier.
# RUN_TUI was gathered up front (answerfile field or the initial prompt), so no
# additional interaction is needed at this point.
_DO_TUI="${RUN_TUI}"
else
read -rp "Run dotfiles TUI setup inside chroot now? [YES/no]: " _TUI_IN
# Default to YES when the user presses Enter without typing anything.
_TUI_IN="${_TUI_IN:-YES}"
[[ "${_TUI_IN^^}" == "YES" ]] && _DO_TUI="YES" || _DO_TUI="NO"
fi
if [[ "${_DO_TUI^^}" == "YES" ]]; then
# Grant temporary passwordless sudo so the TUI installer can call pacman/yay

View File

@ -58,15 +58,15 @@ ensure_flatpak() {
log "Installing flatpak..."
sudo pacman -S --noconfirm --needed flatpak
fi
# Add the Flathub remote at --user scope. A system-scope remote (and the
# matching `flatpak install` below) needs the org.freedesktop.Flatpak
# SystemHelper D-Bus service + polkit, which are not activatable in a chroot
# or bare-TTY install — that is the "The name is not activatable" failure.
# User scope needs neither, so it works during install and is the right place
# for per-user app installs anyway (apply_flatpak_theme already uses --user).
if ! flatpak remotes --user 2>/dev/null | grep -q flathub; then
log "Adding Flathub remote (user)..."
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
# Add the Flathub remote at --system scope (global install for all users).
# A non-root user requesting a system action triggers the
# org.freedesktop.Flatpak SystemHelper over D-Bus + polkit, which is not
# activatable in a chroot/bare-TTY install — the "The name is not activatable"
# failure. Running via sudo (as root) performs the system operation directly,
# so no helper/polkit is involved and global installs work during setup.
if ! flatpak remotes --system 2>/dev/null | grep -q flathub; then
log "Adding Flathub remote (system)..."
sudo flatpak remote-add --system --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
fi
}

View File

@ -25,7 +25,7 @@ ensure_flatpak
# Install Ardour from the Flathub repository.
# -y skips the interactive confirmation prompt so the script is non-interactive.
flatpak install --user -y flathub org.ardour.Ardour
sudo flatpak install --system -y flathub org.ardour.Ardour
# apply_flatpak_theme: applies the cyberqueer GTK theme override so Ardour's
# GTK widgets match the rest of the desktop, rather than using the Flatpak

View File

@ -22,7 +22,7 @@ log "Installing Audacity (Flatpak)..."
ensure_flatpak
# Install from Flathub. -y suppresses the interactive confirmation.
flatpak install --user -y flathub org.audacityteam.Audacity
sudo flatpak install --system -y flathub org.audacityteam.Audacity
# apply_flatpak_theme: injects the cyberqueer GTK theme into Audacity's
# Flatpak environment so it renders consistently with the rest of the desktop.

View File

@ -29,7 +29,7 @@ ensure_flatpak
# Install Blender from Flathub. The Flatpak edition ships its own Python,
# CUDA/HIP compute libraries, and codec support pre-bundled, avoiding
# dependency conflicts with the system Python or GPU driver packages.
flatpak install --user -y flathub org.blender.Blender
sudo flatpak install --system -y flathub org.blender.Blender
# apply_flatpak_theme: sets the cyberqueer GTK theme for Blender's dialogs
# (the main 3D viewport uses its own renderer, but file choosers and system

View File

@ -24,7 +24,7 @@ log "Installing Chromium (Flatpak)..."
ensure_flatpak
# Install Chromium from Flathub. -y skips the confirmation prompt.
flatpak install --user -y flathub org.chromium.Chromium
sudo flatpak install --system -y flathub org.chromium.Chromium
# apply_flatpak_theme: applies the cyberqueer GTK theme so Chromium's file
# dialogs and context menus match the rest of the desktop.

View File

@ -26,7 +26,7 @@ log "Installing Firefox (Flatpak)..."
ensure_flatpak
# Install from Flathub. -y skips interactive confirmation.
flatpak install --user -y flathub org.mozilla.firefox
sudo flatpak install --system -y flathub org.mozilla.firefox
# apply_flatpak_theme: injects the cyberqueer GTK theme so Firefox's native
# file-open dialogs and context menus match the rest of the desktop.

View File

@ -4,7 +4,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Obsidian (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub md.obsidian.Obsidian
sudo flatpak install --system -y flathub md.obsidian.Obsidian
apply_flatpak_theme "md.obsidian.Obsidian"
log "Obsidian installed."

View File

@ -8,6 +8,6 @@ log "Installing PrismLauncher (Flatpak)..."
# PrismLauncher is a Minecraft launcher that manages multiple instances and Java versions.
# The Flatpak edition bundles its own Java runtimes, avoiding system JDK conflicts.
# -y: non-interactive, auto-approve all prompts.
flatpak install --user -y flathub org.prismlauncher.PrismLauncher
sudo flatpak install --system -y flathub org.prismlauncher.PrismLauncher
apply_flatpak_theme "org.prismlauncher.PrismLauncher"
log "PrismLauncher installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Remmina (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub org.remmina.Remmina
sudo flatpak install --system -y flathub org.remmina.Remmina
apply_flatpak_theme "org.remmina.Remmina"
log "Remmina installed."

View File

@ -4,7 +4,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Rnote (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub com.github.flxzt.rnote
sudo flatpak install --system -y flathub com.github.flxzt.rnote
apply_flatpak_theme "com.github.flxzt.rnote"
log "Rnote installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Shotcut (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub org.shotcut.Shotcut
sudo flatpak install --system -y flathub org.shotcut.Shotcut
apply_flatpak_theme "org.shotcut.Shotcut"
log "Shotcut installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Steam (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub com.valvesoftware.Steam
sudo flatpak install --system -y flathub com.valvesoftware.Steam
apply_flatpak_theme "com.valvesoftware.Steam"
log "Steam installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Stunt Rally (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub io.github.stuntrally.StuntRally3
sudo flatpak install --system -y flathub io.github.stuntrally.StuntRally3
apply_flatpak_theme "io.github.stuntrally.StuntRally3"
log "Stunt Rally installed."

View File

@ -4,7 +4,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Tangent Notes (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub io.github.suchnsuch.Tangent
sudo flatpak install --system -y flathub io.github.suchnsuch.Tangent
apply_flatpak_theme "io.github.suchnsuch.Tangent"
log "Tangent Notes installed."

View File

@ -4,7 +4,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Vesktop (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub dev.vencord.Vesktop
sudo flatpak install --system -y flathub dev.vencord.Vesktop
apply_flatpak_theme "dev.vencord.Vesktop"
log "Deploying Vencord config..."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing VSCodium (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub com.vscodium.codium
sudo flatpak install --system -y flathub com.vscodium.codium
apply_flatpak_theme "com.vscodium.codium"
log "VSCodium installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Wireshark (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub org.wireshark.Wireshark
sudo flatpak install --system -y flathub org.wireshark.Wireshark
apply_flatpak_theme "org.wireshark.Wireshark"
log "Wireshark installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Xournal++ (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub com.github.xournalpp.xournalpp
sudo flatpak install --system -y flathub com.github.xournalpp.xournalpp
apply_flatpak_theme "com.github.xournalpp.xournalpp"
log "Xournal++ installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Zed editor (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub dev.zed.Zed
sudo flatpak install --system -y flathub dev.zed.Zed
apply_flatpak_theme "dev.zed.Zed"
log "Zed editor installed."

View File

@ -4,6 +4,6 @@ source "$(dirname "${BASH_SOURCE[0]}")/../../lib/logging.sh"
log "Installing Zen Browser (Flatpak)..."
ensure_flatpak
flatpak install --user -y flathub io.github.zen_browser.zen
sudo flatpak install --system -y flathub io.github.zen_browser.zen
apply_flatpak_theme "io.github.zen_browser.zen"
log "Zen Browser installed."

View File

@ -390,7 +390,6 @@ count_steps() {
# AF_* variables hold values parsed from the JSON answerfile.
# They are initialised to safe defaults so the rest of the script can reference
# them unconditionally whether or not answerfile mode is active.
AF_HOSTNAME=""
AF_COMPONENTS=""
AF_DE="none"
AF_APPS=""
@ -406,7 +405,6 @@ load_answerfile() {
# -r outputs raw strings without JSON quoting.
# '// ""' / '// "none"' are jq's null-coalescing operator: if the key is
# absent from the file the expression yields the fallback instead.
AF_HOSTNAME=$(jq -r '.hostname // ""' "$ANSWERFILE")
# JSON arrays are flattened to a space-separated string to match the format
# that COMPONENTS and SELECTED_APPS expect throughout the rest of the script.
AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE")
@ -421,20 +419,6 @@ load_answerfile() {
AF_COLOR_RED=$(jq -r '.colors.COLOR_RED // ""' "$ANSWERFILE")
}
# ── MAC address helper ────────────────────────────────────────────────────────
get_mac_suffix() {
# Returns the MAC address of the first non-loopback interface, colons stripped.
# Used to make answerfile-derived hostnames unique per machine (e.g. "myhost-aabbccddee").
local mac
# awk state machine: sets iface=1 when it sees an interface line whose name
# does NOT start with "lo" ([^l][^o] skips "lo"), then captures the
# 'link/ether XX:XX:XX:XX:XX:XX' line and exits immediately after the first hit.
mac=$(ip link show 2>/dev/null \
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
# Strip colons from the MAC so it is safe to embed in a hostname (only [a-z0-9-] allowed).
printf '%s' "${mac//:/}"
}
# ── Preflight ─────────────────────────────────────────────────────────────────
if [[ $EUID -eq 0 ]]; then
# Root context (e.g. archiso chroot): shim sudo as a passthrough
@ -493,32 +477,8 @@ if ! $ANSWERFILE_MODE; then
Log: $LOG"
fi
# ── Hostname ──────────────────────────────────────────────────────────────────
HOSTNAME_SET=""
if $ANSWERFILE_MODE; then
if [[ -n "$AF_HOSTNAME" ]]; then
# Append the stripped MAC address to the base name from the answerfile.
# This makes each cloned machine's hostname unique without needing per-host
# answerfiles (useful for mass-provisioning identical images).
MAC=$(get_mac_suffix)
HOSTNAME_SET="${AF_HOSTNAME}-${MAC}"
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
fi
else
# ui_input prints its prompt to stderr and the typed value to stdout, so the
# $() capture receives only the hostname. A blank line keeps the default.
HOSTNAME_INPUT=$(ui_input " Hostname " \
" Hostname for this machine (leave blank to keep default).") || HOSTNAME_INPUT=""
HOSTNAME_SET="$HOSTNAME_INPUT"
fi
if [[ -n "$HOSTNAME_SET" ]]; then
# hostnamectl is the preferred method on systemd systems; fall back to writing
# /etc/hostname directly for minimal chroot environments that lack systemd.
sudo hostnamectl set-hostname "$HOSTNAME_SET" 2>/dev/null \
|| echo "$HOSTNAME_SET" | sudo tee /etc/hostname > /dev/null
printf "Hostname set: %s\n" "$HOSTNAME_SET" >> "$LOG"
fi
# Note: the hostname is set by the base installer (archbaseos-guided-install.sh),
# so this script intentionally does not prompt for or change it.
# ── Component selection ───────────────────────────────────────────────────────
if $ANSWERFILE_MODE; then
@ -660,7 +620,6 @@ if ! $ANSWERFILE_MODE; then
# Build a human-readable summary of everything that will be installed so the
# user can review the full list before any changes are made to the system.
SUMMARY=""
[[ -n "$HOSTNAME_SET" ]] && SUMMARY+=" ✦ Hostname: $HOSTNAME_SET\n"
[[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n"
[[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n"
[[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n"