refactor(tui-install): replace dialog with plain-bash CLI prompts
Drop the dialog dependency entirely so the installer runs on a bare console with only bash + coreutils. Reimplement the needed widgets (msgbox, yesno, input, menu, checklist, form) as ui_* helpers using read, preserving the cyberqueer magenta/cyan palette via ANSI codes and the stdout/stderr fd convention so existing capture sites work unchanged. Update generate-modules.sh to emit the ui_checklist form. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>main
parent
0ab535f772
commit
1354dc4fd1
|
|
@ -60,19 +60,19 @@ for i in "${ACTIVE_IDS[@]}"; do
|
||||||
done
|
done
|
||||||
|
|
||||||
# -- module-checklist (tui-install.sh and generate-answerfile.sh) -------------
|
# -- module-checklist (tui-install.sh and generate-answerfile.sh) -------------
|
||||||
# Builds the full SELECTED_APPS=$(dialog ...) call including open/close.
|
# Builds the full SELECTED_APPS=$(ui_checklist ...) call including open/close.
|
||||||
|
# ui_checklist (defined in tui-install.sh) is a plain-bash replacement for the
|
||||||
|
# `dialog --checklist` widget; it takes the same tag/desc/state triplets.
|
||||||
build_checklist_tui() {
|
build_checklist_tui() {
|
||||||
local out
|
local out
|
||||||
out=' SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \\\n'
|
out=' SELECTED_APPS=$(ui_checklist " Applications " "Optional applications — installed after base components:" \\\n'
|
||||||
out+=' --title " Applications " \\\n'
|
|
||||||
out+=' --checklist "Optional applications — installed after base components:" "$_APP_H" 76 "$_APP_LIST_H" \\\n'
|
|
||||||
for i in "${ACTIVE_IDS[@]}"; do
|
for i in "${ACTIVE_IDS[@]}"; do
|
||||||
local id="${M_IDS[$i]}" desc="${M_DESCS[$i]}" def="${M_DEFAULTS[$i]}"
|
local id="${M_IDS[$i]}" desc="${M_DESCS[$i]}" def="${M_DEFAULTS[$i]}"
|
||||||
local padded
|
local padded
|
||||||
padded=$(printf '%-20s' "$id")
|
padded=$(printf '%-20s' "$id")
|
||||||
out+=" \"${id}\" \"${padded} ${desc}\" ${def} \\\\\n"
|
out+=" \"${id}\" \"${padded} ${desc}\" ${def} \\\\\n"
|
||||||
done
|
done
|
||||||
out+=' 3>&1 1>&2 2>&3) || SELECTED_APPS=""\n'
|
out+=' ) || SELECTED_APPS=""\n'
|
||||||
printf '%s' "$out"
|
printf '%s' "$out"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,49 +30,165 @@ ANSWERFILE_MODE=false
|
||||||
# Enable unattended mode only when the answerfile is actually present on disk.
|
# Enable unattended mode only when the answerfile is actually present on disk.
|
||||||
[[ -f "$ANSWERFILE" ]] && ANSWERFILE_MODE=true
|
[[ -f "$ANSWERFILE" ]] && ANSWERFILE_MODE=true
|
||||||
|
|
||||||
BACKTITLE="the_miro's Arch Dotfiles"
|
# ── Cyberqueer CLI palette ────────────────────────────────────────────────────
|
||||||
|
# Plain ANSI escape codes used by the prompt helpers below. These keep the
|
||||||
|
# magenta/cyan "cyberqueer" look without depending on the `dialog` binary, so
|
||||||
|
# the installer runs on a bare console with nothing but bash + coreutils.
|
||||||
|
# tput is not used here because it may be unavailable on a minimal TTY.
|
||||||
|
C_RESET=$'\033[0m'
|
||||||
|
C_TITLE=$'\033[1;35m' # bold magenta — section headers
|
||||||
|
C_RULE=$'\033[35m' # magenta — separators
|
||||||
|
C_ACCENT=$'\033[36m' # cyan — prompts and selected marks
|
||||||
|
C_BOLD=$'\033[1m'
|
||||||
|
C_DIM=$'\033[2m'
|
||||||
|
|
||||||
# ── Terminal dimensions (bare console safe) ───────────────────────────────────
|
# ── Plain-CLI prompt helpers ──────────────────────────────────────────────────
|
||||||
# tput may fail on a bare TTY with no TERM set; fall back to safe 80×24 defaults
|
# These reimplement the subset of `dialog` widgets the installer needs using
|
||||||
# so dialog sizing arithmetic never operates on empty strings.
|
# only `read`. Widgets that return a value (input/menu/checklist/form) print
|
||||||
TERM_H=$(tput lines 2>/dev/null || echo 24)
|
# their UI to stderr and the chosen value(s) to stdout, mirroring dialog's
|
||||||
TERM_W=$(tput cols 2>/dev/null || echo 80)
|
# `3>&1 1>&2 2>&3` fd-swap convention so existing `$(...)` capture sites work
|
||||||
|
# unchanged. All interactive reads come from /dev/tty so the helpers keep
|
||||||
|
# working even when stdin is redirected.
|
||||||
|
|
||||||
# ── Cyberqueer dialog theme ───────────────────────────────────────────────────
|
ui_header() {
|
||||||
# dialog reads its color scheme from $DIALOGRC at startup.
|
# Styled section header printed to the given fd (default stdout).
|
||||||
# Writing it to TMP_D (rather than ~/.dialogrc) avoids permanently altering
|
printf '\n%s──── %s ────%s\n\n' "$C_TITLE" "$1" "$C_RESET"
|
||||||
# the user's existing dialog configuration on the live system.
|
}
|
||||||
export DIALOGRC="$TMP_D/dialogrc"
|
|
||||||
# The heredoc uses single-quoted 'EOF' so no variable expansion happens inside;
|
ui_msgbox() {
|
||||||
# every value is a literal dialog color tuple: (FG, BG, BOLD).
|
# Informational box: print title + body, wait for Enter to acknowledge.
|
||||||
cat > "$DIALOGRC" <<'EOF'
|
local title="$1" body="$2"
|
||||||
use_shadow = ON
|
ui_header "$title"
|
||||||
use_colors = ON
|
printf '%b\n' "$body"
|
||||||
screen_color = (BLACK,BLACK,ON)
|
printf '\n%s Press Enter to continue…%s' "$C_DIM" "$C_RESET"
|
||||||
shadow_color = (BLACK,BLACK,ON)
|
read -r _ </dev/tty || true
|
||||||
title_color = (MAGENTA,BLACK,ON)
|
printf '\n'
|
||||||
border_color = (MAGENTA,BLACK,ON)
|
}
|
||||||
button_active_color = (BLACK,MAGENTA,ON)
|
|
||||||
button_inactive_color = (WHITE,BLACK,OFF)
|
ui_yesno() {
|
||||||
button_key_active_color = (BLACK,CYAN,ON)
|
# Confirmation prompt. Returns 0 for yes, 1 for no.
|
||||||
button_key_inactive_color = (CYAN,BLACK,ON)
|
# $3 sets the default applied on a bare Enter ("yes" or "no").
|
||||||
button_label_active_color = (BLACK,MAGENTA,ON)
|
local title="$1" body="$2" def="${3:-yes}" ans hint="[Y/n]"
|
||||||
button_label_inactive_color = (WHITE,BLACK,OFF)
|
[[ "$def" == no ]] && hint="[y/N]"
|
||||||
inputbox_color = (WHITE,BLACK,OFF)
|
ui_header "$title"
|
||||||
inputbox_border_color = (MAGENTA,BLACK,ON)
|
printf '%b\n' "$body"
|
||||||
menubox_color = (WHITE,BLACK,OFF)
|
while true; do
|
||||||
menubox_border_color = (MAGENTA,BLACK,ON)
|
printf '\n%s %s %s%s ' "$C_ACCENT" "Confirm?" "$hint" "$C_RESET"
|
||||||
item_color = (WHITE,BLACK,OFF)
|
read -r ans </dev/tty || ans=""
|
||||||
item_selected_color = (BLACK,MAGENTA,ON)
|
ans="${ans,,}"; [[ -z "$ans" ]] && ans="$def"
|
||||||
tag_color = (CYAN,BLACK,ON)
|
case "$ans" in
|
||||||
tag_selected_color = (BLACK,CYAN,ON)
|
y|yes) return 0 ;;
|
||||||
tag_key_color = (CYAN,BLACK,ON)
|
n|no) return 1 ;;
|
||||||
tag_key_selected_color = (BLACK,CYAN,ON)
|
esac
|
||||||
check_color = (WHITE,BLACK,OFF)
|
done
|
||||||
check_selected_color = (BLACK,MAGENTA,ON)
|
}
|
||||||
uarrow_color = (MAGENTA,BLACK,ON)
|
|
||||||
darrow_color = (MAGENTA,BLACK,ON)
|
ui_input() {
|
||||||
EOF
|
# Single-line text entry. Echoes the entered value (or default) to stdout.
|
||||||
|
local title="$1" prompt="$2" def="${3:-}" val
|
||||||
|
{
|
||||||
|
ui_header "$title"
|
||||||
|
printf '%b\n' "$prompt"
|
||||||
|
[[ -n "$def" ]] && printf '%s (default: %s)%s\n' "$C_DIM" "$def" "$C_RESET"
|
||||||
|
printf '%s > %s' "$C_ACCENT" "$C_RESET"
|
||||||
|
} >&2
|
||||||
|
read -r val </dev/tty || val=""
|
||||||
|
[[ -z "$val" ]] && val="$def"
|
||||||
|
printf '%s' "$val"
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_menu() {
|
||||||
|
# Single-choice list. Args: title prompt tag1 desc1 tag2 desc2 …
|
||||||
|
# Echoes the chosen tag to stdout; returns 1 (no selection) if the user
|
||||||
|
# enters 'q' or a blank line, so callers can fall back to a default.
|
||||||
|
local title="$1" prompt="$2"; shift 2
|
||||||
|
local -a tags=() descs=()
|
||||||
|
while [[ $# -gt 0 ]]; do tags+=("$1"); descs+=("$2"); shift 2; done
|
||||||
|
local i choice
|
||||||
|
while true; do
|
||||||
|
{
|
||||||
|
ui_header "$title"
|
||||||
|
printf '%b\n\n' "$prompt"
|
||||||
|
for i in "${!tags[@]}"; do
|
||||||
|
printf ' %s%2d%s) %s%-12s%s %s\n' \
|
||||||
|
"$C_ACCENT" $((i + 1)) "$C_RESET" "$C_BOLD" "${tags[$i]}" "$C_RESET" "${descs[$i]}"
|
||||||
|
done
|
||||||
|
printf '\n%s Enter number (q to skip): %s' "$C_ACCENT" "$C_RESET"
|
||||||
|
} >&2
|
||||||
|
read -r choice </dev/tty || choice=""
|
||||||
|
[[ "$choice" == q || -z "$choice" ]] && return 1
|
||||||
|
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#tags[@]} )); then
|
||||||
|
printf '%s' "${tags[$((choice - 1))]}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_checklist() {
|
||||||
|
# Multi-select list. Args: title prompt tag1 desc1 state1 tag2 desc2 state2 …
|
||||||
|
# state is "on"/"off" for the initial selection. The list is redrawn each
|
||||||
|
# round; the user toggles items by entering numbers and/or ranges
|
||||||
|
# (e.g. "1 3 5-8"), then presses Enter to confirm. Entering 'q' aborts
|
||||||
|
# (returns 1). The space-separated selected tags are printed to stdout.
|
||||||
|
local title="$1" prompt="$2"; shift 2
|
||||||
|
local -a tags=() descs=() state=()
|
||||||
|
while [[ $# -gt 0 ]]; do tags+=("$1"); descs+=("$2"); state+=("$3"); shift 3; done
|
||||||
|
local n=${#tags[@]} i mark line tok lo hi j
|
||||||
|
while true; do
|
||||||
|
{
|
||||||
|
ui_header "$title"
|
||||||
|
printf '%b\n\n' "$prompt"
|
||||||
|
for i in "${!tags[@]}"; do
|
||||||
|
if [[ "${state[$i]}" == on ]]; then mark="${C_ACCENT}[x]${C_RESET}"; else mark='[ ]'; fi
|
||||||
|
printf ' %s %s%3d%s %s\n' "$mark" "$C_ACCENT" $((i + 1)) "$C_RESET" "${descs[$i]}"
|
||||||
|
done
|
||||||
|
printf '\n%s Toggle items by number/range (e.g. 1 3 5-8); Enter confirms, q aborts.%s\n' "$C_DIM" "$C_RESET"
|
||||||
|
printf '%s > %s' "$C_ACCENT" "$C_RESET"
|
||||||
|
} >&2
|
||||||
|
read -r line </dev/tty || line=""
|
||||||
|
[[ "$line" == q ]] && return 1
|
||||||
|
[[ -z "$line" ]] && break
|
||||||
|
for tok in $line; do
|
||||||
|
if [[ "$tok" =~ ^([0-9]+)-([0-9]+)$ ]]; then
|
||||||
|
lo=${BASH_REMATCH[1]}; hi=${BASH_REMATCH[2]}
|
||||||
|
elif [[ "$tok" =~ ^[0-9]+$ ]]; then
|
||||||
|
lo=$tok; hi=$tok
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
for (( j = lo; j <= hi; j++ )); do
|
||||||
|
(( j >= 1 && j <= n )) || continue
|
||||||
|
if [[ "${state[$((j - 1))]}" == on ]]; then state[$((j - 1))]=off; else state[$((j - 1))]=on; fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
done
|
||||||
|
local out=""
|
||||||
|
for i in "${!tags[@]}"; do
|
||||||
|
[[ "${state[$i]}" == on ]] && out+="${tags[$i]} "
|
||||||
|
done
|
||||||
|
printf '%s' "${out% }"
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_form() {
|
||||||
|
# Sequential field entry. Args: title prompt label1 default1 label2 default2 …
|
||||||
|
# Prints one entered value per line to stdout (blank field → its default),
|
||||||
|
# matching the newline-separated output of `dialog --form`.
|
||||||
|
local title="$1" prompt="$2"; shift 2
|
||||||
|
{
|
||||||
|
ui_header "$title"
|
||||||
|
printf '%b\n' "$prompt"
|
||||||
|
} >&2
|
||||||
|
local label def v out=""
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
label="$1"; def="$2"; shift 2
|
||||||
|
printf '%s %s%s%s [%s]: ' "$C_ACCENT" "$C_BOLD" "$label" "$C_RESET$C_ACCENT" "$def" >&2
|
||||||
|
printf '%s' "$C_RESET" >&2
|
||||||
|
read -r v </dev/tty || v=""
|
||||||
|
[[ -z "$v" ]] && v="$def"
|
||||||
|
out+="$v"$'\n'
|
||||||
|
done
|
||||||
|
printf '%s' "$out"
|
||||||
|
}
|
||||||
|
|
||||||
# ── State ─────────────────────────────────────────────────────────────────────
|
# ── State ─────────────────────────────────────────────────────────────────────
|
||||||
# STEP counts completed modules; TOTAL is pre-computed by count_steps() before
|
# STEP counts completed modules; TOTAL is pre-computed by count_steps() before
|
||||||
|
|
@ -81,24 +197,16 @@ STEP=0
|
||||||
TOTAL=0
|
TOTAL=0
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
require_dialog() {
|
|
||||||
# Short-circuit if dialog is already present; otherwise bootstrap it with pacman.
|
|
||||||
# This allows the script to be run on a fresh Arch install that hasn't yet installed 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() {
|
require_jq() {
|
||||||
# Same bootstrap pattern as require_dialog: jq is only needed in answerfile mode
|
# jq is only needed in answerfile mode, so we install it on demand rather
|
||||||
# but we install it on demand to avoid a hard dependency for interactive use.
|
# than making it a hard dependency for interactive use.
|
||||||
command -v jq &>/dev/null && return
|
command -v jq &>/dev/null && return
|
||||||
echo "jq not found — installing..."
|
echo "jq not found — installing..."
|
||||||
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
|
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
|
||||||
}
|
}
|
||||||
|
|
||||||
die() {
|
die() {
|
||||||
# Fatal error helper: clear the dialog overlay before printing so the message
|
# Fatal error helper: clear the screen before printing so the message
|
||||||
# is readable, then exit 1. Uses stderr to keep it out of any $() capture.
|
# is readable, then exit 1. Uses stderr to keep it out of any $() capture.
|
||||||
clear
|
clear
|
||||||
printf "\n Error: %s\n\n" "$1" >&2
|
printf "\n Error: %s\n\n" "$1" >&2
|
||||||
|
|
@ -107,13 +215,11 @@ die() {
|
||||||
|
|
||||||
warn() {
|
warn() {
|
||||||
# Non-fatal notice: in answerfile mode just log it; interactively show a
|
# Non-fatal notice: in answerfile mode just log it; interactively show a
|
||||||
# dialog msgbox that blocks until the user acknowledges with OK.
|
# message box that blocks until the user acknowledges with Enter.
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
printf "\n Warning: %s\n" "$1" | tee -a "$LOG"
|
printf "\n Warning: %s\n" "$1" | tee -a "$LOG"
|
||||||
else
|
else
|
||||||
dialog --backtitle "$BACKTITLE" \
|
ui_msgbox " Module Conflict " " $1"
|
||||||
--title " Module Conflict " \
|
|
||||||
--msgbox "\n $1" 8 62
|
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,7 +234,7 @@ run_module() {
|
||||||
STEP=$(( STEP + 1 ))
|
STEP=$(( STEP + 1 ))
|
||||||
log_sep "[$STEP/$TOTAL] $label"
|
log_sep "[$STEP/$TOTAL] $label"
|
||||||
|
|
||||||
# Clear the dialog overlay so module output scrolls cleanly on the raw terminal.
|
# Clear the selection screen so module output scrolls cleanly on the raw terminal.
|
||||||
clear
|
clear
|
||||||
printf "\n\033[1;35m [$STEP/$TOTAL] %s\033[0m\n" "$label"
|
printf "\n\033[1;35m [$STEP/$TOTAL] %s\033[0m\n" "$label"
|
||||||
printf "\033[35m ─────────────────────────────────────────────\033[0m\n\n"
|
printf "\033[35m ─────────────────────────────────────────────\033[0m\n\n"
|
||||||
|
|
@ -146,11 +252,9 @@ run_module() {
|
||||||
printf "\n Warning: %s exited with code %d — continuing.\n" "$label" "$rc" | tee -a "$LOG"
|
printf "\n Warning: %s exited with code %d — continuing.\n" "$label" "$rc" | tee -a "$LOG"
|
||||||
else
|
else
|
||||||
# Interactive mode: ask the user whether to abort or continue.
|
# Interactive mode: ask the user whether to abort or continue.
|
||||||
# 'dialog --yesno' returns 0 for Yes and 1 for No; the '|| { ...; exit 1; }'
|
# ui_yesno returns 0 for Yes and 1 for No; the '|| { ...; exit 1; }'
|
||||||
# fires on No, aborting the install.
|
# fires on No, aborting the install.
|
||||||
dialog --backtitle "$BACKTITLE" \
|
ui_yesno " Module Failed " "$label exited with code $rc.\n\nContinue anyway?" no \
|
||||||
--title " Module Failed " \
|
|
||||||
--yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \
|
|
||||||
|| { clear; exit 1; }
|
|| { clear; exit 1; }
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -322,9 +426,6 @@ fi
|
||||||
# the installer cannot function on this OS.
|
# the installer cannot function on this OS.
|
||||||
command -v pacman &>/dev/null || die "pacman not found — Arch Linux required."
|
command -v pacman &>/dev/null || die "pacman not found — Arch Linux required."
|
||||||
|
|
||||||
# Ensure the dialog binary is available before we try to use it.
|
|
||||||
require_dialog
|
|
||||||
|
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
load_answerfile
|
load_answerfile
|
||||||
printf "Answerfile mode: %s\n" "$ANSWERFILE" | tee -a "$LOG"
|
printf "Answerfile mode: %s\n" "$ANSWERFILE" | tee -a "$LOG"
|
||||||
|
|
@ -341,13 +442,9 @@ if ! ping -c1 -W3 archlinux.org &>/dev/null; then
|
||||||
# Give the user a chance to plug in a cable or run iwctl before we
|
# Give the user a chance to plug in a cable or run iwctl before we
|
||||||
# re-test. A second failed ping offers a soft-abort rather than a
|
# re-test. A second failed ping offers a soft-abort rather than a
|
||||||
# hard failure, in case the user wants to proceed with a local mirror.
|
# hard failure, in case the user wants to proceed with a local mirror.
|
||||||
dialog --backtitle "$BACKTITLE" \
|
ui_msgbox " No Network Detected " " No internet connection found.\n\n Wired: ensure the cable is plugged in.\n WiFi: switch to another TTY (Alt+F2)\n and run: iwctl\n\n Reconnect, then press Enter."
|
||||||
--title " No Network Detected " \
|
|
||||||
--msgbox "\n No internet connection found.\n\n Wired: ensure the cable is plugged in.\n WiFi: switch to another TTY (Alt+F2)\n and run: iwctl\n\n Press OK once connected.\n" 13 58
|
|
||||||
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
|
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
|
||||||
dialog --backtitle "$BACKTITLE" \
|
ui_yesno " Still Offline " " Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" no \
|
||||||
--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; }
|
|| { clear; echo "Aborted — no network."; exit 1; }
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -360,9 +457,7 @@ printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" >
|
||||||
|
|
||||||
# ── Welcome ───────────────────────────────────────────────────────────────────
|
# ── Welcome ───────────────────────────────────────────────────────────────────
|
||||||
if ! $ANSWERFILE_MODE; then
|
if ! $ANSWERFILE_MODE; then
|
||||||
dialog --backtitle "$BACKTITLE" \
|
ui_msgbox " Welcome " "\
|
||||||
--title " Welcome " \
|
|
||||||
--msgbox "\n\
|
|
||||||
the_miro's Arch dotfiles installer\n\
|
the_miro's Arch dotfiles installer\n\
|
||||||
Cyberqueer · Wayland · Hyprland\n\
|
Cyberqueer · Wayland · Hyprland\n\
|
||||||
─────────────────────────────────────────\n\
|
─────────────────────────────────────────\n\
|
||||||
|
|
@ -370,7 +465,7 @@ if ! $ANSWERFILE_MODE; then
|
||||||
Arch Linux — network admin, development & gaming\n\
|
Arch Linux — network admin, development & gaming\n\
|
||||||
\n\
|
\n\
|
||||||
Source: $DOTFILES_DIR\n\
|
Source: $DOTFILES_DIR\n\
|
||||||
Log: $LOG\n" 14 62
|
Log: $LOG"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Hostname ──────────────────────────────────────────────────────────────────
|
# ── Hostname ──────────────────────────────────────────────────────────────────
|
||||||
|
|
@ -385,14 +480,10 @@ if $ANSWERFILE_MODE; then
|
||||||
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
|
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# dialog --inputbox: 3>&1 1>&2 2>&3 swaps fd1 and fd2 so the user's typed
|
# ui_input prints its prompt to stderr and the typed value to stdout, so the
|
||||||
# value (written to stdout by dialog) is captured by the $() subshell, while
|
# $() capture receives only the hostname. A blank line keeps the default.
|
||||||
# dialog's UI (which needs stderr for the terminal) flows to the actual terminal.
|
HOSTNAME_INPUT=$(ui_input " Hostname " \
|
||||||
# The '|| HOSTNAME_INPUT=""' handles the user pressing Esc (dialog returns 1).
|
" Hostname for this machine (leave blank to keep default).") || HOSTNAME_INPUT=""
|
||||||
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"
|
HOSTNAME_SET="$HOSTNAME_INPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -408,30 +499,25 @@ fi
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
COMPONENTS="$AF_COMPONENTS"
|
COMPONENTS="$AF_COMPONENTS"
|
||||||
else
|
else
|
||||||
# dialog --checklist args: height width list-height, then triplets of tag desc state.
|
# ui_checklist takes tag/desc/state triplets and echoes the selected tags.
|
||||||
# All four components are pre-selected ("on") because they form the expected base.
|
# All four components are pre-selected ("on") because they form the expected base.
|
||||||
# The 3>&1 1>&2 2>&3 fd swap captures dialog's output (the selected tags) via $().
|
# Entering 'q' returns exit code 1; the '|| { ...; exit 0; }' treats that as a clean abort.
|
||||||
# Esc / Cancel returns exit code 1; the '|| { ...; exit 0; }' treats that as a clean abort.
|
COMPONENTS=$(ui_checklist " Select Components " "Optional base components — all enabled by default:" \
|
||||||
COMPONENTS=$(dialog --backtitle "$BACKTITLE" \
|
"pkg" "pkg Package managers yay · nvm · rust" on \
|
||||||
--title " Select Components " \
|
"core" "core Core packages 100+ base system packages" on \
|
||||||
--checklist "Space toggles · Enter confirms · Esc quits" 16 68 4 \
|
"svc" "svc Core services NetworkManager · cronie · fail2ban" on \
|
||||||
"pkg" "Package managers yay · nvm · rust" on \
|
"shell" "shell Shell setup zsh · nvim · yazi · micro · starship" on) \
|
||||||
"core" "Core packages 100+ base system packages" on \
|
|| { clear; echo "Aborted."; exit 0; }
|
||||||
"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
|
fi
|
||||||
|
|
||||||
# ── DE selection ──────────────────────────────────────────────────────────────
|
# ── DE selection ──────────────────────────────────────────────────────────────
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
DE="$AF_DE"
|
DE="$AF_DE"
|
||||||
else
|
else
|
||||||
# dialog --menu is a single-choice list; it outputs the selected tag.
|
# ui_menu is a single-choice list; it outputs the selected tag.
|
||||||
# Esc returns exit code 1 — '|| DE="none"' defaults to skipping the DE,
|
# 'q'/blank returns exit code 1 — '|| DE="none"' defaults to skipping the DE,
|
||||||
# preserving whatever DE the user already has installed.
|
# preserving whatever DE the user already has installed.
|
||||||
DE=$(dialog --backtitle "$BACKTITLE" \
|
DE=$(ui_menu " Desktop Environment " "Select a desktop environment · q / none to skip:" \
|
||||||
--title " Desktop Environment " \
|
|
||||||
--menu "Select a desktop environment · Esc / none to skip:" 24 72 11 \
|
|
||||||
"hyprlua" "HyprLua — Hyprland with Lua config (recommended)" \
|
"hyprlua" "HyprLua — Hyprland with Lua config (recommended)" \
|
||||||
"niri" "Niri — scrollable-tiling Wayland compositor" \
|
"niri" "Niri — scrollable-tiling Wayland compositor" \
|
||||||
"hyprland" "Hyprland — Wayland WM, hyprlang config (legacy)" \
|
"hyprland" "Hyprland — Wayland WM, hyprlang config (legacy)" \
|
||||||
|
|
@ -441,23 +527,15 @@ else
|
||||||
"cosmic" "COSMIC — Rust-built Wayland DE (System76)" \
|
"cosmic" "COSMIC — Rust-built Wayland DE (System76)" \
|
||||||
"xfce" "XFCE — lightweight X11 DE" \
|
"xfce" "XFCE — lightweight X11 DE" \
|
||||||
"lxqt" "LXQt — lightweight Qt X11 DE" \
|
"lxqt" "LXQt — lightweight Qt X11 DE" \
|
||||||
"none" "Skip DE installation" \
|
"none" "Skip DE installation") || DE="none"
|
||||||
3>&1 1>&2 2>&3) || DE="none"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Apps selection ────────────────────────────────────────────────────────────
|
# ── Apps selection ────────────────────────────────────────────────────────────
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
SELECTED_APPS="$AF_APPS"
|
SELECTED_APPS="$AF_APPS"
|
||||||
else
|
else
|
||||||
# Cap the dialog box at 40 rows but shrink to fit smaller terminals.
|
|
||||||
# The list height is the dialog height minus 8 rows of chrome (title, borders,
|
|
||||||
# prompt, buttons), with a minimum of 4 to remain usable on tiny screens.
|
|
||||||
_APP_H=$(( TERM_H - 2 < 40 ? TERM_H - 2 : 40 ))
|
|
||||||
_APP_LIST_H=$(( _APP_H - 8 < 4 ? 4 : _APP_H - 8 ))
|
|
||||||
# BEGIN GENERATED MODULES: module-checklist
|
# BEGIN GENERATED MODULES: module-checklist
|
||||||
SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \
|
SELECTED_APPS=$(ui_checklist " Applications " "Optional applications — installed after base components:" \
|
||||||
--title " Applications " \
|
|
||||||
--checklist "Optional applications — installed after base components:" "$_APP_H" 76 "$_APP_LIST_H" \
|
|
||||||
"ollama" "ollama local LLM runner and API server" off \
|
"ollama" "ollama local LLM runner and API server" off \
|
||||||
"llama-cpp" "llama-cpp standalone LLM inference CLI and server" off \
|
"llama-cpp" "llama-cpp standalone LLM inference CLI and server" off \
|
||||||
"open-webui" "open-webui browser UI for Ollama and LLM backends" off \
|
"open-webui" "open-webui browser UI for Ollama and LLM backends" off \
|
||||||
|
|
@ -538,7 +616,7 @@ else
|
||||||
"qemu" "qemu full QEMU/KVM stack with virt-manager" off \
|
"qemu" "qemu full QEMU/KVM stack with virt-manager" off \
|
||||||
"freeipa-client" "freeipa-client sssd and ipa-client-install with auto-enrollment" off \
|
"freeipa-client" "freeipa-client sssd and ipa-client-install with auto-enrollment" off \
|
||||||
"freeipa-server" "freeipa-server interactive FreeIPA server setup with client generator" off \
|
"freeipa-server" "freeipa-server interactive FreeIPA server setup with client generator" off \
|
||||||
3>&1 1>&2 2>&3) || SELECTED_APPS=""
|
) || SELECTED_APPS=""
|
||||||
# END GENERATED MODULES: module-checklist
|
# END GENERATED MODULES: module-checklist
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -546,12 +624,10 @@ fi
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
SHELL_RC="$AF_SHELL_RC"
|
SHELL_RC="$AF_SHELL_RC"
|
||||||
else
|
else
|
||||||
SHELL_RC=$(dialog --backtitle "$BACKTITLE" \
|
SHELL_RC=$(ui_menu " Shell Config for New Users " \
|
||||||
--title " Shell Config for New Users " \
|
"Should new users on this machine inherit the dotfiles' rc files?\n (Controls what gets copied to /etc/skel)" \
|
||||||
--menu "\n Should new users on this machine inherit the dotfiles' rc files?\n (Controls what gets copied to /etc/skel)\n" 13 68 2 \
|
|
||||||
"dotfiles" "Use the_miro's .zshrc / .bashrc / .vimrc from dotfiles" \
|
"dotfiles" "Use the_miro's .zshrc / .bashrc / .vimrc from dotfiles" \
|
||||||
"defaults" "Skip — use system defaults for new users" \
|
"defaults" "Skip — use system defaults for new users") || SHELL_RC="defaults"
|
||||||
3>&1 1>&2 2>&3) || SHELL_RC="defaults"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Confirmation (interactive mode only) ──────────────────────────────────────
|
# ── Confirmation (interactive mode only) ──────────────────────────────────────
|
||||||
|
|
@ -654,12 +730,8 @@ if ! $ANSWERFILE_MODE; then
|
||||||
# END GENERATED MODULES: module-summary
|
# END GENERATED MODULES: module-summary
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Size the confirmation dialog to the terminal, capped at 24 rows.
|
ui_yesno " Confirm Installation " " Components to install:\n\n${SUMMARY}\n Log: $LOG" \
|
||||||
_CONF_H=$(( TERM_H - 2 < 24 ? TERM_H - 2 : 24 ))
|
|| { clear; echo "Aborted."; exit 0; }
|
||||||
dialog --backtitle "$BACKTITLE" \
|
|
||||||
--title " Confirm Installation " \
|
|
||||||
--yesno "\n Components to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \
|
|
||||||
"$_CONF_H" 62 || { clear; echo "Aborted."; exit 0; }
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Pre-count all selected steps before installation starts so run_module() can
|
# Pre-count all selected steps before installation starts so run_module() can
|
||||||
|
|
@ -846,20 +918,17 @@ if $ANSWERFILE_MODE; then
|
||||||
bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true
|
bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Interactive: show color form dialog
|
# Interactive: prompt for each color field in turn.
|
||||||
# dialog --form: each field is specified as label row col default frow fcol flen max.
|
# ui_form emits one value per line (blank → default), captured via $().
|
||||||
# The fd swap (3>&1 1>&2 2>&3) captures the form output (one value per line) via $().
|
# Pressing Enter on every field leaves COLORWAY_RAW at the defaults, and the
|
||||||
# Esc / Cancel sets COLORWAY_RAW="" so the colorway step is simply skipped.
|
# comparison below then skips the colorway step entirely.
|
||||||
COLORWAY_RAW=$(dialog --backtitle "$BACKTITLE" \
|
COLORWAY_RAW=$(ui_form " Colorway (optional) " \
|
||||||
--title " Colorway (optional) " \
|
" Customize theme colors — bare 6-digit hex, no #.\n Leave each field unchanged to skip colorway setup." \
|
||||||
--form "\n Customize theme colors — bare 6-digit hex, no #.\n Leave unchanged to skip colorway setup.\n" \
|
"COLOR_TEXT" "$DEF_TEXT" \
|
||||||
16 62 5 \
|
"COLOR_BG" "$DEF_BG" \
|
||||||
"COLOR_TEXT " 1 1 "$DEF_TEXT" 1 18 10 7 \
|
"COLOR_HIGHLIGHT" "$DEF_HIGHLIGHT" \
|
||||||
"COLOR_BG " 2 1 "$DEF_BG" 2 18 10 7 \
|
"COLOR_DARK" "$DEF_DARK" \
|
||||||
"COLOR_HIGHLIGHT " 3 1 "$DEF_HIGHLIGHT" 3 18 10 7 \
|
"COLOR_RED" "$DEF_RED") || COLORWAY_RAW=""
|
||||||
"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
|
if [[ -n "$COLORWAY_RAW" ]]; then
|
||||||
# mapfile reads the newline-separated form output into an array.
|
# mapfile reads the newline-separated form output into an array.
|
||||||
|
|
@ -912,9 +981,7 @@ fi
|
||||||
if $ANSWERFILE_MODE; then
|
if $ANSWERFILE_MODE; then
|
||||||
printf "\nDone. Log: %s\n" "$LOG"
|
printf "\nDone. Log: %s\n" "$LOG"
|
||||||
else
|
else
|
||||||
dialog --backtitle "$BACKTITLE" \
|
ui_msgbox " Done " " All selected components installed.\n\n Log: $LOG\n\n A reboot may be required for all changes to take effect."
|
||||||
--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
|
clear
|
||||||
printf "\n Done. Log: %s\n\n" "$LOG"
|
printf "\n Done. Log: %s\n\n" "$LOG"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue