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
|
||||
|
||||
# -- 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() {
|
||||
local out
|
||||
out=' SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \\\n'
|
||||
out+=' --title " Applications " \\\n'
|
||||
out+=' --checklist "Optional applications — installed after base components:" "$_APP_H" 76 "$_APP_LIST_H" \\\n'
|
||||
out=' SELECTED_APPS=$(ui_checklist " Applications " "Optional applications — installed after base components:" \\\n'
|
||||
for i in "${ACTIVE_IDS[@]}"; do
|
||||
local id="${M_IDS[$i]}" desc="${M_DESCS[$i]}" def="${M_DEFAULTS[$i]}"
|
||||
local padded
|
||||
padded=$(printf '%-20s' "$id")
|
||||
out+=" \"${id}\" \"${padded} ${desc}\" ${def} \\\\\n"
|
||||
done
|
||||
out+=' 3>&1 1>&2 2>&3) || SELECTED_APPS=""\n'
|
||||
out+=' ) || SELECTED_APPS=""\n'
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,49 +30,165 @@ ANSWERFILE_MODE=false
|
|||
# Enable unattended mode only when the answerfile is actually present on disk.
|
||||
[[ -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) ───────────────────────────────────
|
||||
# tput may fail on a bare TTY with no TERM set; fall back to safe 80×24 defaults
|
||||
# so dialog sizing arithmetic never operates on empty strings.
|
||||
TERM_H=$(tput lines 2>/dev/null || echo 24)
|
||||
TERM_W=$(tput cols 2>/dev/null || echo 80)
|
||||
# ── Plain-CLI prompt helpers ──────────────────────────────────────────────────
|
||||
# These reimplement the subset of `dialog` widgets the installer needs using
|
||||
# only `read`. Widgets that return a value (input/menu/checklist/form) print
|
||||
# their UI to stderr and the chosen value(s) to stdout, mirroring dialog's
|
||||
# `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 ───────────────────────────────────────────────────
|
||||
# dialog reads its color scheme from $DIALOGRC at startup.
|
||||
# Writing it to TMP_D (rather than ~/.dialogrc) avoids permanently altering
|
||||
# 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;
|
||||
# every value is a literal dialog color tuple: (FG, BG, BOLD).
|
||||
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
|
||||
ui_header() {
|
||||
# Styled section header printed to the given fd (default stdout).
|
||||
printf '\n%s──── %s ────%s\n\n' "$C_TITLE" "$1" "$C_RESET"
|
||||
}
|
||||
|
||||
ui_msgbox() {
|
||||
# Informational box: print title + body, wait for Enter to acknowledge.
|
||||
local title="$1" body="$2"
|
||||
ui_header "$title"
|
||||
printf '%b\n' "$body"
|
||||
printf '\n%s Press Enter to continue…%s' "$C_DIM" "$C_RESET"
|
||||
read -r _ </dev/tty || true
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
ui_yesno() {
|
||||
# Confirmation prompt. Returns 0 for yes, 1 for no.
|
||||
# $3 sets the default applied on a bare Enter ("yes" or "no").
|
||||
local title="$1" body="$2" def="${3:-yes}" ans hint="[Y/n]"
|
||||
[[ "$def" == no ]] && hint="[y/N]"
|
||||
ui_header "$title"
|
||||
printf '%b\n' "$body"
|
||||
while true; do
|
||||
printf '\n%s %s %s%s ' "$C_ACCENT" "Confirm?" "$hint" "$C_RESET"
|
||||
read -r ans </dev/tty || ans=""
|
||||
ans="${ans,,}"; [[ -z "$ans" ]] && ans="$def"
|
||||
case "$ans" in
|
||||
y|yes) return 0 ;;
|
||||
n|no) return 1 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
ui_input() {
|
||||
# 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 ─────────────────────────────────────────────────────────────────────
|
||||
# STEP counts completed modules; TOTAL is pre-computed by count_steps() before
|
||||
|
|
@ -81,24 +197,16 @@ STEP=0
|
|||
TOTAL=0
|
||||
|
||||
# ── 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() {
|
||||
# Same bootstrap pattern as require_dialog: jq is only needed in answerfile mode
|
||||
# but we install it on demand to avoid a hard dependency for interactive use.
|
||||
# jq is only needed in answerfile mode, so we install it on demand rather
|
||||
# than making it a hard dependency for interactive use.
|
||||
command -v jq &>/dev/null && return
|
||||
echo "jq not found — installing..."
|
||||
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
|
||||
}
|
||||
|
||||
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.
|
||||
clear
|
||||
printf "\n Error: %s\n\n" "$1" >&2
|
||||
|
|
@ -107,13 +215,11 @@ die() {
|
|||
|
||||
warn() {
|
||||
# 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
|
||||
printf "\n Warning: %s\n" "$1" | tee -a "$LOG"
|
||||
else
|
||||
dialog --backtitle "$BACKTITLE" \
|
||||
--title " Module Conflict " \
|
||||
--msgbox "\n $1" 8 62
|
||||
ui_msgbox " Module Conflict " " $1"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +234,7 @@ run_module() {
|
|||
STEP=$(( STEP + 1 ))
|
||||
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
|
||||
printf "\n\033[1;35m [$STEP/$TOTAL] %s\033[0m\n" "$label"
|
||||
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"
|
||||
else
|
||||
# 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.
|
||||
dialog --backtitle "$BACKTITLE" \
|
||||
--title " Module Failed " \
|
||||
--yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \
|
||||
ui_yesno " Module Failed " "$label exited with code $rc.\n\nContinue anyway?" no \
|
||||
|| { clear; exit 1; }
|
||||
fi
|
||||
fi
|
||||
|
|
@ -322,9 +426,6 @@ fi
|
|||
# the installer cannot function on this OS.
|
||||
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
|
||||
load_answerfile
|
||||
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
|
||||
# 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.
|
||||
dialog --backtitle "$BACKTITLE" \
|
||||
--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
|
||||
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."
|
||||
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 \
|
||||
ui_yesno " Still Offline " " Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" no \
|
||||
|| { clear; echo "Aborted — no network."; exit 1; }
|
||||
fi
|
||||
fi
|
||||
|
|
@ -360,9 +457,7 @@ printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" >
|
|||
|
||||
# ── Welcome ───────────────────────────────────────────────────────────────────
|
||||
if ! $ANSWERFILE_MODE; then
|
||||
dialog --backtitle "$BACKTITLE" \
|
||||
--title " Welcome " \
|
||||
--msgbox "\n\
|
||||
ui_msgbox " Welcome " "\
|
||||
the_miro's Arch dotfiles installer\n\
|
||||
Cyberqueer · Wayland · Hyprland\n\
|
||||
─────────────────────────────────────────\n\
|
||||
|
|
@ -370,7 +465,7 @@ if ! $ANSWERFILE_MODE; then
|
|||
Arch Linux — network admin, development & gaming\n\
|
||||
\n\
|
||||
Source: $DOTFILES_DIR\n\
|
||||
Log: $LOG\n" 14 62
|
||||
Log: $LOG"
|
||||
fi
|
||||
|
||||
# ── Hostname ──────────────────────────────────────────────────────────────────
|
||||
|
|
@ -385,14 +480,10 @@ if $ANSWERFILE_MODE; then
|
|||
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
|
||||
fi
|
||||
else
|
||||
# dialog --inputbox: 3>&1 1>&2 2>&3 swaps fd1 and fd2 so the user's typed
|
||||
# value (written to stdout by dialog) is captured by the $() subshell, while
|
||||
# dialog's UI (which needs stderr for the terminal) flows to the actual terminal.
|
||||
# The '|| HOSTNAME_INPUT=""' handles the user pressing Esc (dialog returns 1).
|
||||
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=""
|
||||
# ui_input prints its prompt to stderr and the typed value to stdout, so the
|
||||
# $() capture receives only the hostname. A blank line keeps the default.
|
||||
HOSTNAME_INPUT=$(ui_input " Hostname " \
|
||||
" Hostname for this machine (leave blank to keep default).") || HOSTNAME_INPUT=""
|
||||
HOSTNAME_SET="$HOSTNAME_INPUT"
|
||||
fi
|
||||
|
||||
|
|
@ -408,30 +499,25 @@ fi
|
|||
if $ANSWERFILE_MODE; then
|
||||
COMPONENTS="$AF_COMPONENTS"
|
||||
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.
|
||||
# The 3>&1 1>&2 2>&3 fd swap captures dialog's output (the selected tags) via $().
|
||||
# Esc / Cancel returns exit code 1; the '|| { ...; exit 0; }' treats that as a clean abort.
|
||||
COMPONENTS=$(dialog --backtitle "$BACKTITLE" \
|
||||
--title " Select Components " \
|
||||
--checklist "Space toggles · Enter confirms · Esc quits" 16 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; }
|
||||
# Entering 'q' 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:" \
|
||||
"pkg" "pkg Package managers yay · nvm · rust" on \
|
||||
"core" "core Core packages 100+ base system packages" on \
|
||||
"svc" "svc Core services NetworkManager · cronie · fail2ban" on \
|
||||
"shell" "shell Shell setup zsh · nvim · yazi · micro · starship" on) \
|
||||
|| { clear; echo "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
# ── DE selection ──────────────────────────────────────────────────────────────
|
||||
if $ANSWERFILE_MODE; then
|
||||
DE="$AF_DE"
|
||||
else
|
||||
# dialog --menu is a single-choice list; it outputs the selected tag.
|
||||
# Esc returns exit code 1 — '|| DE="none"' defaults to skipping the DE,
|
||||
# ui_menu is a single-choice list; it outputs the selected tag.
|
||||
# 'q'/blank returns exit code 1 — '|| DE="none"' defaults to skipping the DE,
|
||||
# preserving whatever DE the user already has installed.
|
||||
DE=$(dialog --backtitle "$BACKTITLE" \
|
||||
--title " Desktop Environment " \
|
||||
--menu "Select a desktop environment · Esc / none to skip:" 24 72 11 \
|
||||
DE=$(ui_menu " Desktop Environment " "Select a desktop environment · q / none to skip:" \
|
||||
"hyprlua" "HyprLua — Hyprland with Lua config (recommended)" \
|
||||
"niri" "Niri — scrollable-tiling Wayland compositor" \
|
||||
"hyprland" "Hyprland — Wayland WM, hyprlang config (legacy)" \
|
||||
|
|
@ -441,23 +527,15 @@ else
|
|||
"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"
|
||||
"none" "Skip DE installation") || DE="none"
|
||||
fi
|
||||
|
||||
# ── Apps selection ────────────────────────────────────────────────────────────
|
||||
if $ANSWERFILE_MODE; then
|
||||
SELECTED_APPS="$AF_APPS"
|
||||
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
|
||||
SELECTED_APPS=$(dialog --backtitle "$BACKTITLE" \
|
||||
--title " Applications " \
|
||||
--checklist "Optional applications — installed after base components:" "$_APP_H" 76 "$_APP_LIST_H" \
|
||||
SELECTED_APPS=$(ui_checklist " Applications " "Optional applications — installed after base components:" \
|
||||
"ollama" "ollama local LLM runner and API 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 \
|
||||
|
|
@ -538,7 +616,7 @@ else
|
|||
"qemu" "qemu full QEMU/KVM stack with virt-manager" 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 \
|
||||
3>&1 1>&2 2>&3) || SELECTED_APPS=""
|
||||
) || SELECTED_APPS=""
|
||||
# END GENERATED MODULES: module-checklist
|
||||
fi
|
||||
|
||||
|
|
@ -546,12 +624,10 @@ fi
|
|||
if $ANSWERFILE_MODE; then
|
||||
SHELL_RC="$AF_SHELL_RC"
|
||||
else
|
||||
SHELL_RC=$(dialog --backtitle "$BACKTITLE" \
|
||||
--title " Shell Config for New Users " \
|
||||
--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 \
|
||||
SHELL_RC=$(ui_menu " Shell Config for New Users " \
|
||||
"Should new users on this machine inherit the dotfiles' rc files?\n (Controls what gets copied to /etc/skel)" \
|
||||
"dotfiles" "Use the_miro's .zshrc / .bashrc / .vimrc from dotfiles" \
|
||||
"defaults" "Skip — use system defaults for new users" \
|
||||
3>&1 1>&2 2>&3) || SHELL_RC="defaults"
|
||||
"defaults" "Skip — use system defaults for new users") || SHELL_RC="defaults"
|
||||
fi
|
||||
|
||||
# ── Confirmation (interactive mode only) ──────────────────────────────────────
|
||||
|
|
@ -654,12 +730,8 @@ if ! $ANSWERFILE_MODE; then
|
|||
# END GENERATED MODULES: module-summary
|
||||
fi
|
||||
|
||||
# Size the confirmation dialog to the terminal, capped at 24 rows.
|
||||
_CONF_H=$(( TERM_H - 2 < 24 ? TERM_H - 2 : 24 ))
|
||||
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; }
|
||||
ui_yesno " Confirm Installation " " Components to install:\n\n${SUMMARY}\n Log: $LOG" \
|
||||
|| { clear; echo "Aborted."; exit 0; }
|
||||
fi
|
||||
|
||||
# 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
|
||||
fi
|
||||
else
|
||||
# Interactive: show color form dialog
|
||||
# dialog --form: each field is specified as label row col default frow fcol flen max.
|
||||
# The fd swap (3>&1 1>&2 2>&3) captures the form output (one value per line) via $().
|
||||
# Esc / Cancel sets COLORWAY_RAW="" so the colorway step is simply skipped.
|
||||
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=""
|
||||
# Interactive: prompt for each color field in turn.
|
||||
# ui_form emits one value per line (blank → default), captured via $().
|
||||
# Pressing Enter on every field leaves COLORWAY_RAW at the defaults, and the
|
||||
# comparison below then skips the colorway step entirely.
|
||||
COLORWAY_RAW=$(ui_form " Colorway (optional) " \
|
||||
" Customize theme colors — bare 6-digit hex, no #.\n Leave each field unchanged to skip colorway setup." \
|
||||
"COLOR_TEXT" "$DEF_TEXT" \
|
||||
"COLOR_BG" "$DEF_BG" \
|
||||
"COLOR_HIGHLIGHT" "$DEF_HIGHLIGHT" \
|
||||
"COLOR_DARK" "$DEF_DARK" \
|
||||
"COLOR_RED" "$DEF_RED") || COLORWAY_RAW=""
|
||||
|
||||
if [[ -n "$COLORWAY_RAW" ]]; then
|
||||
# mapfile reads the newline-separated form output into an array.
|
||||
|
|
@ -912,9 +981,7 @@ fi
|
|||
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
|
||||
ui_msgbox " Done " " All selected components installed.\n\n Log: $LOG\n\n A reboot may be required for all changes to take effect."
|
||||
|
||||
clear
|
||||
printf "\n Done. Log: %s\n\n" "$LOG"
|
||||
|
|
|
|||
Loading…
Reference in New Issue