378 lines
20 KiB
Bash
378 lines
20 KiB
Bash
#!/bin/bash
|
|
# generate-answerfile.sh — dry-run the M-Archy installer dialogs and write
|
|
# all selections to an answerfile.json. Nothing is installed.
|
|
#
|
|
# Usage:
|
|
# bash generate-answerfile.sh [OUTPUT_PATH]
|
|
# OUTPUT_PATH defaults to ~/answerfile.json
|
|
#
|
|
# The generated file can be placed at /answerfile.json on the installer USB
|
|
# (use build.sh --preconf to embed it automatically) so that the full
|
|
# install — base OS + dotfiles — runs without any manual input.
|
|
|
|
set -uo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
OUTPUT="${1:-$HOME/answerfile.json}"
|
|
TMP_D="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_D"' EXIT
|
|
|
|
BACKTITLE="M-Archy Answerfile Generator"
|
|
|
|
# ── Dialog theme ──────────────────────────────────────────────────────────────
|
|
export DIALOGRC="$TMP_D/dialogrc"
|
|
cat > "$DIALOGRC" <<'EOF'
|
|
use_shadow = ON
|
|
use_colors = ON
|
|
screen_color = (BLACK,BLACK,ON)
|
|
shadow_color = (BLACK,BLACK,ON)
|
|
title_color = (MAGENTA,BLACK,ON)
|
|
border_color = (MAGENTA,BLACK,ON)
|
|
button_active_color = (BLACK,MAGENTA,ON)
|
|
button_inactive_color = (WHITE,BLACK,OFF)
|
|
button_key_active_color = (BLACK,CYAN,ON)
|
|
button_key_inactive_color = (CYAN,BLACK,ON)
|
|
button_label_active_color = (BLACK,MAGENTA,ON)
|
|
button_label_inactive_color = (WHITE,BLACK,OFF)
|
|
inputbox_color = (WHITE,BLACK,OFF)
|
|
inputbox_border_color = (MAGENTA,BLACK,ON)
|
|
menubox_color = (WHITE,BLACK,OFF)
|
|
menubox_border_color = (MAGENTA,BLACK,ON)
|
|
item_color = (WHITE,BLACK,OFF)
|
|
item_selected_color = (BLACK,MAGENTA,ON)
|
|
tag_color = (CYAN,BLACK,ON)
|
|
tag_selected_color = (BLACK,CYAN,ON)
|
|
tag_key_color = (CYAN,BLACK,ON)
|
|
tag_key_selected_color = (BLACK,CYAN,ON)
|
|
check_color = (WHITE,BLACK,OFF)
|
|
check_selected_color = (BLACK,MAGENTA,ON)
|
|
uarrow_color = (MAGENTA,BLACK,ON)
|
|
darrow_color = (MAGENTA,BLACK,ON)
|
|
EOF
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
require_dialog() {
|
|
command -v dialog &>/dev/null && return
|
|
echo "dialog not found — installing..."
|
|
sudo pacman -S --noconfirm dialog || { echo "Failed to install dialog."; exit 1; }
|
|
}
|
|
|
|
require_jq() {
|
|
command -v jq &>/dev/null && return
|
|
echo "jq not found — installing..."
|
|
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
|
|
}
|
|
|
|
die() { clear; printf "\n Error: %s\n\n" "$1" >&2; exit 1; }
|
|
|
|
# json_str: emit a properly-quoted JSON string value (handles empty → null)
|
|
json_str() {
|
|
local v="$1"
|
|
if [[ -z "$v" ]]; then printf 'null'; else printf '"%s"' "$v"; fi
|
|
}
|
|
|
|
# json_bool: emit true/false from YES/NO or true/false input
|
|
json_bool() {
|
|
local v="${1,,}"
|
|
[[ "$v" == "yes" || "$v" == "true" ]] && echo "true" || echo "false"
|
|
}
|
|
|
|
# ── Preflight ─────────────────────────────────────────────────────────────────
|
|
require_dialog
|
|
require_jq
|
|
|
|
# ── Welcome ───────────────────────────────────────────────────────────────────
|
|
dialog --backtitle "$BACKTITLE" \
|
|
--title " Answerfile Generator " \
|
|
--msgbox "\n\
|
|
This tool walks you through all installer dialogs\n\
|
|
and saves your choices to an answerfile.\n\
|
|
─────────────────────────────────────────────────\n\
|
|
\n\
|
|
NO software will be installed — this is a dry run.\n\
|
|
\n\
|
|
Output: $OUTPUT\n\
|
|
\n\
|
|
To use on a USB: build.sh --preconf $OUTPUT\n" 16 62
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# PART 1 — Base OS install options
|
|
# ═══════════════════════════════════════════════════════════════
|
|
|
|
# ── Drive ─────────────────────────────────────────────────────────────────────
|
|
AVAIL_DRIVES=$(lsblk -dn -o NAME,SIZE,MODEL 2>/dev/null | awk '{printf "%s \"%s %s\" off ", $1, $2, $3}' || true)
|
|
|
|
AF_DRIVE=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Target Drive " \
|
|
--inputbox "\n Enter the install drive (e.g. /dev/sda, /dev/nvme0n1).\n\n Available drives:\n$(lsblk -dn -o NAME,SIZE,MODEL 2>/dev/null | sed 's/^/ /')\n" \
|
|
16 64 "/dev/sda" \
|
|
3>&1 1>&2 2>&3) || AF_DRIVE=""
|
|
|
|
# ── Kernel ────────────────────────────────────────────────────────────────────
|
|
AF_KERNEL=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Kernel " \
|
|
--menu "Select kernel package:" 12 54 3 \
|
|
"linux" "Stable kernel" \
|
|
"linux-lts" "Long-term support kernel" \
|
|
"linux-zen" "Zen performance kernel" \
|
|
3>&1 1>&2 2>&3) || AF_KERNEL="linux"
|
|
|
|
# ── Hostname ──────────────────────────────────────────────────────────────────
|
|
AF_HOSTNAME=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Hostname " \
|
|
--inputbox "\n Hostname for the new system.\n\n Leave blank to keep default.\n\n Note: a MAC address suffix will be appended\n automatically when the answerfile is applied,\n preventing hostname conflicts across machines.\n" \
|
|
14 62 "" \
|
|
3>&1 1>&2 2>&3) || AF_HOSTNAME=""
|
|
|
|
# ── Username ──────────────────────────────────────────────────────────────────
|
|
AF_USERNAME=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Username " \
|
|
--inputbox "\n Name for the primary user account.\n" \
|
|
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
|
|
|
|
# ── Disk encryption ───────────────────────────────────────────────────────────
|
|
dialog --backtitle "$BACKTITLE" \
|
|
--title " Disk Encryption " \
|
|
--yesno "\n Enable LUKS2 disk encryption on the root partition?\n\n If yes, a backup LUKS key will be auto-generated\n and placed at /_LUKS_BACKUP_KEY in the new system.\n" \
|
|
11 62 && AF_ENCRYPT="true" || AF_ENCRYPT="false"
|
|
|
|
AF_FIDO2_ROOT="false"
|
|
AF_FIDO2_USER="false"
|
|
if [[ "$AF_ENCRYPT" == "true" ]]; then
|
|
dialog --backtitle "$BACKTITLE" \
|
|
--title " FIDO2 Root Unlock " \
|
|
--yesno "\n Enable FIDO2 hardware key for LUKS root unlock?\n" \
|
|
8 56 && AF_FIDO2_ROOT="true" || AF_FIDO2_ROOT="false"
|
|
fi
|
|
|
|
dialog --backtitle "$BACKTITLE" \
|
|
--title " FIDO2 User Login " \
|
|
--yesno "\n Enable FIDO2 hardware key for user login (PAM)?\n" \
|
|
8 56 && AF_FIDO2_USER="true" || AF_FIDO2_USER="false"
|
|
|
|
# ── Run TUI installer after base install ──────────────────────────────────────
|
|
dialog --backtitle "$BACKTITLE" \
|
|
--title " Dotfiles Setup " \
|
|
--yesno "\n Automatically run the dotfiles TUI installer\n inside the chroot after base install completes?\n" \
|
|
9 58 && AF_RUN_TUI="true" || AF_RUN_TUI="false"
|
|
|
|
# ═══════════════════════════════════════════════════════════════
|
|
# PART 2 — Dotfiles / TUI installer options
|
|
# (only shown if run_tui is true)
|
|
# ═══════════════════════════════════════════════════════════════
|
|
AF_COMPONENTS=""
|
|
AF_DE="none"
|
|
AF_APPS=""
|
|
AF_COLOR_TEXT=""
|
|
AF_COLOR_BG=""
|
|
AF_COLOR_HIGHLIGHT=""
|
|
AF_COLOR_DARK=""
|
|
AF_COLOR_RED=""
|
|
|
|
if [[ "$AF_RUN_TUI" == "true" ]]; then
|
|
|
|
# ── Components ────────────────────────────────────────────────────────────
|
|
AF_COMPONENTS=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Select Components " \
|
|
--checklist "Space toggles · Enter confirms · Esc quits" 15 68 4 \
|
|
"pkg" "Package managers yay · nvm · rust" on \
|
|
"core" "Core packages 100+ base system packages" on \
|
|
"svc" "Core services NetworkManager · cronie · fail2ban" on \
|
|
"shell" "Shell setup zsh · nvim · yazi · micro · starship" on \
|
|
3>&1 1>&2 2>&3) || AF_COMPONENTS=""
|
|
|
|
# ── DE ────────────────────────────────────────────────────────────────────
|
|
AF_DE=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Desktop Environment " \
|
|
--menu "Select a desktop environment · Esc / none to skip:" 20 70 8 \
|
|
"hyprland" "Hyprland — Wayland WM, full setup (primary)" \
|
|
"sway" "Sway — Wayland tiling WM" \
|
|
"kde-plasma" "KDE Plasma — feature-rich Wayland/X11 DE" \
|
|
"gnome" "GNOME — modern Wayland DE" \
|
|
"cosmic" "COSMIC — Rust-built Wayland DE (System76)" \
|
|
"xfce" "XFCE — lightweight X11 DE" \
|
|
"lxqt" "LXQt — lightweight Qt X11 DE" \
|
|
"none" "Skip DE installation" \
|
|
3>&1 1>&2 2>&3) || AF_DE="none"
|
|
|
|
# ── Apps ──────────────────────────────────────────────────────────────────
|
|
AF_APPS=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Applications " \
|
|
--checklist "Optional applications — installed after base components:" 40 76 32 \
|
|
"ollama" "Ollama local LLM runner + API server" off \
|
|
"llama-cpp" "llama.cpp standalone inference CLI + server" off \
|
|
"open-webui" "Open WebUI browser UI for Ollama / LLM backends" off \
|
|
"claude" "Claude Code Anthropic CLI via npm" off \
|
|
"networking-cli" "Networking CLI nmap · nethogs · mitmproxy · httpie" off \
|
|
"disk-recovery" "Disk Recovery ddrescue · f3" off \
|
|
"himalaya" "Himalaya terminal email client (AUR)" off \
|
|
"gnuplot" "Gnuplot scientific plotting" off \
|
|
"povray" "POV-Ray ray-tracing renderer" off \
|
|
"blender" "Blender 3D creation suite" off \
|
|
"toot" "toot Mastodon CLI client (AUR)" off \
|
|
"db-clients" "DB Clients pgcli · mycli" off \
|
|
"mysql" "MySQL / MariaDB mariadb server + setup" off \
|
|
"productivity" "Productivity taskwarrior · watson · jrnl" off \
|
|
"yt-dlp" "yt-dlp YouTube / media downloader" off \
|
|
"sox" "SoX audio processing toolkit" off \
|
|
"imagemagick" "ImageMagick image manipulation" off \
|
|
"ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \
|
|
"localtunnel" "LocalTunnel expose localhost via tunnel" off \
|
|
"butter" "butter btrfs snapshot backup (AUR)" off \
|
|
"tlp" "TLP laptop power management" off \
|
|
"steam" "Steam gaming platform" off \
|
|
"vesktop" "Vesktop Discord + Vencord theme" off \
|
|
"spotify" "Spotify launcher + Spicetify theming" off \
|
|
"prism" "PrismLauncher Minecraft launcher (Flatpak)" off \
|
|
"vintagestory" "Vintage Story survival game (AUR)" off \
|
|
"localsend" "LocalSend LAN file transfer (AUR)" off \
|
|
"croc" "croc cross-platform file transfer" off \
|
|
"onlyoffice" "OnlyOffice office suite (AUR)" off \
|
|
"wireshark" "Wireshark network packet analyser (GUI)" off \
|
|
"k8s" "Kubernetes tools kubectl · podman-desktop" off \
|
|
"docker" "Docker docker · docker-compose" off \
|
|
"podman" "Podman rootless containers · buildah" off \
|
|
"cockpit" "Cockpit web UI · machines · podman" off \
|
|
"ssh-server" "SSH server openssh · key-auth · enabled" off \
|
|
"freeipa-client" "FreeIPA Client sssd + ipa-client-install + enrollment" off \
|
|
"freeipa-server" "FreeIPA Server interactive server setup + client gen" off \
|
|
"freeipa-image" "FreeIPA Image OCI/LXC/Proxmox LXC builder + Keycloak" off \
|
|
"python" "Python tools pyright · pipx · pynvim" off \
|
|
"zfs" "ZFS zfs-dkms kernel module" off \
|
|
"wprs" "WPRS wprs-git (AUR)" off \
|
|
"chromium" "Chromium open-source browser (official)" off \
|
|
"firefox-browser" "Firefox Mozilla browser (official)" off \
|
|
"zen-browser" "Zen Browser Firefox-based privacy browser (AUR)" off \
|
|
"nyxt" "Nyxt keyboard-driven browser (AUR)" off \
|
|
"librewolf" "LibreWolf hardened Firefox fork (AUR)" off \
|
|
"min-browser" "Min minimal Electron browser (AUR)" off \
|
|
"vscodium" "VSCodium telemetry-free VS Code (AUR)" off \
|
|
"zed-ide" "Zed high-performance Rust IDE (official)" off \
|
|
"geany" "Geany lightweight IDE + plugins (official)" off \
|
|
"codeblocks" "Code::Blocks C/C++ IDE (official)" off \
|
|
"kate" "Kate KDE advanced text editor (official)" off \
|
|
3>&1 1>&2 2>&3) || AF_APPS=""
|
|
|
|
# ── Colorway ──────────────────────────────────────────────────────────────
|
|
# Read defaults from repo colors.conf
|
|
declare -A _cdef
|
|
if [[ -f "$DOTFILES_DIR/colors.conf" ]]; then
|
|
while IFS='=' read -r k v; do
|
|
k="${k%%[[:space:]]*}"
|
|
[[ "$k" =~ ^[[:space:]]*# || -z "$k" ]] && continue
|
|
v="${v%%#*}"; v="${v//[[:space:]]/}"; v="${v^^}"
|
|
_cdef[$k]="$v"
|
|
done < "$DOTFILES_DIR/colors.conf"
|
|
fi
|
|
DEF_TEXT="${_cdef[COLOR_TEXT]:-D6ABAB}"
|
|
DEF_BG="${_cdef[COLOR_BG]:-1A1A1A}"
|
|
DEF_HIGHLIGHT="${_cdef[COLOR_HIGHLIGHT]:-E40046}"
|
|
DEF_DARK="${_cdef[COLOR_DARK]:-5018DD}"
|
|
DEF_RED="${_cdef[COLOR_RED]:-F50505}"
|
|
|
|
COLORWAY_RAW=$(dialog --backtitle "$BACKTITLE" \
|
|
--title " Colorway (optional) " \
|
|
--form "\n Customize theme colors — bare 6-digit hex, no #.\n Leave unchanged to omit colors from answerfile.\n" \
|
|
16 62 5 \
|
|
"COLOR_TEXT " 1 1 "$DEF_TEXT" 1 18 10 7 \
|
|
"COLOR_BG " 2 1 "$DEF_BG" 2 18 10 7 \
|
|
"COLOR_HIGHLIGHT " 3 1 "$DEF_HIGHLIGHT" 3 18 10 7 \
|
|
"COLOR_DARK " 4 1 "$DEF_DARK" 4 18 10 7 \
|
|
"COLOR_RED " 5 1 "$DEF_RED" 5 18 10 7 \
|
|
3>&1 1>&2 2>&3) || COLORWAY_RAW=""
|
|
|
|
if [[ -n "$COLORWAY_RAW" ]]; then
|
|
mapfile -t _cv <<< "$COLORWAY_RAW"
|
|
N_TEXT="${_cv[0]:-$DEF_TEXT}"
|
|
N_BG="${_cv[1]:-$DEF_BG}"
|
|
N_HIGHLIGHT="${_cv[2]:-$DEF_HIGHLIGHT}"
|
|
N_DARK="${_cv[3]:-$DEF_DARK}"
|
|
N_RED="${_cv[4]:-$DEF_RED}"
|
|
# Only save colors if any differ from defaults
|
|
if [[ "${N_TEXT^^}" != "$DEF_TEXT" || \
|
|
"${N_BG^^}" != "$DEF_BG" || \
|
|
"${N_HIGHLIGHT^^}" != "$DEF_HIGHLIGHT" || \
|
|
"${N_DARK^^}" != "$DEF_DARK" || \
|
|
"${N_RED^^}" != "$DEF_RED" ]]; then
|
|
AF_COLOR_TEXT="${N_TEXT^^}"
|
|
AF_COLOR_BG="${N_BG^^}"
|
|
AF_COLOR_HIGHLIGHT="${N_HIGHLIGHT^^}"
|
|
AF_COLOR_DARK="${N_DARK^^}"
|
|
AF_COLOR_RED="${N_RED^^}"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# ── Confirmation ──────────────────────────────────────────────────────────────
|
|
SUMMARY=""
|
|
[[ -n "$AF_DRIVE" ]] && SUMMARY+=" Drive: $AF_DRIVE\n"
|
|
[[ -n "$AF_KERNEL" ]] && SUMMARY+=" Kernel: $AF_KERNEL\n"
|
|
[[ -n "$AF_HOSTNAME" ]] && SUMMARY+=" Hostname: $AF_HOSTNAME (+ MAC suffix at deploy)\n"
|
|
[[ -n "$AF_USERNAME" ]] && SUMMARY+=" Username: $AF_USERNAME\n"
|
|
SUMMARY+=" Encrypt: $AF_ENCRYPT\n"
|
|
SUMMARY+=" FIDO2 root: $AF_FIDO2_ROOT / FIDO2 user: $AF_FIDO2_USER\n"
|
|
SUMMARY+=" Run TUI: $AF_RUN_TUI\n"
|
|
[[ -n "$AF_DE" && "$AF_DE" != "none" ]] && SUMMARY+=" DE: $AF_DE\n"
|
|
[[ -n "$AF_COLOR_TEXT" ]] && SUMMARY+=" Colors: custom\n"
|
|
|
|
dialog --backtitle "$BACKTITLE" \
|
|
--title " Confirm " \
|
|
--yesno "\n Save answerfile with these settings:\n\n${SUMMARY}\n Output: $OUTPUT\n\n Proceed?" \
|
|
22 66 || { clear; echo "Aborted."; exit 0; }
|
|
|
|
# ── Build JSON arrays from space-separated dialog output ──────────────────────
|
|
_words_to_json_array() {
|
|
local input="$1"
|
|
local first=1
|
|
printf '['
|
|
for w in $input; do
|
|
[[ $first -eq 0 ]] && printf ','
|
|
printf '"%s"' "$w"
|
|
first=0
|
|
done
|
|
printf ']'
|
|
}
|
|
|
|
# ── Write answerfile ──────────────────────────────────────────────────────────
|
|
mkdir -p "$(dirname "$OUTPUT")"
|
|
|
|
{
|
|
printf '{\n'
|
|
printf ' "_generated": "%s",\n' "$(date -Iseconds)"
|
|
printf ' "drive": %s,\n' "$(json_str "$AF_DRIVE")"
|
|
printf ' "kernel": %s,\n' "$(json_str "$AF_KERNEL")"
|
|
printf ' "hostname": %s,\n' "$(json_str "$AF_HOSTNAME")"
|
|
printf ' "username": %s,\n' "$(json_str "$AF_USERNAME")"
|
|
printf ' "encrypt": %s,\n' "$AF_ENCRYPT"
|
|
printf ' "fido2_root": %s,\n' "$AF_FIDO2_ROOT"
|
|
printf ' "fido2_user": %s,\n' "$AF_FIDO2_USER"
|
|
printf ' "run_tui": %s,\n' "$AF_RUN_TUI"
|
|
printf ' "components": %s,\n' "$(_words_to_json_array "$AF_COMPONENTS")"
|
|
printf ' "desktop_environment": %s,\n' "$(json_str "$AF_DE")"
|
|
printf ' "apps": %s' "$(_words_to_json_array "$AF_APPS")"
|
|
|
|
if [[ -n "$AF_COLOR_TEXT" ]]; then
|
|
printf ',\n "colors": {\n'
|
|
printf ' "COLOR_TEXT": "%s",\n' "$AF_COLOR_TEXT"
|
|
printf ' "COLOR_BG": "%s",\n' "$AF_COLOR_BG"
|
|
printf ' "COLOR_HIGHLIGHT": "%s",\n' "$AF_COLOR_HIGHLIGHT"
|
|
printf ' "COLOR_DARK": "%s",\n' "$AF_COLOR_DARK"
|
|
printf ' "COLOR_RED": "%s"\n' "$AF_COLOR_RED"
|
|
printf ' }'
|
|
fi
|
|
|
|
printf '\n}\n'
|
|
} > "$OUTPUT"
|
|
|
|
clear
|
|
printf "\n Answerfile saved to: %s\n\n" "$OUTPUT"
|
|
printf " To embed in ISO: bash setup/archiso/build.sh --preconf %s\n\n" "$OUTPUT"
|