634 lines
36 KiB
Bash
Executable File
634 lines
36 KiB
Bash
Executable File
#!/bin/bash
|
|
# simple-install.sh — TUI installer for the_miro's Arch dotfiles
|
|
|
|
set -uo pipefail
|
|
|
|
# ── Paths ─────────────────────────────────────────────────────────────────────
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DOTFILES_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
MODULES="$DOTFILES_DIR/setup/modules"
|
|
APPS="$MODULES/optional-Modules/apps"
|
|
LOG="$HOME/dotfiles-install.log"
|
|
TMP_D="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP_D"; tput reset 2>/dev/null || stty sane 2>/dev/null || true' EXIT INT TERM HUP
|
|
|
|
ANSWERFILE="${ANSWERFILE:-/answerfile.json}"
|
|
ANSWERFILE_MODE=false
|
|
[[ -f "$ANSWERFILE" ]] && ANSWERFILE_MODE=true
|
|
|
|
TITLE="the_miro's Arch Dotfiles"
|
|
|
|
# ── Terminal dimensions ───────────────────────────────────────────────────────
|
|
TERM_H=$(tput lines 2>/dev/null || echo 24)
|
|
TERM_W=$(tput cols 2>/dev/null || echo 80)
|
|
TUI_W=$(( TERM_W < 74 ? TERM_W - 2 : 72 ))
|
|
printf -v TUI_BAR '%.0s─' $(seq 1 "$TUI_W")
|
|
|
|
# ── ANSI color codes ──────────────────────────────────────────────────────────
|
|
R=$'\e[0m'; B=$'\e[1m'; D=$'\e[2m'; M=$'\e[1;35m'; CY=$'\e[1;36m'
|
|
|
|
# ── TUI primitives ────────────────────────────────────────────────────────────
|
|
# All display goes to /dev/tty so functions work correctly inside $() captures.
|
|
|
|
_hdr() {
|
|
printf '\e[2J\e[H' >/dev/tty
|
|
printf "${M} ┌%s┐${R}\n ${M}│ ${B}%-*s${R} ${M}│${R}\n ${M}└%s┘${R}\n\n" \
|
|
"$TUI_BAR" $(( TUI_W - 4 )) "$TITLE" "$TUI_BAR" >/dev/tty
|
|
}
|
|
|
|
_sep() { printf " ${D}─────────────────────────────────────────────${R}\n\n" >/dev/tty; }
|
|
|
|
# tui_msg TITLE MSG
|
|
tui_msg() {
|
|
_hdr
|
|
printf " ${M}${B}%s${R}\n" "$1" >/dev/tty; _sep
|
|
printf "%b\n\n" "$2" >/dev/tty
|
|
printf " ${D}Press Enter to continue...${R}" >/dev/tty; read -r </dev/tty
|
|
}
|
|
|
|
# tui_yesno TITLE MSG → 0=yes 1=no
|
|
tui_yesno() {
|
|
_hdr
|
|
printf " ${M}${B}%s${R}\n" "$1" >/dev/tty; _sep
|
|
printf "%b\n\n" "$2" >/dev/tty
|
|
local a
|
|
while true; do
|
|
printf " ${CY}[Y/n]${R} > " >/dev/tty; read -r a </dev/tty
|
|
case "${a,,}" in
|
|
y|yes|"") return 0 ;;
|
|
n|no) return 1 ;;
|
|
*) printf " Enter Y or N.\n" >/dev/tty ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# tui_input TITLE PROMPT [DEFAULT] → prints value to stdout
|
|
tui_input() {
|
|
_hdr
|
|
printf " ${M}${B}%s${R}\n" "$1" >/dev/tty; _sep
|
|
printf " %b\n" "$2" >/dev/tty
|
|
[[ -n "${3:-}" ]] && printf " ${D}(default: %s)${R}\n" "$3" >/dev/tty
|
|
printf "\n > " >/dev/tty; local v; read -r v </dev/tty
|
|
printf '%s' "${v:-${3:-}}"
|
|
}
|
|
|
|
# tui_checklist TITLE PROMPT tag desc state tag desc state ...
|
|
# state: "on" | "off" | "header" (header = section label, tag is ignored)
|
|
# Prints space-separated selected tags to stdout.
|
|
tui_checklist() {
|
|
local title="$1" prompt="$2"; shift 2
|
|
local -a _T _D _S
|
|
while [[ $# -ge 3 ]]; do _T+=("$1"); _D+=("$2"); _S+=("$3"); shift 3; done
|
|
local n=${#_T[@]}
|
|
|
|
while true; do
|
|
_hdr
|
|
printf " ${M}${B}%s${R}\n" "$title" >/dev/tty; _sep
|
|
printf " ${D}%s${R}\n\n" "$prompt" >/dev/tty
|
|
|
|
local -a sel=()
|
|
local i num=0
|
|
for (( i=0; i<n; i++ )); do
|
|
if [[ "${_S[$i]}" == "header" ]]; then
|
|
printf "\n ${M}── %s${R}\n" "${_D[$i]}" >/dev/tty
|
|
continue
|
|
fi
|
|
sel+=("$i"); (( num++ ))
|
|
if [[ "${_S[$i]}" == "on" ]]; then
|
|
printf " ${CY}%3d) [*] %-22s${R} %s\n" "$num" "${_T[$i]}" "${_D[$i]}" >/dev/tty
|
|
else
|
|
printf " %3d) [ ] %-22s %s\n" "$num" "${_T[$i]}" "${_D[$i]}" >/dev/tty
|
|
fi
|
|
done
|
|
local total_sel=${#sel[@]}
|
|
|
|
printf "\n ${D}Number(s) to toggle · 'a' all · 'n' none · Enter confirm:${R}\n > " >/dev/tty
|
|
local inp; read -r inp </dev/tty
|
|
|
|
[[ -z "$inp" ]] && break
|
|
case "$inp" in
|
|
a) for (( i=0; i<n; i++ )); do [[ "${_S[$i]}" != "header" ]] && _S[$i]="on"; done; continue ;;
|
|
n) for (( i=0; i<n; i++ )); do [[ "${_S[$i]}" != "header" ]] && _S[$i]="off"; done; continue ;;
|
|
esac
|
|
for tok in $inp; do
|
|
if [[ "$tok" =~ ^[0-9]+$ ]]; then
|
|
local k=$(( tok - 1 ))
|
|
(( k >= 0 && k < total_sel )) || continue
|
|
local idx="${sel[$k]}"
|
|
[[ "${_S[$idx]}" == "on" ]] && _S[$idx]="off" || _S[$idx]="on"
|
|
fi
|
|
done
|
|
done
|
|
|
|
local res=""
|
|
for (( i=0; i<n; i++ )); do [[ "${_S[$i]}" == "on" ]] && res+="${_T[$i]} "; done
|
|
printf '%s' "${res% }"
|
|
}
|
|
|
|
# tui_menu TITLE PROMPT tag desc tag desc ...
|
|
# Prints selected tag to stdout; loops until a valid number is entered.
|
|
tui_menu() {
|
|
local title="$1" prompt="$2"; shift 2
|
|
local -a _T _D
|
|
while [[ $# -ge 2 ]]; do _T+=("$1"); _D+=("$2"); shift 2; done
|
|
local n=${#_T[@]}
|
|
while true; do
|
|
_hdr
|
|
printf " ${M}${B}%s${R}\n" "$title" >/dev/tty; _sep
|
|
printf " ${D}%s${R}\n\n" "$prompt" >/dev/tty
|
|
local i
|
|
for (( i=0; i<n; i++ )); do
|
|
printf " %3d) %-22s %s\n" $(( i+1 )) "${_T[$i]}" "${_D[$i]}" >/dev/tty
|
|
done
|
|
printf "\n ${D}Enter number:${R}\n > " >/dev/tty
|
|
local inp; read -r inp </dev/tty
|
|
if [[ "$inp" =~ ^[0-9]+$ ]] && (( inp >= 1 && inp <= n )); then
|
|
printf '%s' "${_T[$(( inp-1 ))]}"; return 0
|
|
fi
|
|
printf " Invalid selection.\n" >/dev/tty; sleep 0.4
|
|
done
|
|
}
|
|
|
|
# ── State ─────────────────────────────────────────────────────────────────────
|
|
STEP=0
|
|
TOTAL=0
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
require_jq() {
|
|
command -v jq &>/dev/null && return
|
|
echo "jq not found — installing..."
|
|
sudo pacman -S --noconfirm jq || { echo "Failed to install jq."; exit 1; }
|
|
}
|
|
|
|
die() {
|
|
clear
|
|
printf "\n Error: %s\n\n" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
log_sep() {
|
|
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
|
|
printf "\n${M} [$STEP/$TOTAL] %s${R}\n" "$label"
|
|
printf "${D} ─────────────────────────────────────────────${R}\n\n"
|
|
|
|
local rc=0
|
|
bash "$script" 2>&1 | tee -a "$LOG" || rc=${PIPESTATUS[0]}
|
|
|
|
if [[ $rc -ne 0 ]]; then
|
|
if [[ $ANSWERFILE_MODE == true ]]; then
|
|
printf "\n Warning: %s exited with code %d — continuing.\n" "$label" "$rc" | tee -a "$LOG"
|
|
else
|
|
tui_yesno " Module Failed " \
|
|
" $label exited with code $rc.\n\n Continue anyway?" \
|
|
|| { clear; exit 1; }
|
|
fi
|
|
fi
|
|
}
|
|
|
|
count_steps() {
|
|
local c="$1" de="$2" a="${3:-}"
|
|
TOTAL=0
|
|
[[ "$c" == *"pkg"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$c" == *"core"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$c" == *"svc"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$c" == *"shell"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$de" != "none" ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$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" == *"gnuplot"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"povray"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"blender"* ]] && 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" == *"yt-dlp"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"sox"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"imagemagick"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"ffmpeg"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"localtunnel"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"butter"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"tlp"* ]] && 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" == *"localsend"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"croc"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"onlyoffice"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"wireshark"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"k8s"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"docker"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"podman"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"cockpit"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"ssh-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"freeipa-client"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"freeipa-server"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"freeipa-image"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"python"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"zfs"* ]] && TOTAL=$(( TOTAL + 1 ))
|
|
[[ "$a" == *"wprs"* ]] && 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 ))
|
|
}
|
|
|
|
# ── Answerfile ────────────────────────────────────────────────────────────────
|
|
AF_HOSTNAME=""
|
|
AF_COMPONENTS=""
|
|
AF_DE="none"
|
|
AF_APPS=""
|
|
AF_COLOR_TEXT=""
|
|
AF_COLOR_BG=""
|
|
AF_COLOR_HIGHLIGHT=""
|
|
AF_COLOR_DARK=""
|
|
AF_COLOR_RED=""
|
|
|
|
load_answerfile() {
|
|
require_jq
|
|
AF_HOSTNAME=$(jq -r '.hostname // ""' "$ANSWERFILE")
|
|
AF_COMPONENTS=$(jq -r '(.components // []) | join(" ")' "$ANSWERFILE")
|
|
AF_DE=$(jq -r '.desktop_environment // "none"' "$ANSWERFILE")
|
|
AF_APPS=$(jq -r '(.apps // []) | join(" ")' "$ANSWERFILE")
|
|
AF_COLOR_TEXT=$(jq -r '.colors.COLOR_TEXT // ""' "$ANSWERFILE")
|
|
AF_COLOR_BG=$(jq -r '.colors.COLOR_BG // ""' "$ANSWERFILE")
|
|
AF_COLOR_HIGHLIGHT=$(jq -r '.colors.COLOR_HIGHLIGHT // ""' "$ANSWERFILE")
|
|
AF_COLOR_DARK=$(jq -r '.colors.COLOR_DARK // ""' "$ANSWERFILE")
|
|
AF_COLOR_RED=$(jq -r '.colors.COLOR_RED // ""' "$ANSWERFILE")
|
|
}
|
|
|
|
# ── MAC address helper ────────────────────────────────────────────────────────
|
|
get_mac_suffix() {
|
|
local mac
|
|
mac=$(ip link show 2>/dev/null \
|
|
| awk '/^[0-9]+: [^l][^o]/{iface=1} iface && /link\/ether/{print $2; iface=0; exit}')
|
|
printf '%s' "${mac//:/}"
|
|
}
|
|
|
|
# ── Preflight ─────────────────────────────────────────────────────────────────
|
|
[[ $EUID -eq 0 ]] && die "Run as your normal user (not root)."
|
|
command -v pacman &>/dev/null || die "pacman not found — Arch Linux required."
|
|
|
|
if $ANSWERFILE_MODE; then
|
|
load_answerfile
|
|
printf "Answerfile mode: %s\n" "$ANSWERFILE" | tee -a "$LOG"
|
|
fi
|
|
|
|
# ── Network check ─────────────────────────────────────────────────────────────
|
|
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
|
|
if $ANSWERFILE_MODE; then
|
|
printf "Warning: no internet connection detected.\n" | tee -a "$LOG"
|
|
else
|
|
tui_msg " No Network Detected " \
|
|
" No internet connection found.\n\n nmtui will open so you can configure networking.\n Close nmtui when done to continue the installer."
|
|
nmtui
|
|
if ! ping -c1 -W3 archlinux.org &>/dev/null; then
|
|
tui_yesno " Still Offline " \
|
|
" Still no internet connection.\n\n Packages cannot be downloaded without network access.\n\n Continue anyway?" \
|
|
|| { clear; echo "Aborted — no network."; exit 1; }
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
> "$LOG"
|
|
printf "Dotfiles install: %s\nDotfiles dir: %s\n" "$(date)" "$DOTFILES_DIR" >> "$LOG"
|
|
|
|
# ── Welcome ───────────────────────────────────────────────────────────────────
|
|
if ! $ANSWERFILE_MODE; then
|
|
tui_msg " Welcome " \
|
|
" 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"
|
|
fi
|
|
|
|
# ── Hostname ──────────────────────────────────────────────────────────────────
|
|
HOSTNAME_SET=""
|
|
if $ANSWERFILE_MODE; then
|
|
if [[ -n "$AF_HOSTNAME" ]]; then
|
|
MAC=$(get_mac_suffix)
|
|
HOSTNAME_SET="${AF_HOSTNAME}-${MAC}"
|
|
printf "Hostname (from answerfile + MAC): %s\n" "$HOSTNAME_SET" | tee -a "$LOG"
|
|
fi
|
|
else
|
|
HOSTNAME_SET=$(tui_input " Hostname " \
|
|
"Hostname for this machine (leave blank to keep default)." "")
|
|
fi
|
|
|
|
if [[ -n "$HOSTNAME_SET" ]]; then
|
|
sudo hostnamectl set-hostname "$HOSTNAME_SET" 2>/dev/null \
|
|
|| echo "$HOSTNAME_SET" | sudo tee /etc/hostname > /dev/null
|
|
printf "Hostname set: %s\n" "$HOSTNAME_SET" >> "$LOG"
|
|
fi
|
|
|
|
# ── Component selection ───────────────────────────────────────────────────────
|
|
if $ANSWERFILE_MODE; then
|
|
COMPONENTS="$AF_COMPONENTS"
|
|
else
|
|
COMPONENTS=$(tui_checklist " Select Components " \
|
|
"Number to toggle · Enter to confirm" \
|
|
"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)
|
|
fi
|
|
|
|
# ── DE selection ──────────────────────────────────────────────────────────────
|
|
if $ANSWERFILE_MODE; then
|
|
DE="$AF_DE"
|
|
else
|
|
DE=$(tui_menu " Desktop Environment " \
|
|
"Select a desktop environment:" \
|
|
"hyprlua" "HyprLua — Hyprland with Lua config (recommended)" \
|
|
"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")
|
|
fi
|
|
|
|
# ── Apps selection ────────────────────────────────────────────────────────────
|
|
if $ANSWERFILE_MODE; then
|
|
SELECTED_APPS="$AF_APPS"
|
|
else
|
|
SELECTED_APPS=$(tui_checklist " Applications " \
|
|
"Optional applications — number to toggle, Enter to confirm:" \
|
|
\
|
|
"" "AI / LLM" header \
|
|
"ollama" "Ollama local LLM runner + API server" off \
|
|
"llama-cpp" "llama.cpp standalone inference CLI + server" off \
|
|
"open-webui" "Open WebUI browser UI for Ollama / LLM backends" off \
|
|
"claude" "Claude Code Anthropic CLI via npm" off \
|
|
\
|
|
"" "Networking & Disk" header \
|
|
"networking-cli" "Networking CLI nmap · nethogs · mitmproxy · httpie" off \
|
|
"disk-recovery" "Disk Recovery ddrescue · f3" off \
|
|
\
|
|
"" "CLI Tools" header \
|
|
"himalaya" "Himalaya terminal email client (AUR)" off \
|
|
"gnuplot" "Gnuplot scientific plotting" off \
|
|
"povray" "POV-Ray ray-tracing renderer" off \
|
|
"blender" "Blender 3D creation suite" off \
|
|
"toot" "toot Mastodon CLI client (AUR)" off \
|
|
"db-clients" "DB Clients pgcli · mycli" off \
|
|
"mysql" "MySQL / MariaDB mariadb server + setup" off \
|
|
"productivity" "Productivity taskwarrior · watson · jrnl" off \
|
|
"yt-dlp" "yt-dlp YouTube / media downloader" off \
|
|
"sox" "SoX audio processing toolkit" off \
|
|
"imagemagick" "ImageMagick image manipulation" off \
|
|
"ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \
|
|
"localtunnel" "LocalTunnel expose localhost via tunnel" off \
|
|
"butter" "butter btrfs snapshot backup (AUR)" off \
|
|
"tlp" "TLP laptop power management" off \
|
|
\
|
|
"" "Gaming" header \
|
|
"steam" "Steam gaming platform" off \
|
|
"vesktop" "Vesktop Discord + Vencord theme" off \
|
|
"spotify" "Spotify launcher + Spicetify theming" off \
|
|
"prism" "PrismLauncher Minecraft launcher (Flatpak)" off \
|
|
"vintagestory" "Vintage Story survival game (AUR)" off \
|
|
\
|
|
"" "File Transfer & Office" header \
|
|
"localsend" "LocalSend LAN file transfer (AUR)" off \
|
|
"croc" "croc cross-platform file transfer" off \
|
|
"onlyoffice" "OnlyOffice office suite (AUR)" off \
|
|
\
|
|
"" "Security" header \
|
|
"wireshark" "Wireshark network packet analyser (GUI)" off \
|
|
\
|
|
"" "Containers & Server" header \
|
|
"k8s" "Kubernetes tools kubectl · podman-desktop" off \
|
|
"docker" "Docker docker · docker-compose" off \
|
|
"podman" "Podman rootless containers · buildah" off \
|
|
"cockpit" "Cockpit web UI · machines · podman" off \
|
|
"ssh-server" "SSH server openssh · key-auth · enabled" off \
|
|
"freeipa-client" "FreeIPA Client sssd + ipa-client-install + enrollment" off \
|
|
"freeipa-server" "FreeIPA Server interactive server setup + client gen" off \
|
|
"freeipa-image" "FreeIPA Image OCI/LXC/Proxmox LXC builder + Keycloak" off \
|
|
\
|
|
"" "Dev & System" header \
|
|
"python" "Python tools pyright · pipx · pynvim" off \
|
|
"zfs" "ZFS zfs-dkms kernel module" off \
|
|
"wprs" "WPRS wprs-git (AUR)" off \
|
|
\
|
|
"" "Browsers" header \
|
|
"chromium" "Chromium open-source browser (official)" off \
|
|
"firefox-browser" "Firefox Mozilla browser (official)" off \
|
|
"zen-browser" "Zen Browser Firefox-based privacy browser (AUR)" off \
|
|
"nyxt" "Nyxt keyboard-driven browser (AUR)" off \
|
|
"librewolf" "LibreWolf hardened Firefox fork (AUR)" off \
|
|
"min-browser" "Min minimal Electron browser (AUR)" off \
|
|
\
|
|
"" "IDEs & Editors" header \
|
|
"vscodium" "VSCodium telemetry-free VS Code (AUR)" off \
|
|
"zed-ide" "Zed high-performance Rust IDE (official)" off \
|
|
"geany" "Geany lightweight IDE + plugins (official)" off \
|
|
"codeblocks" "Code::Blocks C/C++ IDE (official)" off \
|
|
"kate" "Kate KDE advanced text editor (official)" off \
|
|
\
|
|
"" "Remote & Virtualization" header \
|
|
"rdp-client" "RDP Client Remmina + FreeRDP + VNC plugins" off \
|
|
"lamco-rdp-server" "Lamco RDP Srv native Wayland RDP server (AUR, Rust)" off \
|
|
"qemu" "QEMU/KVM full virt stack + virt-manager GUI" off)
|
|
fi
|
|
|
|
# ── Confirmation (interactive mode only) ──────────────────────────────────────
|
|
if ! $ANSWERFILE_MODE; then
|
|
SUMMARY=""
|
|
[[ -n "$HOSTNAME_SET" ]] && SUMMARY+=" ✦ Hostname: $HOSTNAME_SET\n"
|
|
[[ "$COMPONENTS" == *"pkg"* ]] && SUMMARY+=" ✦ Package managers (yay, nvm, rust)\n"
|
|
[[ "$COMPONENTS" == *"core"* ]] && SUMMARY+=" ✦ Core packages\n"
|
|
[[ "$COMPONENTS" == *"svc"* ]] && SUMMARY+=" ✦ Core services\n"
|
|
[[ "$COMPONENTS" == *"shell"* ]] && SUMMARY+=" ✦ Shell setup\n"
|
|
[[ "$DE" != "none" ]] && SUMMARY+=" ✦ Desktop environment: $DE\n"
|
|
|
|
if [[ -n "$SELECTED_APPS" ]]; then
|
|
SUMMARY+="\n Applications:\n"
|
|
for _app in ollama llama-cpp open-webui claude networking-cli disk-recovery \
|
|
himalaya gnuplot povray blender toot db-clients mysql productivity \
|
|
yt-dlp sox imagemagick ffmpeg localtunnel butter tlp steam vesktop \
|
|
spotify prism vintagestory localsend croc onlyoffice wireshark k8s \
|
|
docker podman cockpit ssh-server freeipa-client freeipa-server \
|
|
freeipa-image python zfs wprs chromium firefox-browser zen-browser \
|
|
nyxt librewolf min-browser vscodium zed-ide geany codeblocks kate \
|
|
rdp-client lamco-rdp-server qemu; do
|
|
[[ "$SELECTED_APPS" == *"$_app"* ]] && SUMMARY+=" ✦ $_app\n"
|
|
done
|
|
fi
|
|
|
|
tui_yesno " Confirm Installation " \
|
|
" Components to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \
|
|
|| { clear; echo "Aborted."; exit 0; }
|
|
fi
|
|
|
|
count_steps "$COMPONENTS" "$DE" "$SELECTED_APPS"
|
|
|
|
# ── Installation: base components ─────────────────────────────────────────────
|
|
[[ "$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"
|
|
|
|
if [[ "$DE" != "none" ]]; then
|
|
case "$DE" in
|
|
hyprlua) run_module "HyprLua" "$MODULES/Desktop-Environments/hyprlua.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 ────────────────────────────────────────────────
|
|
[[ "$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 Code" "$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" == *"gnuplot"* ]] && run_module "Gnuplot" "$APPS/gnuplot.sh"
|
|
[[ "$SELECTED_APPS" == *"povray"* ]] && run_module "POV-Ray" "$APPS/povray.sh"
|
|
[[ "$SELECTED_APPS" == *"blender"* ]] && run_module "Blender" "$APPS/blender.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 / MariaDB" "$APPS/mysql.sh"
|
|
[[ "$SELECTED_APPS" == *"productivity"* ]] && run_module "Productivity" "$APPS/productivity.sh"
|
|
[[ "$SELECTED_APPS" == *"yt-dlp"* ]] && run_module "yt-dlp" "$APPS/yt-dlp.sh"
|
|
[[ "$SELECTED_APPS" == *"sox"* ]] && run_module "SoX" "$APPS/sox.sh"
|
|
[[ "$SELECTED_APPS" == *"imagemagick"* ]] && run_module "ImageMagick" "$APPS/imagemagick.sh"
|
|
[[ "$SELECTED_APPS" == *"ffmpeg"* ]] && run_module "FFmpeg extras" "$APPS/ffmpeg.sh"
|
|
[[ "$SELECTED_APPS" == *"localtunnel"* ]] && run_module "LocalTunnel" "$APPS/localtunnel.sh"
|
|
[[ "$SELECTED_APPS" == *"butter"* ]] && run_module "butter" "$APPS/butter.sh"
|
|
[[ "$SELECTED_APPS" == *"tlp"* ]] && run_module "TLP" "$APPS/tlp.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 "PrismLauncher" "$APPS/prismlauncher.sh"
|
|
[[ "$SELECTED_APPS" == *"vintagestory"* ]] && run_module "Vintage Story" "$APPS/vintagestory.sh"
|
|
[[ "$SELECTED_APPS" == *"localsend"* ]] && run_module "LocalSend" "$APPS/localsend.sh"
|
|
[[ "$SELECTED_APPS" == *"croc"* ]] && run_module "croc" "$APPS/croc.sh"
|
|
[[ "$SELECTED_APPS" == *"onlyoffice"* ]] && run_module "OnlyOffice" "$APPS/onlyoffice.sh"
|
|
[[ "$SELECTED_APPS" == *"wireshark"* ]] && run_module "Wireshark" "$APPS/wireshark.sh"
|
|
[[ "$SELECTED_APPS" == *"k8s"* ]] && run_module "Kubernetes Tools" "$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" == *"ssh-server"* ]] && run_module "SSH Server" "$APPS/ssh-server.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"
|
|
[[ "$SELECTED_APPS" == *"freeipa-image"* ]] && run_module "FreeIPA Image" "$APPS/freeipa-image-builder.sh"
|
|
[[ "$SELECTED_APPS" == *"python"* ]] && run_module "Python Tools" "$MODULES/optional-Modules/python.sh"
|
|
[[ "$SELECTED_APPS" == *"zfs"* ]] && run_module "ZFS" "$MODULES/optional-Modules/zfs.sh"
|
|
[[ "$SELECTED_APPS" == *"wprs"* ]] && run_module "WPRS" "$MODULES/optional-Modules/wprs.sh"
|
|
[[ "$SELECTED_APPS" == *"chromium"* ]] && run_module "Chromium" "$APPS/chromium.sh"
|
|
[[ "$SELECTED_APPS" == *"firefox-browser"* ]] && run_module "Firefox" "$APPS/firefox.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.sh"
|
|
[[ "$SELECTED_APPS" == *"geany"* ]] && run_module "Geany" "$APPS/geany.sh"
|
|
[[ "$SELECTED_APPS" == *"codeblocks"* ]] && run_module "Code::Blocks" "$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/KVM" "$APPS/qemu.sh"
|
|
|
|
# ── Colorway (final step) ─────────────────────────────────────────────────────
|
|
declare -A _cdef
|
|
if [[ -f "$DOTFILES_DIR/colors.conf" ]]; then
|
|
while IFS='=' read -r k v; do
|
|
k="${k%%[[:space:]]*}"
|
|
[[ "$k" =~ ^[[:space:]]*# || -z "$k" ]] && continue
|
|
v="${v%%#*}"; v="${v//[[:space:]]/}"; v="${v^^}"
|
|
_cdef[$k]="$v"
|
|
done < "$DOTFILES_DIR/colors.conf"
|
|
fi
|
|
DEF_TEXT="${_cdef[COLOR_TEXT]:-D6ABAB}"
|
|
DEF_BG="${_cdef[COLOR_BG]:-1A1A1A}"
|
|
DEF_HIGHLIGHT="${_cdef[COLOR_HIGHLIGHT]:-E40046}"
|
|
DEF_DARK="${_cdef[COLOR_DARK]:-5018DD}"
|
|
DEF_RED="${_cdef[COLOR_RED]:-F50505}"
|
|
|
|
_write_colors_conf() {
|
|
local out="$1" t="$2" b="$3" h="$4" d="$5" r="$6"
|
|
printf 'COLOR_TEXT=%s\nCOLOR_BG=%s\nCOLOR_HIGHLIGHT=%s\nCOLOR_DARK=%s\nCOLOR_RED=%s\n' \
|
|
"${t^^}" "${b^^}" "${h^^}" "${d^^}" "${r^^}" > "$out"
|
|
}
|
|
|
|
if $ANSWERFILE_MODE; then
|
|
if [[ -n "$AF_COLOR_TEXT$AF_COLOR_BG$AF_COLOR_HIGHLIGHT$AF_COLOR_DARK$AF_COLOR_RED" ]]; then
|
|
TMP_COLORS="$TMP_D/colors.conf"
|
|
_write_colors_conf "$TMP_COLORS" \
|
|
"${AF_COLOR_TEXT:-$DEF_TEXT}" \
|
|
"${AF_COLOR_BG:-$DEF_BG}" \
|
|
"${AF_COLOR_HIGHLIGHT:-$DEF_HIGHLIGHT}" \
|
|
"${AF_COLOR_DARK:-$DEF_DARK}" \
|
|
"${AF_COLOR_RED:-$DEF_RED}"
|
|
printf "Applying colorway from answerfile...\n" | tee -a "$LOG"
|
|
bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true
|
|
fi
|
|
else
|
|
_hdr
|
|
printf " ${M}${B} Colorway (optional) ${R}\n" >/dev/tty; _sep
|
|
printf " Customize theme colors — bare 6-digit hex, no #.\n Press Enter to keep each default.\n\n" >/dev/tty
|
|
|
|
printf " COLOR_TEXT ${D}[%s]${R} > " "$DEF_TEXT" >/dev/tty; read -r N_TEXT </dev/tty; N_TEXT="${N_TEXT:-$DEF_TEXT}"
|
|
printf " COLOR_BG ${D}[%s]${R} > " "$DEF_BG" >/dev/tty; read -r N_BG </dev/tty; N_BG="${N_BG:-$DEF_BG}"
|
|
printf " COLOR_HIGHLIGHT ${D}[%s]${R} > " "$DEF_HIGHLIGHT" >/dev/tty; read -r N_HIGHLIGHT </dev/tty; N_HIGHLIGHT="${N_HIGHLIGHT:-$DEF_HIGHLIGHT}"
|
|
printf " COLOR_DARK ${D}[%s]${R} > " "$DEF_DARK" >/dev/tty; read -r N_DARK </dev/tty; N_DARK="${N_DARK:-$DEF_DARK}"
|
|
printf " COLOR_RED ${D}[%s]${R} > " "$DEF_RED" >/dev/tty; read -r N_RED </dev/tty; N_RED="${N_RED:-$DEF_RED}"
|
|
|
|
if [[ "${N_TEXT^^}" != "$DEF_TEXT" || \
|
|
"${N_BG^^}" != "$DEF_BG" || \
|
|
"${N_HIGHLIGHT^^}" != "$DEF_HIGHLIGHT" || \
|
|
"${N_DARK^^}" != "$DEF_DARK" || \
|
|
"${N_RED^^}" != "$DEF_RED" ]]; then
|
|
TMP_COLORS="$TMP_D/colors.conf"
|
|
_write_colors_conf "$TMP_COLORS" "$N_TEXT" "$N_BG" "$N_HIGHLIGHT" "$N_DARK" "$N_RED"
|
|
clear
|
|
printf "\n${M} Applying colorway...${R}\n\n"
|
|
bash "$DOTFILES_DIR/apply-theme.sh" "$TMP_COLORS" 2>&1 | tee -a "$LOG" || true
|
|
fi
|
|
fi
|
|
|
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
if $ANSWERFILE_MODE; then
|
|
printf "\nDone. Log: %s\n" "$LOG"
|
|
else
|
|
tui_msg " 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"
|
|
fi
|