From b5a3b46c79870cf4ef8cc8bf8658101dc16b14c4 Mon Sep 17 00:00:00 2001 From: The_miro Date: Mon, 18 May 2026 15:24:47 +0200 Subject: [PATCH] setup: add answerfile system for fully automated installs tui-install.sh: - Reads /answerfile.json if present (ANSWERFILE_MODE) - All dialog selections (components, DE, apps) sourced from file - Hostname from answerfile gets MAC address suffix appended to prevent conflicts when deploying one image to multiple machines - Interactive hostname inputbox added to the normal TUI flow - Colorway dialog added as final step; skipped if no colors differ from defaults and no answerfile colors are set - Answerfile mode: runs non-interactively, logs warnings on failure generate-answerfile.sh (new): - Dry-runs the full installer dialog flow (OS + dotfiles) - Writes selections to ~/answerfile.json (or a given path) - No software is installed; passwords are never written to the file build.sh: - New --preconf [FILE] flag embeds an answerfile into the ISO at /answerfile.json; omitting the flag leaves the ISO clean - Validates JSON with jq if available before embedding - Reworked arg parsing to handle the new flag alongside OUT_DIR Co-Authored-By: Claude Sonnet 4.6 --- setup/archiso/build.sh | 60 +++- setup/generate-answerfile.sh | 377 +++++++++++++++++++++++++ setup/tui-install.sh | 513 +++++++++++++++++++++++------------ 3 files changed, 777 insertions(+), 173 deletions(-) create mode 100644 setup/generate-answerfile.sh diff --git a/setup/archiso/build.sh b/setup/archiso/build.sh index 1df7eac..9e7bdba 100644 --- a/setup/archiso/build.sh +++ b/setup/archiso/build.sh @@ -1,10 +1,48 @@ #!/usr/bin/env bash +# build.sh — build the M-Archy Arch Linux ISO +# +# Usage: +# bash build.sh [--preconf [FILE]] [OUT_DIR] +# +# --preconf Embed ~/answerfile.json into the ISO at /answerfile.json +# --preconf FILE Embed the specified answerfile instead +# OUT_DIR Output directory (default: ~/m-archy-out, or $OUT_DIR env) + set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" DOTFILES_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# ── Argument parsing ─────────────────────────────────────────────────────────── +PRECONF_FILE="" +OUT_ARG="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --preconf) + # Optional next arg: a file path (doesn't start with -) + if [[ $# -gt 1 && "${2:0:1}" != "-" ]]; then + PRECONF_FILE="$2"; shift + else + PRECONF_FILE="$HOME/answerfile.json" + fi + shift + ;; + --preconf=*) + PRECONF_FILE="${1#--preconf=}" + shift + ;; + -*) + echo "Unknown flag: $1" >&2; exit 1 + ;; + *) + OUT_ARG="$1"; shift + ;; + esac +done + WORK_DIR="${WORK_DIR:-$HOME/m-archy-build}" -OUT_DIR="${1:-${OUT_DIR:-$HOME/m-archy-out}}" +OUT_DIR="${OUT_ARG:-${OUT_DIR:-$HOME/m-archy-out}}" PROFILE="$WORK_DIR/profile" RELENG="/usr/share/archiso/configs/releng" @@ -15,6 +53,16 @@ fi [[ -d "$RELENG" ]] || { echo "ERROR: $RELENG not found — is archiso installed?"; exit 1; } +# Validate answerfile early if --preconf was given +if [[ -n "$PRECONF_FILE" ]]; then + [[ -f "$PRECONF_FILE" ]] \ + || { echo "ERROR: answerfile not found: $PRECONF_FILE"; exit 1; } + command -v jq &>/dev/null \ + && jq empty "$PRECONF_FILE" \ + || echo "Warning: jq not available — skipping answerfile JSON validation" + echo "Answerfile to embed: $PRECONF_FILE" +fi + rm -rf "$WORK_DIR" mkdir -p "$WORK_DIR" "$OUT_DIR" @@ -43,9 +91,19 @@ chmod 755 \ "$PROFILE/airootfs/usr/local/bin/install-arch" \ "$PROFILE/airootfs/root/installer/"*.sh +# ── Embed answerfile (--preconf) ─────────────────────────────────────────────── +if [[ -n "$PRECONF_FILE" ]]; then + echo "Embedding answerfile: $PRECONF_FILE → /answerfile.json" + install -m 644 "$PRECONF_FILE" "$PROFILE/airootfs/answerfile.json" +fi + echo "Building ISO (this may take a while)..." sudo mkarchiso -v -w "$WORK_DIR/mkarchiso" -o "$OUT_DIR" "$PROFILE" echo echo "Done." ls -lh "$OUT_DIR/"*.iso 2>/dev/null || true + +if [[ -n "$PRECONF_FILE" ]]; then + echo "Answerfile embedded — automated install will activate on boot." +fi diff --git a/setup/generate-answerfile.sh b/setup/generate-answerfile.sh new file mode 100644 index 0000000..6e05e95 --- /dev/null +++ b/setup/generate-answerfile.sh @@ -0,0 +1,377 @@ +#!/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" diff --git a/setup/tui-install.sh b/setup/tui-install.sh index 39bdebb..133dea8 100755 --- a/setup/tui-install.sh +++ b/setup/tui-install.sh @@ -12,6 +12,10 @@ LOG="$HOME/dotfiles-install.log" TMP_D="$(mktemp -d)" trap 'rm -rf "$TMP_D"' EXIT +ANSWERFILE="${ANSWERFILE:-/answerfile.json}" +ANSWERFILE_MODE=false +[[ -f "$ANSWERFILE" ]] && ANSWERFILE_MODE=true + BACKTITLE="the_miro's Arch Dotfiles" # ── Cyberqueer dialog theme ─────────────────────────────────────────────────── @@ -56,6 +60,12 @@ require_dialog() { 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 @@ -79,10 +89,14 @@ run_module() { bash "$script" 2>&1 | tee -a "$LOG" || rc=${PIPESTATUS[0]} if [[ $rc -ne 0 ]]; then - dialog --backtitle "$BACKTITLE" \ - --title " Module Failed " \ - --yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \ - || { clear; exit 1; } + if [[ $ANSWERFILE_MODE == true ]]; then + printf "\n Warning: %s exited with code %d — continuing.\n" "$label" "$rc" | tee -a "$LOG" + else + dialog --backtitle "$BACKTITLE" \ + --title " Module Failed " \ + --yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \ + || { clear; exit 1; } + fi fi } @@ -148,22 +162,63 @@ count_steps() { [[ "$a" == *"kate"* ]] && TOTAL=$(( TOTAL + 1 )) } +# ── Answerfile ──────────────────────────────────────────────────────────────── +AF_HOSTNAME="" +AF_COMPONENTS="" +AF_DE="none" +AF_APPS="" +AF_COLOR_TEXT="" +AF_COLOR_BG="" +AF_COLOR_HIGHLIGHT="" +AF_COLOR_DARK="" +AF_COLOR_RED="" + +load_answerfile() { + require_jq + AF_HOSTNAME=$(jq -r '.hostname // ""' "$ANSWERFILE") + AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE") + AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE") + AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE") + AF_COLOR_TEXT=$(jq -r '.colors.COLOR_TEXT // ""' "$ANSWERFILE") + AF_COLOR_BG=$(jq -r '.colors.COLOR_BG // ""' "$ANSWERFILE") + AF_COLOR_HIGHLIGHT=$(jq -r '.colors.COLOR_HIGHLIGHT // ""' "$ANSWERFILE") + AF_COLOR_DARK=$(jq -r '.colors.COLOR_DARK // ""' "$ANSWERFILE") + AF_COLOR_RED=$(jq -r '.colors.COLOR_RED // ""' "$ANSWERFILE") +} + +# ── MAC address helper ──────────────────────────────────────────────────────── +get_mac_suffix() { + local mac + mac=$(ip link show 2>/dev/null \ + | awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}') + printf '%s' "${mac//:/}" +} + # ── Preflight ───────────────────────────────────────────────────────────────── [[ $EUID -eq 0 ]] && die "Run as your normal user (not root)." command -v pacman &>/dev/null || die "pacman not found — Arch Linux required." require_dialog +if $ANSWERFILE_MODE; then + load_answerfile + printf "Answerfile mode: %s\n" "$ANSWERFILE" | tee -a "$LOG" +fi + if ! ping -c1 -W3 archlinux.org &>/dev/null; then - dialog --backtitle "$BACKTITLE" \ - --title " No Network Detected " \ - --msgbox "\n No internet connection found.\n\n nmtui will open so you can configure networking.\n Close nmtui when done to continue the installer.\n" 11 58 - nmtui - if ! ping -c1 -W3 archlinux.org &>/dev/null; then + if $ANSWERFILE_MODE; then + printf "Warning: no internet connection detected.\n" | tee -a "$LOG" + else dialog --backtitle "$BACKTITLE" \ - --title " Still Offline " \ - --yesno "\n Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" 11 58 \ - || { clear; echo "Aborted — no network."; exit 1; } + --title " No Network Detected " \ + --msgbox "\n No internet connection found.\n\n nmtui will open so you can configure networking.\n Close nmtui when done to continue the installer.\n" 11 58 + nmtui + if ! ping -c1 -W3 archlinux.org &>/dev/null; then + dialog --backtitle "$BACKTITLE" \ + --title " Still Offline " \ + --yesno "\n Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" 11 58 \ + || { clear; echo "Aborted — no network."; exit 1; } + fi fi fi @@ -171,9 +226,10 @@ fi printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" >> "$LOG" # ── Welcome ─────────────────────────────────────────────────────────────────── -dialog --backtitle "$BACKTITLE" \ - --title " Welcome " \ - --msgbox "\n\ +if ! $ANSWERFILE_MODE; then + dialog --backtitle "$BACKTITLE" \ + --title " Welcome " \ + --msgbox "\n\ the_miro's Arch dotfiles installer\n\ Cyberqueer · Wayland · Hyprland\n\ ─────────────────────────────────────────\n\ @@ -182,159 +238,197 @@ dialog --backtitle "$BACKTITLE" \ \n\ Source: $DOTFILES_DIR\n\ Log: $LOG\n" 14 62 - -# ── Component selection ─────────────────────────────────────────────────────── -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) || { clear; echo "Aborted."; exit 0; } - -# ── DE selection ────────────────────────────────────────────────────────────── -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) || DE="none" - -# ── Apps selection ──────────────────────────────────────────────────────────── -SELECTED_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) || SELECTED_APPS="" - -# ── Confirmation ────────────────────────────────────────────────────────────── -SUMMARY="" -[[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n" -[[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n" -[[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n" -[[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n" -[[ "$DE" != "none" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n" - -if [[ -n "$SELECTED_APPS" ]]; then - SUMMARY+="\n Applications:\n" - [[ "$SELECTED_APPS" == *"ollama"* ]] && SUMMARY+=" ✦ Ollama\n" - [[ "$SELECTED_APPS" == *"llama-cpp"* ]] && SUMMARY+=" ✦ llama.cpp\n" - [[ "$SELECTED_APPS" == *"open-webui"* ]] && SUMMARY+=" ✦ Open WebUI\n" - [[ "$SELECTED_APPS" == *"claude"* ]] && SUMMARY+=" ✦ Claude Code\n" - [[ "$SELECTED_APPS" == *"networking-cli"* ]] && SUMMARY+=" ✦ Networking CLI (nmap, nethogs, mitmproxy, httpie)\n" - [[ "$SELECTED_APPS" == *"disk-recovery"* ]] && SUMMARY+=" ✦ Disk Recovery (ddrescue, f3)\n" - [[ "$SELECTED_APPS" == *"himalaya"* ]] && SUMMARY+=" ✦ Himalaya\n" - [[ "$SELECTED_APPS" == *"gnuplot"* ]] && SUMMARY+=" ✦ Gnuplot\n" - [[ "$SELECTED_APPS" == *"povray"* ]] && SUMMARY+=" ✦ POV-Ray\n" - [[ "$SELECTED_APPS" == *"blender"* ]] && SUMMARY+=" ✦ Blender\n" - [[ "$SELECTED_APPS" == *"toot"* ]] && SUMMARY+=" ✦ toot\n" - [[ "$SELECTED_APPS" == *"db-clients"* ]] && SUMMARY+=" ✦ DB Clients (pgcli, mycli)\n" - [[ "$SELECTED_APPS" == *"mysql"* ]] && SUMMARY+=" ✦ MySQL / MariaDB\n" - [[ "$SELECTED_APPS" == *"productivity"* ]] && SUMMARY+=" ✦ Productivity (taskwarrior, watson, jrnl)\n" - [[ "$SELECTED_APPS" == *"yt-dlp"* ]] && SUMMARY+=" ✦ yt-dlp\n" - [[ "$SELECTED_APPS" == *"sox"* ]] && SUMMARY+=" ✦ SoX\n" - [[ "$SELECTED_APPS" == *"imagemagick"* ]] && SUMMARY+=" ✦ ImageMagick\n" - [[ "$SELECTED_APPS" == *"ffmpeg"* ]] && SUMMARY+=" ✦ FFmpeg extras\n" - [[ "$SELECTED_APPS" == *"localtunnel"* ]] && SUMMARY+=" ✦ LocalTunnel\n" - [[ "$SELECTED_APPS" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n" - [[ "$SELECTED_APPS" == *"tlp"* ]] && SUMMARY+=" ✦ TLP\n" - [[ "$SELECTED_APPS" == *"steam"* ]] && SUMMARY+=" ✦ Steam\n" - [[ "$SELECTED_APPS" == *"vesktop"* ]] && SUMMARY+=" ✦ Vesktop + Vencord theme\n" - [[ "$SELECTED_APPS" == *"spotify"* ]] && SUMMARY+=" ✦ Spotify + Spicetify\n" - [[ "$SELECTED_APPS" == *"prism"* ]] && SUMMARY+=" ✦ PrismLauncher\n" - [[ "$SELECTED_APPS" == *"vintagestory"* ]] && SUMMARY+=" ✦ Vintage Story\n" - [[ "$SELECTED_APPS" == *"localsend"* ]] && SUMMARY+=" ✦ LocalSend\n" - [[ "$SELECTED_APPS" == *"croc"* ]] && SUMMARY+=" ✦ croc\n" - [[ "$SELECTED_APPS" == *"onlyoffice"* ]] && SUMMARY+=" ✦ OnlyOffice\n" - [[ "$SELECTED_APPS" == *"wireshark"* ]] && SUMMARY+=" ✦ Wireshark\n" - [[ "$SELECTED_APPS" == *"k8s"* ]] && SUMMARY+=" ✦ Kubernetes tools\n" - [[ "$SELECTED_APPS" == *"docker"* ]] && SUMMARY+=" ✦ Docker + Compose\n" - [[ "$SELECTED_APPS" == *"podman"* ]] && SUMMARY+=" ✦ Podman (rootless) + Buildah\n" - [[ "$SELECTED_APPS" == *"cockpit"* ]] && SUMMARY+=" ✦ Cockpit web UI\n" - [[ "$SELECTED_APPS" == *"ssh-server"* ]] && SUMMARY+=" ✦ SSH server (openssh, key auth)\n" - [[ "$SELECTED_APPS" == *"freeipa-client"* ]] && SUMMARY+=" ✦ FreeIPA Client\n" - [[ "$SELECTED_APPS" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n" - [[ "$SELECTED_APPS" == *"freeipa-image"* ]] && SUMMARY+=" ✦ FreeIPA Image Builder\n" - [[ "$SELECTED_APPS" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n" - [[ "$SELECTED_APPS" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n" - [[ "$SELECTED_APPS" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n" - [[ "$SELECTED_APPS" == *"chromium"* ]] && SUMMARY+=" ✦ Chromium\n" - [[ "$SELECTED_APPS" == *"firefox-browser"* ]] && SUMMARY+=" ✦ Firefox\n" - [[ "$SELECTED_APPS" == *"zen-browser"* ]] && SUMMARY+=" ✦ Zen Browser\n" - [[ "$SELECTED_APPS" == *"nyxt"* ]] && SUMMARY+=" ✦ Nyxt\n" - [[ "$SELECTED_APPS" == *"librewolf"* ]] && SUMMARY+=" ✦ LibreWolf\n" - [[ "$SELECTED_APPS" == *"min-browser"* ]] && SUMMARY+=" ✦ Min Browser\n" - [[ "$SELECTED_APPS" == *"vscodium"* ]] && SUMMARY+=" ✦ VSCodium\n" - [[ "$SELECTED_APPS" == *"zed-ide"* ]] && SUMMARY+=" ✦ Zed IDE\n" - [[ "$SELECTED_APPS" == *"geany"* ]] && SUMMARY+=" ✦ Geany\n" - [[ "$SELECTED_APPS" == *"codeblocks"* ]] && SUMMARY+=" ✦ Code::Blocks\n" - [[ "$SELECTED_APPS" == *"kate"* ]] && SUMMARY+=" ✦ Kate\n" fi -dialog --backtitle "$BACKTITLE" \ - --title " Confirm Installation " \ - --yesno "\n Components to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \ - 24 62 || { clear; echo "Aborted."; exit 0; } +# ── Hostname ────────────────────────────────────────────────────────────────── +HOSTNAME_SET="" +if $ANSWERFILE_MODE; then + if [[ -n "$AF_HOSTNAME" ]]; then + MAC=$(get_mac_suffix) + HOSTNAME_SET="${AF_HOSTNAME}-${MAC}" + printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG" + fi +else + HOSTNAME_INPUT=$(dialog --backtitle "$BACKTITLE" \ + --title " Hostname " \ + --inputbox "\n Hostname for this machine (leave blank to keep default).\n" 9 54 "" \ + 3>&1 1>&2 2>&3) || HOSTNAME_INPUT="" + HOSTNAME_SET="$HOSTNAME_INPUT" +fi + +if [[ -n "$HOSTNAME_SET" ]]; then + 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 + +# ── Component selection ─────────────────────────────────────────────────────── +if $ANSWERFILE_MODE; then + COMPONENTS="$AF_COMPONENTS" +else + 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) || { clear; echo "Aborted."; exit 0; } +fi + +# ── DE selection ────────────────────────────────────────────────────────────── +if $ANSWERFILE_MODE; then + DE="$AF_DE" +else + 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) || DE="none" +fi + +# ── Apps selection ──────────────────────────────────────────────────────────── +if $ANSWERFILE_MODE; then + SELECTED_APPS="$AF_APPS" +else + SELECTED_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) || SELECTED_APPS="" +fi + +# ── Confirmation (interactive mode only) ────────────────────────────────────── +if ! $ANSWERFILE_MODE; then + 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" + [[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n" + [[ "$DE" != "none" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n" + + if [[ -n "$SELECTED_APPS" ]]; then + SUMMARY+="\n Applications:\n" + [[ "$SELECTED_APPS" == *"ollama"* ]] && SUMMARY+=" ✦ Ollama\n" + [[ "$SELECTED_APPS" == *"llama-cpp"* ]] && SUMMARY+=" ✦ llama.cpp\n" + [[ "$SELECTED_APPS" == *"open-webui"* ]] && SUMMARY+=" ✦ Open WebUI\n" + [[ "$SELECTED_APPS" == *"claude"* ]] && SUMMARY+=" ✦ Claude Code\n" + [[ "$SELECTED_APPS" == *"networking-cli"* ]] && SUMMARY+=" ✦ Networking CLI (nmap, nethogs, mitmproxy, httpie)\n" + [[ "$SELECTED_APPS" == *"disk-recovery"* ]] && SUMMARY+=" ✦ Disk Recovery (ddrescue, f3)\n" + [[ "$SELECTED_APPS" == *"himalaya"* ]] && SUMMARY+=" ✦ Himalaya\n" + [[ "$SELECTED_APPS" == *"gnuplot"* ]] && SUMMARY+=" ✦ Gnuplot\n" + [[ "$SELECTED_APPS" == *"povray"* ]] && SUMMARY+=" ✦ POV-Ray\n" + [[ "$SELECTED_APPS" == *"blender"* ]] && SUMMARY+=" ✦ Blender\n" + [[ "$SELECTED_APPS" == *"toot"* ]] && SUMMARY+=" ✦ toot\n" + [[ "$SELECTED_APPS" == *"db-clients"* ]] && SUMMARY+=" ✦ DB Clients (pgcli, mycli)\n" + [[ "$SELECTED_APPS" == *"mysql"* ]] && SUMMARY+=" ✦ MySQL / MariaDB\n" + [[ "$SELECTED_APPS" == *"productivity"* ]] && SUMMARY+=" ✦ Productivity (taskwarrior, watson, jrnl)\n" + [[ "$SELECTED_APPS" == *"yt-dlp"* ]] && SUMMARY+=" ✦ yt-dlp\n" + [[ "$SELECTED_APPS" == *"sox"* ]] && SUMMARY+=" ✦ SoX\n" + [[ "$SELECTED_APPS" == *"imagemagick"* ]] && SUMMARY+=" ✦ ImageMagick\n" + [[ "$SELECTED_APPS" == *"ffmpeg"* ]] && SUMMARY+=" ✦ FFmpeg extras\n" + [[ "$SELECTED_APPS" == *"localtunnel"* ]] && SUMMARY+=" ✦ LocalTunnel\n" + [[ "$SELECTED_APPS" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n" + [[ "$SELECTED_APPS" == *"tlp"* ]] && SUMMARY+=" ✦ TLP\n" + [[ "$SELECTED_APPS" == *"steam"* ]] && SUMMARY+=" ✦ Steam\n" + [[ "$SELECTED_APPS" == *"vesktop"* ]] && SUMMARY+=" ✦ Vesktop + Vencord theme\n" + [[ "$SELECTED_APPS" == *"spotify"* ]] && SUMMARY+=" ✦ Spotify + Spicetify\n" + [[ "$SELECTED_APPS" == *"prism"* ]] && SUMMARY+=" ✦ PrismLauncher\n" + [[ "$SELECTED_APPS" == *"vintagestory"* ]] && SUMMARY+=" ✦ Vintage Story\n" + [[ "$SELECTED_APPS" == *"localsend"* ]] && SUMMARY+=" ✦ LocalSend\n" + [[ "$SELECTED_APPS" == *"croc"* ]] && SUMMARY+=" ✦ croc\n" + [[ "$SELECTED_APPS" == *"onlyoffice"* ]] && SUMMARY+=" ✦ OnlyOffice\n" + [[ "$SELECTED_APPS" == *"wireshark"* ]] && SUMMARY+=" ✦ Wireshark\n" + [[ "$SELECTED_APPS" == *"k8s"* ]] && SUMMARY+=" ✦ Kubernetes tools\n" + [[ "$SELECTED_APPS" == *"docker"* ]] && SUMMARY+=" ✦ Docker + Compose\n" + [[ "$SELECTED_APPS" == *"podman"* ]] && SUMMARY+=" ✦ Podman (rootless) + Buildah\n" + [[ "$SELECTED_APPS" == *"cockpit"* ]] && SUMMARY+=" ✦ Cockpit web UI\n" + [[ "$SELECTED_APPS" == *"ssh-server"* ]] && SUMMARY+=" ✦ SSH server (openssh, key auth)\n" + [[ "$SELECTED_APPS" == *"freeipa-client"* ]] && SUMMARY+=" ✦ FreeIPA Client\n" + [[ "$SELECTED_APPS" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n" + [[ "$SELECTED_APPS" == *"freeipa-image"* ]] && SUMMARY+=" ✦ FreeIPA Image Builder\n" + [[ "$SELECTED_APPS" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n" + [[ "$SELECTED_APPS" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n" + [[ "$SELECTED_APPS" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n" + [[ "$SELECTED_APPS" == *"chromium"* ]] && SUMMARY+=" ✦ Chromium\n" + [[ "$SELECTED_APPS" == *"firefox-browser"* ]] && SUMMARY+=" ✦ Firefox\n" + [[ "$SELECTED_APPS" == *"zen-browser"* ]] && SUMMARY+=" ✦ Zen Browser\n" + [[ "$SELECTED_APPS" == *"nyxt"* ]] && SUMMARY+=" ✦ Nyxt\n" + [[ "$SELECTED_APPS" == *"librewolf"* ]] && SUMMARY+=" ✦ LibreWolf\n" + [[ "$SELECTED_APPS" == *"min-browser"* ]] && SUMMARY+=" ✦ Min Browser\n" + [[ "$SELECTED_APPS" == *"vscodium"* ]] && SUMMARY+=" ✦ VSCodium\n" + [[ "$SELECTED_APPS" == *"zed-ide"* ]] && SUMMARY+=" ✦ Zed IDE\n" + [[ "$SELECTED_APPS" == *"geany"* ]] && SUMMARY+=" ✦ Geany\n" + [[ "$SELECTED_APPS" == *"codeblocks"* ]] && SUMMARY+=" ✦ Code::Blocks\n" + [[ "$SELECTED_APPS" == *"kate"* ]] && SUMMARY+=" ✦ Kate\n" + fi + + dialog --backtitle "$BACKTITLE" \ + --title " Confirm Installation " \ + --yesno "\n Components to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \ + 24 62 || { clear; echo "Aborted."; exit 0; } +fi count_steps "$COMPONENTS" "$DE" "$SELECTED_APPS" @@ -410,10 +504,85 @@ fi [[ "$SELECTED_APPS" == *"codeblocks"* ]] && run_module "Code::Blocks" "$APPS/codeblocks.sh" [[ "$SELECTED_APPS" == *"kate"* ]] && run_module "Kate" "$APPS/kate.sh" -# ── Done ────────────────────────────────────────────────────────────────────── -dialog --backtitle "$BACKTITLE" \ - --title " Done " \ - --msgbox "\n All selected components installed.\n\n Log: $LOG\n\n A reboot may be required for all changes to take effect.\n" 12 58 +# ── Colorway (final step) ───────────────────────────────────────────────────── +# Read defaults from repo colors.conf for pre-population +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}" -clear -printf "\n Done. Log: %s\n\n" "$LOG" +_write_colors_conf() { + local out="$1" t="$2" b="$3" h="$4" d="$5" r="$6" + printf 'COLOR_TEXT=%s\nCOLOR_BG=%s\nCOLOR_HIGHLIGHT=%s\nCOLOR_DARK=%s\nCOLOR_RED=%s\n' \ + "${t^^}" "${b^^}" "${h^^}" "${d^^}" "${r^^}" > "$out" +} + +if $ANSWERFILE_MODE; then + # Apply colors from answerfile if any are set + if [[ -n "$AF_COLOR_TEXT$AF_COLOR_BG$AF_COLOR_HIGHLIGHT$AF_COLOR_DARK$AF_COLOR_RED" ]]; then + TMP_COLORS="$TMP_D/colors.conf" + _write_colors_conf "$TMP_COLORS" \ + "${AF_COLOR_TEXT:-$DEF_TEXT}" \ + "${AF_COLOR_BG:-$DEF_BG}" \ + "${AF_COLOR_HIGHLIGHT:-$DEF_HIGHLIGHT}" \ + "${AF_COLOR_DARK:-$DEF_DARK}" \ + "${AF_COLOR_RED:-$DEF_RED}" + printf "Applying colorway from answerfile...\n" | tee -a "$LOG" + bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true + fi +else + # Interactive: show color form dialog + COLORWAY_RAW=$(dialog --backtitle "$BACKTITLE" \ + --title " Colorway (optional) " \ + --form "\n Customize theme colors — bare 6-digit hex, no #.\n Leave unchanged to skip colorway setup.\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}" + + if [[ "${N_TEXT^^}" != "$DEF_TEXT" || \ + "${N_BG^^}" != "$DEF_BG" || \ + "${N_HIGHLIGHT^^}" != "$DEF_HIGHLIGHT" || \ + "${N_DARK^^}" != "$DEF_DARK" || \ + "${N_RED^^}" != "$DEF_RED" ]]; then + TMP_COLORS="$TMP_D/colors.conf" + _write_colors_conf "$TMP_COLORS" "$N_TEXT" "$N_BG" "$N_HIGHLIGHT" "$N_DARK" "$N_RED" + clear + printf "\n\033[1;35m Applying colorway...\033[0m\n\n" + bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true + fi + fi +fi + +# ── Done ────────────────────────────────────────────────────────────────────── +if $ANSWERFILE_MODE; then + printf "\nDone. Log: %s\n" "$LOG" +else + dialog --backtitle "$BACKTITLE" \ + --title " Done " \ + --msgbox "\n All selected components installed.\n\n Log: $LOG\n\n A reboot may be required for all changes to take effect.\n" 12 58 + + clear + printf "\n Done. Log: %s\n\n" "$LOG" +fi