922 lines
57 KiB
Bash
Executable File
922 lines
57 KiB
Bash
Executable File
#!/bin/bash
|
||
# tui-install.sh — TUI installer for the_miro's Arch dotfiles
|
||
|
||
|
||
# -u: treat unset variables as errors; -o pipefail: a pipe fails if any stage fails.
|
||
# Intentionally omits -e so individual module failures can be handled explicitly.
|
||
set -uo pipefail
|
||
|
||
# ── Paths ─────────────────────────────────────────────────────────────────────
|
||
# Resolve the installer's own directory regardless of where the script was called from.
|
||
# BASH_SOURCE[0] stays correct even when the script is sourced rather than executed directly.
|
||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
# Dotfiles root is one level up from setup/.
|
||
DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||
MODULES="$DOTFILES_DIR/setup/modules"
|
||
APPS="$MODULES/optional-Modules/apps"
|
||
LOG="$HOME/dotfiles-install.log"
|
||
# Private scratch space for the custom DIALOGRC theme file and a temporary colors.conf.
|
||
# mktemp -d creates a uniquely named directory that only this process owns.
|
||
TMP_D="$(mktemp -d)"
|
||
# Cleanup trap: remove the scratch dir and restore the terminal state on any exit signal.
|
||
# tput reset issues a full terminal reset sequence; stty sane is the bare-console fallback.
|
||
# '|| true' ensures the trap never returns a non-zero code, which would mask the real exit status.
|
||
trap 'rm -rf "$TMP_D"; tput reset 2>/dev/null || stty sane 2>/dev/null || true' EXIT INT TERM HUP
|
||
|
||
# Allow the caller to inject a different answerfile path via the ANSWERFILE env var.
|
||
# The default /answerfile.json is a conventional location for CI/PXE boot images.
|
||
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
|
||
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"
|
||
|
||
# ── 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)
|
||
|
||
# ── 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
|
||
|
||
# ── State ─────────────────────────────────────────────────────────────────────
|
||
# STEP counts completed modules; TOTAL is pre-computed by count_steps() before
|
||
# installation begins so run_module() can print accurate [N/TOTAL] progress.
|
||
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.
|
||
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
|
||
# is readable, then exit 1. Uses stderr to keep it out of any $() capture.
|
||
clear
|
||
printf "\n Error: %s\n\n" "$1" >&2
|
||
exit 1
|
||
}
|
||
|
||
warn() {
|
||
# Non-fatal notice: in answerfile mode just log it; interactively show a
|
||
# dialog msgbox that blocks until the user acknowledges with OK.
|
||
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
|
||
fi
|
||
}
|
||
|
||
log_sep() {
|
||
# Write a visible separator plus a timestamp to the log before each module run.
|
||
# Appended with >> so earlier log entries are never overwritten mid-install.
|
||
printf "\n══════════════════════════════════\n %s\n %s\n" "$1" "$(date)" >> "$LOG"
|
||
}
|
||
|
||
run_module() {
|
||
local label="$1" script="$2"
|
||
STEP=$(( STEP + 1 ))
|
||
log_sep "[$STEP/$TOTAL] $label"
|
||
|
||
# Clear the dialog overlay 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"
|
||
|
||
# Run the module script, merging stderr into stdout, and tee to the log.
|
||
# PIPESTATUS[0] captures the exit code of 'bash "$script"' even though
|
||
# 'tee' is the last command in the pipe (and would always succeed).
|
||
local rc=0
|
||
bash "$script" 2>&1 | tee -a "$LOG" || rc=${PIPESTATUS[0]}
|
||
|
||
if [[ $rc -ne 0 ]]; then
|
||
if [[ $ANSWERFILE_MODE == true ]]; then
|
||
# Unattended mode: log the failure and keep going; aborting mid-CI
|
||
# would require a full manual re-run.
|
||
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; }'
|
||
# fires on No, aborting the install.
|
||
dialog --backtitle "$BACKTITLE" \
|
||
--title " Module Failed " \
|
||
--yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \
|
||
|| { clear; exit 1; }
|
||
fi
|
||
fi
|
||
}
|
||
|
||
count_steps() {
|
||
# Pre-count the total number of modules that will actually run so that
|
||
# run_module() can display accurate [N/TOTAL] progress from the first step.
|
||
# Each glob test mirrors the corresponding run_module() call below; they
|
||
# must stay in sync whenever a new module is added or removed.
|
||
local c="$1" de="$2" a="${3:-}"
|
||
TOTAL=0
|
||
# Base components: each keyword maps to exactly one module script.
|
||
[[ "$c" == *"pkg"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ "$c" == *"core"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ "$c" == *"svc"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ "$c" == *"shell"* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
# A non-"none" DE selection always installs exactly one DE module.
|
||
[[ "$de" != "none" ]] && TOTAL=$(( TOTAL + 1 ))
|
||
# Optional app modules: one word-boundary check per app, one increment per match.
|
||
# Padding $a with spaces and matching " tag " prevents partial-name false matches
|
||
# (e.g. "plymouth" must not match against "plymouth-custom").
|
||
# BEGIN GENERATED MODULES: module-counters
|
||
[[ " $a " == *" ollama "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" llama-cpp "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" open-webui "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" claude "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" networking-cli "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" disk-recovery "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" himalaya "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" mail-notmuch "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" caldav-sync "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" ssh-server "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" wireshark "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" anti-malware "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" gnuplot "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" blender-povray "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" toot "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" db-clients "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" mysql "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" productivity "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" python "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" k8s "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" docker "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" podman "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" cockpit "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" tlp "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" butter "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" localsend "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" croc "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" opendeck "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" localtunnel "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" timeshift "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" zfs "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" wprs "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" plymouth "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" plymouth-custom "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" steam "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" vesktop "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" spotify "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" prism "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" vintagestory "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" openarena "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" tetris "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" doom "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" sauerbraten "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" stuntrally "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" onlyoffice "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" xournal "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" rnote "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" obsidian "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" tangent-notes "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" ffmpeg "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" sox "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" imagemagick "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" yt-dlp "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" gimp "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" inkscape "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" krita "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" kdenlive "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" openshot "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" shotcut "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" ardour "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" audacity "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" lmms "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" mixxx "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" cecilia "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" chromium "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" firefox-browser "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" zen-browser "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" nyxt "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" librewolf "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" min-browser "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" vscodium "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" zed-ide "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" geany "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" codeblocks "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" kate "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" rdp-client "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" lamco-rdp-server "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" qemu "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" freeipa-client "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
[[ " $a " == *" freeipa-server "* ]] && TOTAL=$(( TOTAL + 1 ))
|
||
# END GENERATED MODULES: module-counters
|
||
}
|
||
|
||
# ── Answerfile ────────────────────────────────────────────────────────────────
|
||
# AF_* variables hold values parsed from the JSON answerfile.
|
||
# They are initialised to safe defaults so the rest of the script can reference
|
||
# them unconditionally whether or not answerfile mode is active.
|
||
AF_HOSTNAME=""
|
||
AF_COMPONENTS=""
|
||
AF_DE="none"
|
||
AF_APPS=""
|
||
AF_SHELL_RC="dotfiles"
|
||
AF_COLOR_TEXT=""
|
||
AF_COLOR_BG=""
|
||
AF_COLOR_HIGHLIGHT=""
|
||
AF_COLOR_DARK=""
|
||
AF_COLOR_RED=""
|
||
|
||
load_answerfile() {
|
||
require_jq
|
||
# -r outputs raw strings without JSON quoting.
|
||
# '// ""' / '// "none"' are jq's null-coalescing operator: if the key is
|
||
# absent from the file the expression yields the fallback instead.
|
||
AF_HOSTNAME=$(jq -r '.hostname // ""' "$ANSWERFILE")
|
||
# JSON arrays are flattened to a space-separated string to match the format
|
||
# that COMPONENTS and SELECTED_APPS expect throughout the rest of the script.
|
||
AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE")
|
||
AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE")
|
||
AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE")
|
||
AF_SHELL_RC=$(jq -r '.shell_rc // "dotfiles"' "$ANSWERFILE")
|
||
# Color values are optional; an empty string means "keep the repo default".
|
||
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() {
|
||
# Returns the MAC address of the first non-loopback interface, colons stripped.
|
||
# Used to make answerfile-derived hostnames unique per machine (e.g. "myhost-aabbccddee").
|
||
local mac
|
||
# awk state machine: sets iface=1 when it sees an interface line whose name
|
||
# does NOT start with "lo" ([^l][^o] skips "lo"), then captures the
|
||
# 'link/ether XX:XX:XX:XX:XX:XX' line and exits immediately after the first hit.
|
||
mac=$(ip link show 2>/dev/null \
|
||
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
|
||
# Strip colons from the MAC so it is safe to embed in a hostname (only [a-z0-9-] allowed).
|
||
printf '%s' "${mac//:/}"
|
||
}
|
||
|
||
# ── Preflight ─────────────────────────────────────────────────────────────────
|
||
if [[ $EUID -eq 0 ]]; then
|
||
# Root context (e.g. archiso chroot): shim sudo as a passthrough
|
||
# Module scripts call 'sudo' for privilege operations; when we are already root
|
||
# there is no sudo binary (or it may be absent), so inject a fake one that is
|
||
# just 'exec "$@"' — transparently forwarding all arguments.
|
||
mkdir -p "$TMP_D/bin"
|
||
printf '#!/bin/bash\nexec "$@"\n' > "$TMP_D/bin/sudo"
|
||
chmod +x "$TMP_D/bin/sudo"
|
||
# Prepend to PATH so this shim takes precedence over any real sudo.
|
||
export PATH="$TMP_D/bin:$PATH"
|
||
fi
|
||
# Hard guard: pacman is the package manager used by every module; without it
|
||
# 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"
|
||
fi
|
||
|
||
# Network check: -c1 sends one ICMP echo, -W3 waits up to 3 seconds for reply.
|
||
# We test against archlinux.org because that's where pacman will fetch packages.
|
||
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
|
||
if $ANSWERFILE_MODE; then
|
||
# In unattended mode just warn; the caller decides whether the image
|
||
# is expected to be offline (e.g. local mirror pre-seeded in pacman).
|
||
printf "Warning: no internet connection detected.\n" | tee -a "$LOG"
|
||
else
|
||
# 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
|
||
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
|
||
|
||
# Truncate the log file now that preflight passed; all prior output was to stdout.
|
||
# Entries appended from this point on are the authoritative install log.
|
||
> "$LOG"
|
||
printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" >> "$LOG"
|
||
|
||
# ── Welcome ───────────────────────────────────────────────────────────────────
|
||
if ! $ANSWERFILE_MODE; then
|
||
dialog --backtitle "$BACKTITLE" \
|
||
--title " Welcome " \
|
||
--msgbox "\n\
|
||
the_miro's Arch dotfiles installer\n\
|
||
Cyberqueer · Wayland · Hyprland\n\
|
||
─────────────────────────────────────────\n\
|
||
\n\
|
||
Arch Linux — network admin, development & gaming\n\
|
||
\n\
|
||
Source: $DOTFILES_DIR\n\
|
||
Log: $LOG\n" 14 62
|
||
fi
|
||
|
||
# ── Hostname ──────────────────────────────────────────────────────────────────
|
||
HOSTNAME_SET=""
|
||
if $ANSWERFILE_MODE; then
|
||
if [[ -n "$AF_HOSTNAME" ]]; then
|
||
# Append the stripped MAC address to the base name from the answerfile.
|
||
# This makes each cloned machine's hostname unique without needing per-host
|
||
# answerfiles (useful for mass-provisioning identical images).
|
||
MAC=$(get_mac_suffix)
|
||
HOSTNAME_SET="${AF_HOSTNAME}-${MAC}"
|
||
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
|
||
fi
|
||
else
|
||
# 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=""
|
||
HOSTNAME_SET="$HOSTNAME_INPUT"
|
||
fi
|
||
|
||
if [[ -n "$HOSTNAME_SET" ]]; then
|
||
# hostnamectl is the preferred method on systemd systems; fall back to writing
|
||
# /etc/hostname directly for minimal chroot environments that lack systemd.
|
||
sudo hostnamectl set-hostname "$HOSTNAME_SET" 2>/dev/null \
|
||
|| echo "$HOSTNAME_SET" | sudo tee /etc/hostname > /dev/null
|
||
printf "Hostname set: %s\n" "$HOSTNAME_SET" >> "$LOG"
|
||
fi
|
||
|
||
# ── Component selection ───────────────────────────────────────────────────────
|
||
if $ANSWERFILE_MODE; then
|
||
COMPONENTS="$AF_COMPONENTS"
|
||
else
|
||
# dialog --checklist args: height width list-height, then triplets of tag desc state.
|
||
# 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; }
|
||
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,
|
||
# 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 \
|
||
"hyprlua" "HyprLua — Hyprland with Lua config (recommended)" \
|
||
"niri" "Niri — scrollable-tiling Wayland compositor" \
|
||
"hyprland" "Hyprland — Wayland WM, hyprlang config (legacy)" \
|
||
"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
|
||
# 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" \
|
||
"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 \
|
||
"claude" "claude Anthropic Claude Code CLI via npm" off \
|
||
"networking-cli" "networking-cli nmap, nethogs, mitmproxy, httpie" off \
|
||
"disk-recovery" "disk-recovery ddrescue and f3 disk recovery tools" off \
|
||
"himalaya" "himalaya terminal email client (AUR)" off \
|
||
"mail-notmuch" "mail-notmuch isync, msmtp, notmuch, alot mail stack" off \
|
||
"caldav-sync" "caldav-sync vdirsyncer and khal CalDAV calendar sync" off \
|
||
"ssh-server" "ssh-server openssh with key-auth and systemd unit enabled" off \
|
||
"wireshark" "wireshark network packet analyser GUI" off \
|
||
"anti-malware" "anti-malware ClamAV, rkhunter, chkrootkit" off \
|
||
"gnuplot" "gnuplot scientific plotting tool" off \
|
||
"blender-povray" "blender-povray 3D modelling and ray-tracing (Blender + POV-Ray)" off \
|
||
"toot" "toot Mastodon CLI client (AUR)" off \
|
||
"db-clients" "db-clients pgcli and mycli interactive database CLIs" off \
|
||
"mysql" "mysql MariaDB server with initial setup" off \
|
||
"productivity" "productivity taskwarrior, watson, jrnl — task management and time tracking" off \
|
||
"python" "python pyright, pipx, pynvim Python tooling" on \
|
||
"k8s" "k8s kubectl and podman-desktop Kubernetes tools" off \
|
||
"docker" "docker docker and docker-compose" off \
|
||
"podman" "podman rootless containers with buildah" off \
|
||
"cockpit" "cockpit web UI for machines and containers" off \
|
||
"tlp" "tlp laptop battery optimisation" off \
|
||
"butter" "butter btrfs snapshot backup manager (AUR)" off \
|
||
"localsend" "localsend LAN file transfer, AirDrop-like (AUR)" off \
|
||
"croc" "croc cross-platform encrypted file transfer" off \
|
||
"opendeck" "opendeck Stream Deck controller — ydotool + OpenDeck (Flatpak)" off \
|
||
"localtunnel" "localtunnel expose localhost over a public URL" off \
|
||
"timeshift" "timeshift system snapshot and backup with autosnap" off \
|
||
"zfs" "zfs zfs-dkms kernel module" off \
|
||
"wprs" "wprs Wayland proxy for remote sessions (wprs-git, AUR)" off \
|
||
"plymouth" "plymouth boot splash — bundled skull logo and spinner" on \
|
||
"plymouth-custom" "plymouth-custom boot splash with a user-supplied image" off \
|
||
"steam" "steam Steam gaming platform" off \
|
||
"vesktop" "vesktop Discord client with Vencord theme" off \
|
||
"spotify" "spotify Spotify launcher with Spicetify theming" off \
|
||
"prism" "prism PrismLauncher Minecraft launcher (Flatpak)" off \
|
||
"vintagestory" "vintagestory Vintage Story survival game (AUR)" off \
|
||
"openarena" "openarena open-source Quake III Arena" off \
|
||
"tetris" "tetris bastet and vitetris terminal Tetris" off \
|
||
"doom" "doom Chocolate Doom with Freedoom data" off \
|
||
"sauerbraten" "sauerbraten Sauerbraten open-source FPS (Cube 2)" off \
|
||
"stuntrally" "stuntrally Stunt Rally racing game (Flatpak)" off \
|
||
"onlyoffice" "onlyoffice office suite — Docs, Sheets, Slides (AUR)" on \
|
||
"xournal" "xournal note-taking and PDF annotator" off \
|
||
"rnote" "rnote handwriting and note-taking with stylus support (Flatpak)" off \
|
||
"obsidian" "obsidian knowledge base and Markdown note-taking (Flatpak)" off \
|
||
"tangent-notes" "tangent-notes networked Markdown note-taking (Flatpak)" off \
|
||
"ffmpeg" "ffmpeg GStreamer codecs and ffmpegthumbnailer" off \
|
||
"sox" "sox command-line audio processing toolkit" off \
|
||
"imagemagick" "imagemagick image manipulation suite" off \
|
||
"yt-dlp" "yt-dlp YouTube and media downloader" off \
|
||
"gimp" "gimp GNU Image Manipulation Program" off \
|
||
"inkscape" "inkscape vector graphics editor" off \
|
||
"krita" "krita digital painting and illustration" off \
|
||
"kdenlive" "kdenlive KDE non-linear video editor" off \
|
||
"openshot" "openshot cross-platform video editor" off \
|
||
"shotcut" "shotcut cross-platform video editor" off \
|
||
"ardour" "ardour professional DAW" off \
|
||
"audacity" "audacity multi-track audio editor" off \
|
||
"lmms" "lmms Linux MultiMedia Studio music production" off \
|
||
"mixxx" "mixxx DJ mixing software" off \
|
||
"cecilia" "cecilia audio synthesis and signal processing (AUR)" off \
|
||
"chromium" "chromium open-source Chromium browser (official)" off \
|
||
"firefox-browser" "firefox-browser Mozilla Firefox (official)" on \
|
||
"zen-browser" "zen-browser privacy-focused Firefox fork (AUR)" off \
|
||
"nyxt" "nyxt keyboard-driven hackable browser (AUR)" off \
|
||
"librewolf" "librewolf hardened Firefox fork (AUR)" off \
|
||
"min-browser" "min-browser minimal Electron browser (AUR)" off \
|
||
"vscodium" "vscodium telemetry-free VS Code build (AUR)" off \
|
||
"zed-ide" "zed-ide high-performance Rust IDE (official)" off \
|
||
"geany" "geany lightweight IDE with plugins (official)" off \
|
||
"codeblocks" "codeblocks C/C++ IDE (official)" off \
|
||
"kate" "kate KDE advanced text editor (official)" off \
|
||
"rdp-client" "rdp-client Remmina with FreeRDP and VNC plugins" off \
|
||
"lamco-rdp-server" "lamco-rdp-server native Wayland RDP server (AUR, Rust)" 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-server" "freeipa-server interactive FreeIPA server setup with client generator" off \
|
||
3>&1 1>&2 2>&3) || SELECTED_APPS=""
|
||
# END GENERATED MODULES: module-checklist
|
||
fi
|
||
|
||
# ── Shell RC preference ───────────────────────────────────────────────────────
|
||
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 \
|
||
"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"
|
||
fi
|
||
|
||
# ── Confirmation (interactive mode only) ──────────────────────────────────────
|
||
if ! $ANSWERFILE_MODE; then
|
||
# Build a human-readable summary of everything that will be installed so the
|
||
# user can review the full list before any changes are made to the system.
|
||
SUMMARY=""
|
||
[[ -n "$HOSTNAME_SET" ]] && SUMMARY+=" ✦ Hostname: $HOSTNAME_SET\n"
|
||
[[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n"
|
||
[[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n"
|
||
[[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n"
|
||
[[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n"
|
||
[[ "$DE" != "none" && "$DE" != "" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n"
|
||
[[ "$SHELL_RC" == "dotfiles" ]] && SUMMARY+=" ✦ Shell rc files → /etc/skel (dotfiles)\n" \
|
||
|| SUMMARY+=" ✦ Shell rc files → /etc/skel (system defaults)\n"
|
||
|
||
if [[ -n "$SELECTED_APPS" ]]; then
|
||
SUMMARY+="\n Applications:\n"
|
||
# BEGIN GENERATED MODULES: module-summary
|
||
[[ " $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\n"
|
||
[[ " $SELECTED_APPS " == *" networking-cli "* ]] && SUMMARY+=" ✦ networking-cli\n"
|
||
[[ " $SELECTED_APPS " == *" disk-recovery "* ]] && SUMMARY+=" ✦ disk-recovery\n"
|
||
[[ " $SELECTED_APPS " == *" himalaya "* ]] && SUMMARY+=" ✦ himalaya\n"
|
||
[[ " $SELECTED_APPS " == *" mail-notmuch "* ]] && SUMMARY+=" ✦ mail-notmuch\n"
|
||
[[ " $SELECTED_APPS " == *" caldav-sync "* ]] && SUMMARY+=" ✦ caldav-sync\n"
|
||
[[ " $SELECTED_APPS " == *" ssh-server "* ]] && SUMMARY+=" ✦ ssh-server\n"
|
||
[[ " $SELECTED_APPS " == *" wireshark "* ]] && SUMMARY+=" ✦ wireshark\n"
|
||
[[ " $SELECTED_APPS " == *" anti-malware "* ]] && SUMMARY+=" ✦ anti-malware\n"
|
||
[[ " $SELECTED_APPS " == *" gnuplot "* ]] && SUMMARY+=" ✦ gnuplot\n"
|
||
[[ " $SELECTED_APPS " == *" blender-povray "* ]] && SUMMARY+=" ✦ blender-povray\n"
|
||
[[ " $SELECTED_APPS " == *" toot "* ]] && SUMMARY+=" ✦ toot\n"
|
||
[[ " $SELECTED_APPS " == *" db-clients "* ]] && SUMMARY+=" ✦ db-clients\n"
|
||
[[ " $SELECTED_APPS " == *" mysql "* ]] && SUMMARY+=" ✦ mysql\n"
|
||
[[ " $SELECTED_APPS " == *" productivity "* ]] && SUMMARY+=" ✦ productivity\n"
|
||
[[ " $SELECTED_APPS " == *" python "* ]] && SUMMARY+=" ✦ python\n"
|
||
[[ " $SELECTED_APPS " == *" k8s "* ]] && SUMMARY+=" ✦ k8s\n"
|
||
[[ " $SELECTED_APPS " == *" docker "* ]] && SUMMARY+=" ✦ docker\n"
|
||
[[ " $SELECTED_APPS " == *" podman "* ]] && SUMMARY+=" ✦ podman\n"
|
||
[[ " $SELECTED_APPS " == *" cockpit "* ]] && SUMMARY+=" ✦ cockpit\n"
|
||
[[ " $SELECTED_APPS " == *" tlp "* ]] && SUMMARY+=" ✦ tlp\n"
|
||
[[ " $SELECTED_APPS " == *" butter "* ]] && SUMMARY+=" ✦ butter\n"
|
||
[[ " $SELECTED_APPS " == *" localsend "* ]] && SUMMARY+=" ✦ localsend\n"
|
||
[[ " $SELECTED_APPS " == *" croc "* ]] && SUMMARY+=" ✦ croc\n"
|
||
[[ " $SELECTED_APPS " == *" opendeck "* ]] && SUMMARY+=" ✦ opendeck\n"
|
||
[[ " $SELECTED_APPS " == *" localtunnel "* ]] && SUMMARY+=" ✦ localtunnel\n"
|
||
[[ " $SELECTED_APPS " == *" timeshift "* ]] && SUMMARY+=" ✦ timeshift\n"
|
||
[[ " $SELECTED_APPS " == *" zfs "* ]] && SUMMARY+=" ✦ zfs\n"
|
||
[[ " $SELECTED_APPS " == *" wprs "* ]] && SUMMARY+=" ✦ wprs\n"
|
||
[[ " $SELECTED_APPS " == *" plymouth "* ]] && SUMMARY+=" ✦ plymouth\n"
|
||
[[ " $SELECTED_APPS " == *" plymouth-custom "* ]] && SUMMARY+=" ✦ plymouth-custom\n"
|
||
[[ " $SELECTED_APPS " == *" steam "* ]] && SUMMARY+=" ✦ steam\n"
|
||
[[ " $SELECTED_APPS " == *" vesktop "* ]] && SUMMARY+=" ✦ vesktop\n"
|
||
[[ " $SELECTED_APPS " == *" spotify "* ]] && SUMMARY+=" ✦ spotify\n"
|
||
[[ " $SELECTED_APPS " == *" prism "* ]] && SUMMARY+=" ✦ prism\n"
|
||
[[ " $SELECTED_APPS " == *" vintagestory "* ]] && SUMMARY+=" ✦ vintagestory\n"
|
||
[[ " $SELECTED_APPS " == *" openarena "* ]] && SUMMARY+=" ✦ openarena\n"
|
||
[[ " $SELECTED_APPS " == *" tetris "* ]] && SUMMARY+=" ✦ tetris\n"
|
||
[[ " $SELECTED_APPS " == *" doom "* ]] && SUMMARY+=" ✦ doom\n"
|
||
[[ " $SELECTED_APPS " == *" sauerbraten "* ]] && SUMMARY+=" ✦ sauerbraten\n"
|
||
[[ " $SELECTED_APPS " == *" stuntrally "* ]] && SUMMARY+=" ✦ stuntrally\n"
|
||
[[ " $SELECTED_APPS " == *" onlyoffice "* ]] && SUMMARY+=" ✦ onlyoffice\n"
|
||
[[ " $SELECTED_APPS " == *" xournal "* ]] && SUMMARY+=" ✦ xournal\n"
|
||
[[ " $SELECTED_APPS " == *" rnote "* ]] && SUMMARY+=" ✦ rnote\n"
|
||
[[ " $SELECTED_APPS " == *" obsidian "* ]] && SUMMARY+=" ✦ obsidian\n"
|
||
[[ " $SELECTED_APPS " == *" tangent-notes "* ]] && SUMMARY+=" ✦ tangent-notes\n"
|
||
[[ " $SELECTED_APPS " == *" ffmpeg "* ]] && SUMMARY+=" ✦ ffmpeg\n"
|
||
[[ " $SELECTED_APPS " == *" sox "* ]] && SUMMARY+=" ✦ sox\n"
|
||
[[ " $SELECTED_APPS " == *" imagemagick "* ]] && SUMMARY+=" ✦ imagemagick\n"
|
||
[[ " $SELECTED_APPS " == *" yt-dlp "* ]] && SUMMARY+=" ✦ yt-dlp\n"
|
||
[[ " $SELECTED_APPS " == *" gimp "* ]] && SUMMARY+=" ✦ gimp\n"
|
||
[[ " $SELECTED_APPS " == *" inkscape "* ]] && SUMMARY+=" ✦ inkscape\n"
|
||
[[ " $SELECTED_APPS " == *" krita "* ]] && SUMMARY+=" ✦ krita\n"
|
||
[[ " $SELECTED_APPS " == *" kdenlive "* ]] && SUMMARY+=" ✦ kdenlive\n"
|
||
[[ " $SELECTED_APPS " == *" openshot "* ]] && SUMMARY+=" ✦ openshot\n"
|
||
[[ " $SELECTED_APPS " == *" shotcut "* ]] && SUMMARY+=" ✦ shotcut\n"
|
||
[[ " $SELECTED_APPS " == *" ardour "* ]] && SUMMARY+=" ✦ ardour\n"
|
||
[[ " $SELECTED_APPS " == *" audacity "* ]] && SUMMARY+=" ✦ audacity\n"
|
||
[[ " $SELECTED_APPS " == *" lmms "* ]] && SUMMARY+=" ✦ lmms\n"
|
||
[[ " $SELECTED_APPS " == *" mixxx "* ]] && SUMMARY+=" ✦ mixxx\n"
|
||
[[ " $SELECTED_APPS " == *" cecilia "* ]] && SUMMARY+=" ✦ cecilia\n"
|
||
[[ " $SELECTED_APPS " == *" chromium "* ]] && SUMMARY+=" ✦ chromium\n"
|
||
[[ " $SELECTED_APPS " == *" firefox-browser "* ]] && SUMMARY+=" ✦ firefox-browser\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+=" ✦ codeblocks\n"
|
||
[[ " $SELECTED_APPS " == *" kate "* ]] && SUMMARY+=" ✦ kate\n"
|
||
[[ " $SELECTED_APPS " == *" rdp-client "* ]] && SUMMARY+=" ✦ rdp-client\n"
|
||
[[ " $SELECTED_APPS " == *" lamco-rdp-server "* ]] && SUMMARY+=" ✦ lamco-rdp-server\n"
|
||
[[ " $SELECTED_APPS " == *" qemu "* ]] && SUMMARY+=" ✦ qemu\n"
|
||
[[ " $SELECTED_APPS " == *" freeipa-client "* ]] && SUMMARY+=" ✦ freeipa-client\n"
|
||
[[ " $SELECTED_APPS " == *" freeipa-server "* ]] && SUMMARY+=" ✦ freeipa-server\n"
|
||
# 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; }
|
||
fi
|
||
|
||
# Pre-count all selected steps before installation starts so run_module() can
|
||
# display [N/TOTAL] with an accurate denominator from the very first module.
|
||
count_steps "$COMPONENTS" "$DE" "$SELECTED_APPS"
|
||
|
||
# ── Installation: base components ─────────────────────────────────────────────
|
||
# Each guard uses glob matching against the space-separated COMPONENTS string.
|
||
# Order matters: package managers must be installed before packages that need yay/rust.
|
||
[[ "$COMPONENTS" == *"pkg"* ]] && run_module "Package Managers" "$MODULES/package-managers.sh"
|
||
[[ "$COMPONENTS" == *"core"* ]] && run_module "Core Packages" "$MODULES/core-packages.sh"
|
||
[[ "$COMPONENTS" == *"svc"* ]] && run_module "Core Services" "$MODULES/core.sh"
|
||
[[ "$COMPONENTS" == *"shell"* ]] && run_module "Shell Setup" "$MODULES/shell-setup.sh"
|
||
|
||
# Route the single selected DE value to its corresponding install script.
|
||
# "none" is the skip sentinel — no case branch matches it intentionally.
|
||
if [[ "$DE" != "none" ]]; then
|
||
case "$DE" in
|
||
hyprlua) run_module "HyprLua" "$MODULES/Desktop-Environments/hyprlua.sh" ;;
|
||
niri) run_module "Niri" "$MODULES/Desktop-Environments/niri.sh" ;;
|
||
hyprland) run_module "Hyprland" "$MODULES/Desktop-Environments/hyprland.sh" ;;
|
||
sway) run_module "Sway" "$MODULES/Desktop-Environments/sway.sh" ;;
|
||
kde-plasma) run_module "KDE Plasma" "$MODULES/Desktop-Environments/kde-plasma.sh" ;;
|
||
gnome) run_module "GNOME" "$MODULES/Desktop-Environments/gnome.sh" ;;
|
||
cosmic) run_module "COSMIC" "$MODULES/Desktop-Environments/cosmic.sh" ;;
|
||
xfce) run_module "XFCE" "$MODULES/Desktop-Environments/xfce.sh" ;;
|
||
lxqt) run_module "LXQt" "$MODULES/Desktop-Environments/lxqt.sh" ;;
|
||
esac
|
||
fi
|
||
|
||
# ── Installation: applications ────────────────────────────────────────────────
|
||
# Same guard pattern as base components. Each line is independent; a missing
|
||
# module script will be caught by run_module()'s error handling rather than
|
||
# silently skipped — 'bash "$script"' will exit non-zero if the file is absent.
|
||
# BEGIN GENERATED MODULES: module-conflicts
|
||
if [[ " $SELECTED_APPS " == *" plymouth "* && " $SELECTED_APPS " == *" plymouth-custom "* ]]; then
|
||
warn "'plymouth' and 'plymouth-custom' are mutually exclusive — deselecting 'plymouth-custom'"
|
||
SELECTED_APPS=" $SELECTED_APPS "
|
||
SELECTED_APPS="${SELECTED_APPS/ plymouth-custom / }"
|
||
SELECTED_APPS="${SELECTED_APPS# }"
|
||
SELECTED_APPS="${SELECTED_APPS% }"
|
||
fi
|
||
if [[ " $SELECTED_APPS " == *" plymouth-custom "* && " $SELECTED_APPS " == *" plymouth "* ]]; then
|
||
warn "'plymouth-custom' and 'plymouth' are mutually exclusive — deselecting 'plymouth'"
|
||
SELECTED_APPS=" $SELECTED_APPS "
|
||
SELECTED_APPS="${SELECTED_APPS/ plymouth / }"
|
||
SELECTED_APPS="${SELECTED_APPS# }"
|
||
SELECTED_APPS="${SELECTED_APPS% }"
|
||
fi
|
||
# END GENERATED MODULES: module-conflicts
|
||
# BEGIN GENERATED MODULES: module-dispatch
|
||
[[ " $SELECTED_APPS " == *" ollama "* ]] && run_module "ollama" "$APPS/ollama.sh"
|
||
[[ " $SELECTED_APPS " == *" llama-cpp "* ]] && run_module "llama-cpp" "$APPS/llama-cpp.sh"
|
||
[[ " $SELECTED_APPS " == *" open-webui "* ]] && run_module "open-webui" "$APPS/open-webui.sh"
|
||
[[ " $SELECTED_APPS " == *" claude "* ]] && run_module "claude" "$APPS/claude.sh"
|
||
[[ " $SELECTED_APPS " == *" networking-cli "* ]] && run_module "networking-cli" "$APPS/networking-cli.sh"
|
||
[[ " $SELECTED_APPS " == *" disk-recovery "* ]] && run_module "disk-recovery" "$APPS/disk-recovery.sh"
|
||
[[ " $SELECTED_APPS " == *" himalaya "* ]] && run_module "himalaya" "$APPS/himalaya.sh"
|
||
[[ " $SELECTED_APPS " == *" mail-notmuch "* ]] && run_module "mail-notmuch" "$APPS/mail-notmuch.sh"
|
||
[[ " $SELECTED_APPS " == *" caldav-sync "* ]] && run_module "caldav-sync" "$APPS/caldav-sync.sh"
|
||
[[ " $SELECTED_APPS " == *" ssh-server "* ]] && run_module "ssh-server" "$APPS/ssh-server.sh"
|
||
[[ " $SELECTED_APPS " == *" wireshark "* ]] && run_module "wireshark" "$APPS/wireshark.sh"
|
||
[[ " $SELECTED_APPS " == *" anti-malware "* ]] && run_module "anti-malware" "$APPS/anti-malware.sh"
|
||
[[ " $SELECTED_APPS " == *" gnuplot "* ]] && run_module "gnuplot" "$APPS/gnuplot.sh"
|
||
[[ " $SELECTED_APPS " == *" blender-povray "* ]] && run_module "blender-povray" "$APPS/blender-povray.sh"
|
||
[[ " $SELECTED_APPS " == *" toot "* ]] && run_module "toot" "$APPS/toot.sh"
|
||
[[ " $SELECTED_APPS " == *" db-clients "* ]] && run_module "db-clients" "$APPS/db-clients.sh"
|
||
[[ " $SELECTED_APPS " == *" mysql "* ]] && run_module "mysql" "$APPS/mysql.sh"
|
||
[[ " $SELECTED_APPS " == *" productivity "* ]] && run_module "productivity" "$APPS/productivity.sh"
|
||
[[ " $SELECTED_APPS " == *" python "* ]] && run_module "python" "$APPS/python.sh"
|
||
[[ " $SELECTED_APPS " == *" k8s "* ]] && run_module "k8s" "$APPS/k8s.sh"
|
||
[[ " $SELECTED_APPS " == *" docker "* ]] && run_module "docker" "$APPS/docker.sh"
|
||
[[ " $SELECTED_APPS " == *" podman "* ]] && run_module "podman" "$APPS/podman.sh"
|
||
[[ " $SELECTED_APPS " == *" cockpit "* ]] && run_module "cockpit" "$APPS/cockpit.sh"
|
||
[[ " $SELECTED_APPS " == *" tlp "* ]] && run_module "tlp" "$APPS/tlp.sh"
|
||
[[ " $SELECTED_APPS " == *" butter "* ]] && run_module "butter" "$APPS/butter.sh"
|
||
[[ " $SELECTED_APPS " == *" localsend "* ]] && run_module "localsend" "$APPS/localsend.sh"
|
||
[[ " $SELECTED_APPS " == *" croc "* ]] && run_module "croc" "$APPS/croc.sh"
|
||
[[ " $SELECTED_APPS " == *" opendeck "* ]] && run_module "opendeck" "$APPS/opendeck.sh"
|
||
[[ " $SELECTED_APPS " == *" localtunnel "* ]] && run_module "localtunnel" "$APPS/localtunnel.sh"
|
||
[[ " $SELECTED_APPS " == *" timeshift "* ]] && run_module "timeshift" "$APPS/timeshift.sh"
|
||
[[ " $SELECTED_APPS " == *" zfs "* ]] && run_module "zfs" "$APPS/zfs.sh"
|
||
[[ " $SELECTED_APPS " == *" wprs "* ]] && run_module "wprs" "$APPS/wprs.sh"
|
||
[[ " $SELECTED_APPS " == *" plymouth "* ]] && run_module "plymouth" "$APPS/plymouth.sh"
|
||
[[ " $SELECTED_APPS " == *" plymouth-custom "* ]] && run_module "plymouth-custom" "$APPS/plymouth-custom.sh"
|
||
[[ " $SELECTED_APPS " == *" steam "* ]] && run_module "steam" "$APPS/steam.sh"
|
||
[[ " $SELECTED_APPS " == *" vesktop "* ]] && run_module "vesktop" "$APPS/vesktop.sh"
|
||
[[ " $SELECTED_APPS " == *" spotify "* ]] && run_module "spotify" "$APPS/spotify.sh"
|
||
[[ " $SELECTED_APPS " == *" prism "* ]] && run_module "prism" "$APPS/prism.sh"
|
||
[[ " $SELECTED_APPS " == *" vintagestory "* ]] && run_module "vintagestory" "$APPS/vintagestory.sh"
|
||
[[ " $SELECTED_APPS " == *" openarena "* ]] && run_module "openarena" "$APPS/openarena.sh"
|
||
[[ " $SELECTED_APPS " == *" tetris "* ]] && run_module "tetris" "$APPS/tetris.sh"
|
||
[[ " $SELECTED_APPS " == *" doom "* ]] && run_module "doom" "$APPS/doom.sh"
|
||
[[ " $SELECTED_APPS " == *" sauerbraten "* ]] && run_module "sauerbraten" "$APPS/sauerbraten.sh"
|
||
[[ " $SELECTED_APPS " == *" stuntrally "* ]] && run_module "stuntrally" "$APPS/stuntrally.sh"
|
||
[[ " $SELECTED_APPS " == *" onlyoffice "* ]] && run_module "onlyoffice" "$APPS/onlyoffice.sh"
|
||
[[ " $SELECTED_APPS " == *" xournal "* ]] && run_module "xournal" "$APPS/xournal.sh"
|
||
[[ " $SELECTED_APPS " == *" rnote "* ]] && run_module "rnote" "$APPS/rnote.sh"
|
||
[[ " $SELECTED_APPS " == *" obsidian "* ]] && run_module "obsidian" "$APPS/obsidian.sh"
|
||
[[ " $SELECTED_APPS " == *" tangent-notes "* ]] && run_module "tangent-notes" "$APPS/tangent-notes.sh"
|
||
[[ " $SELECTED_APPS " == *" ffmpeg "* ]] && run_module "ffmpeg" "$APPS/ffmpeg.sh"
|
||
[[ " $SELECTED_APPS " == *" sox "* ]] && run_module "sox" "$APPS/sox.sh"
|
||
[[ " $SELECTED_APPS " == *" imagemagick "* ]] && run_module "imagemagick" "$APPS/imagemagick.sh"
|
||
[[ " $SELECTED_APPS " == *" yt-dlp "* ]] && run_module "yt-dlp" "$APPS/yt-dlp.sh"
|
||
[[ " $SELECTED_APPS " == *" gimp "* ]] && run_module "gimp" "$APPS/gimp.sh"
|
||
[[ " $SELECTED_APPS " == *" inkscape "* ]] && run_module "inkscape" "$APPS/inkscape.sh"
|
||
[[ " $SELECTED_APPS " == *" krita "* ]] && run_module "krita" "$APPS/krita.sh"
|
||
[[ " $SELECTED_APPS " == *" kdenlive "* ]] && run_module "kdenlive" "$APPS/kdenlive.sh"
|
||
[[ " $SELECTED_APPS " == *" openshot "* ]] && run_module "openshot" "$APPS/openshot.sh"
|
||
[[ " $SELECTED_APPS " == *" shotcut "* ]] && run_module "shotcut" "$APPS/shotcut.sh"
|
||
[[ " $SELECTED_APPS " == *" ardour "* ]] && run_module "ardour" "$APPS/ardour.sh"
|
||
[[ " $SELECTED_APPS " == *" audacity "* ]] && run_module "audacity" "$APPS/audacity.sh"
|
||
[[ " $SELECTED_APPS " == *" lmms "* ]] && run_module "lmms" "$APPS/lmms.sh"
|
||
[[ " $SELECTED_APPS " == *" mixxx "* ]] && run_module "mixxx" "$APPS/mixxx.sh"
|
||
[[ " $SELECTED_APPS " == *" cecilia "* ]] && run_module "cecilia" "$APPS/cecilia.sh"
|
||
[[ " $SELECTED_APPS " == *" chromium "* ]] && run_module "chromium" "$APPS/chromium.sh"
|
||
[[ " $SELECTED_APPS " == *" firefox-browser "* ]] && run_module "firefox-browser" "$APPS/firefox-browser.sh"
|
||
[[ " $SELECTED_APPS " == *" zen-browser "* ]] && run_module "zen-browser" "$APPS/zen-browser.sh"
|
||
[[ " $SELECTED_APPS " == *" nyxt "* ]] && run_module "nyxt" "$APPS/nyxt.sh"
|
||
[[ " $SELECTED_APPS " == *" librewolf "* ]] && run_module "librewolf" "$APPS/librewolf.sh"
|
||
[[ " $SELECTED_APPS " == *" min-browser "* ]] && run_module "min-browser" "$APPS/min-browser.sh"
|
||
[[ " $SELECTED_APPS " == *" vscodium "* ]] && run_module "vscodium" "$APPS/vscodium.sh"
|
||
[[ " $SELECTED_APPS " == *" zed-ide "* ]] && run_module "zed-ide" "$APPS/zed-ide.sh"
|
||
[[ " $SELECTED_APPS " == *" geany "* ]] && run_module "geany" "$APPS/geany.sh"
|
||
[[ " $SELECTED_APPS " == *" codeblocks "* ]] && run_module "codeblocks" "$APPS/codeblocks.sh"
|
||
[[ " $SELECTED_APPS " == *" kate "* ]] && run_module "kate" "$APPS/kate.sh"
|
||
[[ " $SELECTED_APPS " == *" rdp-client "* ]] && run_module "rdp-client" "$APPS/rdp-client.sh"
|
||
[[ " $SELECTED_APPS " == *" lamco-rdp-server "* ]] && run_module "lamco-rdp-server" "$APPS/lamco-rdp-server.sh"
|
||
[[ " $SELECTED_APPS " == *" qemu "* ]] && run_module "qemu" "$APPS/qemu.sh"
|
||
[[ " $SELECTED_APPS " == *" freeipa-client "* ]] && run_module "freeipa-client" "$APPS/freeipa-client.sh"
|
||
[[ " $SELECTED_APPS " == *" freeipa-server "* ]] && run_module "freeipa-server" "$APPS/freeipa-server.sh"
|
||
# END GENERATED MODULES: module-dispatch
|
||
|
||
# ── Colorway (final step) ─────────────────────────────────────────────────────
|
||
# Read defaults from repo colors.conf for pre-population.
|
||
# Running after all modules ensures apply-theme.sh can re-process configs
|
||
# that were just symlinked or written by the DE/app modules.
|
||
declare -A _cdef
|
||
if [[ -f "$DOTFILES_DIR/colors.conf" ]]; then
|
||
# Parse 'KEY=VALUE' lines, skipping comments and blank lines.
|
||
# k="${k%%[[:space:]]*}" strips any trailing whitespace from the key.
|
||
# v="${v%%#*}" strips inline comments; v="${v//[[:space:]]/}" removes all spaces;
|
||
# v="${v^^}" uppercases the hex value so comparisons are case-insensitive.
|
||
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
|
||
# Fall back to hard-coded defaults if colors.conf is absent or missing a key.
|
||
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}"
|
||
|
||
_write_colors_conf() {
|
||
# Write a normalized colors.conf to a temporary path.
|
||
# ${t^^} uppercases each hex value so apply-theme.sh always sees consistent casing.
|
||
# The file is written to TMP_D so it never clobbers the repo's own 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.
|
||
# The concatenated string test is a concise way to check if at least one
|
||
# color field is non-empty without needing five separate [[ -n ]] guards.
|
||
if [[ -n "$AF_COLOR_TEXT$AF_COLOR_BG$AF_COLOR_HIGHLIGHT$AF_COLOR_DARK$AF_COLOR_RED" ]]; then
|
||
TMP_COLORS="$TMP_D/colors.conf"
|
||
# Per-field fallback: if the answerfile omits a color, use the repo default
|
||
# so apply-theme.sh always receives a complete, five-color config file.
|
||
_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"
|
||
# '|| true' suppresses a non-zero exit from apply-theme.sh so a colorway
|
||
# failure never aborts an otherwise successful unattended install.
|
||
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=""
|
||
|
||
if [[ -n "$COLORWAY_RAW" ]]; then
|
||
# mapfile reads the newline-separated form output into an array.
|
||
# _cv[0] = COLOR_TEXT line, _cv[1] = COLOR_BG, ... through _cv[4].
|
||
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 invoke apply-theme.sh if at least one color actually changed.
|
||
# ${VAR^^} uppercases before comparison to treat 'e40046' == 'E40046'.
|
||
# This avoids a redundant theme rebuild when the user just pressed Enter
|
||
# on every field without changing anything.
|
||
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
|
||
|
||
# ── Sync user config to /etc/skel ─────────────────────────────────────────────
|
||
# /etc/skel is the skeleton directory Linux copies to new user home dirs on creation.
|
||
# Propagating the post-install config here means any new user added to this machine
|
||
# automatically gets the full dotfiles environment without a manual copy step.
|
||
if [[ -d "$HOME/.config" ]]; then
|
||
printf "\n Syncing ~/.config to /etc/skel...\n"
|
||
sudo mkdir -p /etc/skel/.config
|
||
# '.' suffix on the source copies directory contents rather than the directory
|
||
# itself, making the merge non-destructive if /etc/skel/.config already exists.
|
||
sudo cp -r "$HOME/.config/." /etc/skel/.config/
|
||
fi
|
||
[[ -d "$HOME/.themes" ]] && { sudo mkdir -p /etc/skel/.themes; sudo cp -r "$HOME/.themes/." /etc/skel/.themes/; }
|
||
# Copy shell rc files to skel only if the user opted in to the dotfiles configs.
|
||
if [[ "$SHELL_RC" == "dotfiles" ]]; then
|
||
[[ -f "$HOME/.zshrc" ]] && sudo cp "$HOME/.zshrc" /etc/skel/.zshrc
|
||
[[ -f "$HOME/.bashrc" ]] && sudo cp "$HOME/.bashrc" /etc/skel/.bashrc
|
||
[[ -f "$HOME/.vimrc" ]] && sudo cp "$HOME/.vimrc" /etc/skel/.vimrc
|
||
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
|