#!/bin/bash # install-modules.sh — install optional modules on an already-configured system set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MODULES="$SCRIPT_DIR/modules" APPS="$MODULES/optional-Modules/apps" LOG="$HOME/dotfiles-modules.log" TMP_D="$(mktemp -d)" trap 'rm -rf "$TMP_D"' EXIT BACKTITLE="the_miro's Arch Dotfiles — Module Installer" export DIALOGRC="$TMP_D/dialogrc" cat > "$DIALOGRC" <<'EOF' use_shadow = ON use_colors = ON screen_color = (BLACK,BLACK,ON) shadow_color = (BLACK,BLACK,ON) title_color = (MAGENTA,BLACK,ON) border_color = (MAGENTA,BLACK,ON) button_active_color = (BLACK,MAGENTA,ON) button_inactive_color = (WHITE,BLACK,OFF) button_key_active_color = (BLACK,CYAN,ON) button_key_inactive_color = (CYAN,BLACK,ON) button_label_active_color = (BLACK,MAGENTA,ON) button_label_inactive_color = (WHITE,BLACK,OFF) inputbox_color = (WHITE,BLACK,OFF) inputbox_border_color = (MAGENTA,BLACK,ON) menubox_color = (WHITE,BLACK,OFF) menubox_border_color = (MAGENTA,BLACK,ON) item_color = (WHITE,BLACK,OFF) item_selected_color = (BLACK,MAGENTA,ON) tag_color = (CYAN,BLACK,ON) tag_selected_color = (BLACK,CYAN,ON) tag_key_color = (CYAN,BLACK,ON) tag_key_selected_color = (BLACK,CYAN,ON) check_color = (WHITE,BLACK,OFF) check_selected_color = (BLACK,MAGENTA,ON) uarrow_color = (MAGENTA,BLACK,ON) darrow_color = (MAGENTA,BLACK,ON) EOF STEP=0 TOTAL=0 require_dialog() { command -v dialog &>/dev/null && return echo "dialog not found — installing..." sudo pacman -S --noconfirm dialog || { echo "Failed to install dialog."; exit 1; } } 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\033[1;35m [$STEP/$TOTAL] %s\033[0m\n" "$label" printf "\033[35m ─────────────────────────────────────────────\033[0m\n\n" local rc=0 bash "$script" 2>&1 | tee -a "$LOG" || rc=${PIPESTATUS[0]} if [[ $rc -ne 0 ]]; then dialog --backtitle "$BACKTITLE" \ --title " Module Failed " \ --yesno "$label exited with code $rc.\n\nContinue anyway?" 8 54 \ || { clear; exit 1; } fi } count_steps() { local sel="$1" TOTAL=0 [[ "$sel" == *"ollama"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"llama-cpp"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"open-webui"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"claude"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"networking-cli"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"disk-recovery"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"himalaya"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"gnuplot"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"povray"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"blender"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"toot"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"db-clients"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"mysql"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"productivity"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"yt-dlp"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"sox"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"imagemagick"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"ffmpeg"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"localtunnel"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"butter"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"tlp"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"steam"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"vesktop"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"spotify"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"prism"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"vintagestory"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"localsend"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"croc"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"onlyoffice"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"wireshark"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"k8s"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"docker"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"podman"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"cockpit"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"ssh-server"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"freeipa-client"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"freeipa-server"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"freeipa-image"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"python"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"zfs"* ]] && TOTAL=$(( TOTAL + 1 )) [[ "$sel" == *"wprs"* ]] && TOTAL=$(( TOTAL + 1 )) } [[ $EUID -eq 0 ]] && die "Run as your normal user (not root)." command -v pacman &>/dev/null || die "pacman not found — Arch Linux required." require_dialog > "$LOG" printf "Module install: %s\n" "$(date)" >> "$LOG" dialog --backtitle "$BACKTITLE" \ --title " Module Installer " \ --msgbox "\n\ Install optional modules on an existing system.\n\ ─────────────────────────────────────────────────\n\ \n\ Select any combination of modules below.\n\ Each module is idempotent — safe to re-run.\n\ \n\ Log: $LOG\n" 13 62 SELECTED=$(dialog --backtitle "$BACKTITLE" \ --title " Select Modules " \ --checklist "Space toggles · Enter confirms · Arrow keys scroll" 40 76 32 \ \ "ollama" "Ollama local LLM runner + API server" off \ "llama-cpp" "llama.cpp standalone inference CLI + server" off \ "open-webui" "Open WebUI browser UI for Ollama / LLM backends" off \ "claude" "Claude Code Anthropic CLI via npm" off \ "networking-cli" "Networking CLI nmap · nethogs · mitmproxy · httpie" off \ "disk-recovery" "Disk Recovery ddrescue · f3" off \ "himalaya" "Himalaya terminal email client (AUR)" off \ "gnuplot" "Gnuplot scientific plotting" off \ "povray" "POV-Ray ray-tracing renderer" off \ "blender" "Blender 3D creation suite" off \ "toot" "toot Mastodon CLI client (AUR)" off \ "db-clients" "DB Clients pgcli · mycli" off \ "mysql" "MySQL / MariaDB mariadb server + setup" off \ "productivity" "Productivity taskwarrior · watson · jrnl" off \ "yt-dlp" "yt-dlp YouTube / media downloader" off \ "sox" "SoX audio processing toolkit" off \ "imagemagick" "ImageMagick image manipulation" off \ "ffmpeg" "FFmpeg extras thumbnailer · GStreamer codecs" off \ "localtunnel" "LocalTunnel expose localhost via tunnel" off \ "butter" "butter btrfs snapshot backup (AUR)" off \ "tlp" "TLP laptop power management" off \ \ "steam" "Steam gaming platform" off \ "vesktop" "Vesktop Discord + Vencord theme" off \ "spotify" "Spotify launcher + Spicetify theming" off \ "prism" "PrismLauncher Minecraft launcher (Flatpak)" off \ "vintagestory" "Vintage Story survival game (AUR)" off \ "localsend" "LocalSend LAN file transfer (AUR)" off \ "croc" "croc cross-platform file transfer" off \ "onlyoffice" "OnlyOffice office suite (AUR)" off \ "wireshark" "Wireshark network packet analyser (GUI)" off \ "k8s" "Kubernetes tools kubectl · podman-desktop" off \ "docker" "Docker docker · docker-compose" off \ "podman" "Podman rootless containers · buildah" off \ "cockpit" "Cockpit web UI · machines · podman" off \ "ssh-server" "SSH server openssh · key-auth · enabled" off \ "freeipa-client" "FreeIPA Client sssd + ipa-client-install + enrollment" off \ "freeipa-server" "FreeIPA Server interactive server setup + client gen" off \ "freeipa-image" "FreeIPA Image OCI/LXC/Proxmox LXC builder + Keycloak" off \ "python" "Python tools pyright · pipx · pynvim" off \ "zfs" "ZFS zfs-dkms kernel module" off \ "wprs" "WPRS wprs-git (AUR)" off \ 3>&1 1>&2 2>&3) || { clear; echo "Aborted."; exit 0; } [[ -z "$SELECTED" ]] && { clear; echo "Nothing selected."; exit 0; } SUMMARY="" [[ "$SELECTED" == *"ollama"* ]] && SUMMARY+=" ✦ Ollama\n" [[ "$SELECTED" == *"llama-cpp"* ]] && SUMMARY+=" ✦ llama.cpp\n" [[ "$SELECTED" == *"open-webui"* ]] && SUMMARY+=" ✦ Open WebUI\n" [[ "$SELECTED" == *"claude"* ]] && SUMMARY+=" ✦ Claude Code\n" [[ "$SELECTED" == *"networking-cli"* ]] && SUMMARY+=" ✦ Networking CLI (nmap, nethogs, mitmproxy, httpie)\n" [[ "$SELECTED" == *"disk-recovery"* ]] && SUMMARY+=" ✦ Disk Recovery (ddrescue, f3)\n" [[ "$SELECTED" == *"himalaya"* ]] && SUMMARY+=" ✦ Himalaya\n" [[ "$SELECTED" == *"gnuplot"* ]] && SUMMARY+=" ✦ Gnuplot\n" [[ "$SELECTED" == *"povray"* ]] && SUMMARY+=" ✦ POV-Ray\n" [[ "$SELECTED" == *"blender"* ]] && SUMMARY+=" ✦ Blender\n" [[ "$SELECTED" == *"toot"* ]] && SUMMARY+=" ✦ toot\n" [[ "$SELECTED" == *"db-clients"* ]] && SUMMARY+=" ✦ DB Clients (pgcli, mycli)\n" [[ "$SELECTED" == *"mysql"* ]] && SUMMARY+=" ✦ MySQL / MariaDB\n" [[ "$SELECTED" == *"productivity"* ]] && SUMMARY+=" ✦ Productivity (taskwarrior, watson, jrnl)\n" [[ "$SELECTED" == *"yt-dlp"* ]] && SUMMARY+=" ✦ yt-dlp\n" [[ "$SELECTED" == *"sox"* ]] && SUMMARY+=" ✦ SoX\n" [[ "$SELECTED" == *"imagemagick"* ]] && SUMMARY+=" ✦ ImageMagick\n" [[ "$SELECTED" == *"ffmpeg"* ]] && SUMMARY+=" ✦ FFmpeg extras\n" [[ "$SELECTED" == *"localtunnel"* ]] && SUMMARY+=" ✦ LocalTunnel\n" [[ "$SELECTED" == *"butter"* ]] && SUMMARY+=" ✦ butter (btrfs backup)\n" [[ "$SELECTED" == *"tlp"* ]] && SUMMARY+=" ✦ TLP\n" [[ "$SELECTED" == *"steam"* ]] && SUMMARY+=" ✦ Steam\n" [[ "$SELECTED" == *"vesktop"* ]] && SUMMARY+=" ✦ Vesktop\n" [[ "$SELECTED" == *"spotify"* ]] && SUMMARY+=" ✦ Spotify\n" [[ "$SELECTED" == *"prism"* ]] && SUMMARY+=" ✦ PrismLauncher\n" [[ "$SELECTED" == *"vintagestory"* ]] && SUMMARY+=" ✦ Vintage Story\n" [[ "$SELECTED" == *"localsend"* ]] && SUMMARY+=" ✦ LocalSend\n" [[ "$SELECTED" == *"croc"* ]] && SUMMARY+=" ✦ croc\n" [[ "$SELECTED" == *"onlyoffice"* ]] && SUMMARY+=" ✦ OnlyOffice\n" [[ "$SELECTED" == *"wireshark"* ]] && SUMMARY+=" ✦ Wireshark\n" [[ "$SELECTED" == *"k8s"* ]] && SUMMARY+=" ✦ Kubernetes tools\n" [[ "$SELECTED" == *"docker"* ]] && SUMMARY+=" ✦ Docker\n" [[ "$SELECTED" == *"podman"* ]] && SUMMARY+=" ✦ Podman\n" [[ "$SELECTED" == *"cockpit"* ]] && SUMMARY+=" ✦ Cockpit\n" [[ "$SELECTED" == *"ssh-server"* ]] && SUMMARY+=" ✦ SSH server\n" [[ "$SELECTED" == *"freeipa-client"* ]] && SUMMARY+=" ✦ FreeIPA Client\n" [[ "$SELECTED" == *"freeipa-server"* ]] && SUMMARY+=" ✦ FreeIPA Server\n" [[ "$SELECTED" == *"freeipa-image"* ]] && SUMMARY+=" ✦ FreeIPA Image Builder\n" [[ "$SELECTED" == *"python"* ]] && SUMMARY+=" ✦ Python tools\n" [[ "$SELECTED" == *"zfs"* ]] && SUMMARY+=" ✦ ZFS\n" [[ "$SELECTED" == *"wprs"* ]] && SUMMARY+=" ✦ WPRS\n" dialog --backtitle "$BACKTITLE" \ --title " Confirm " \ --yesno "\n Modules to install:\n\n${SUMMARY}\n Log: $LOG\n\n Proceed?" \ 30 64 || { clear; echo "Aborted."; exit 0; } count_steps "$SELECTED" [[ "$SELECTED" == *"ollama"* ]] && run_module "Ollama" "$APPS/ollama.sh" [[ "$SELECTED" == *"llama-cpp"* ]] && run_module "llama.cpp" "$APPS/llama-cpp.sh" [[ "$SELECTED" == *"open-webui"* ]] && run_module "Open WebUI" "$APPS/open-webui.sh" [[ "$SELECTED" == *"claude"* ]] && run_module "Claude Code" "$APPS/claude.sh" [[ "$SELECTED" == *"networking-cli"* ]] && run_module "Networking CLI" "$APPS/networking-cli.sh" [[ "$SELECTED" == *"disk-recovery"* ]] && run_module "Disk Recovery" "$APPS/disk-recovery.sh" [[ "$SELECTED" == *"himalaya"* ]] && run_module "Himalaya" "$APPS/himalaya.sh" [[ "$SELECTED" == *"gnuplot"* ]] && run_module "Gnuplot" "$APPS/gnuplot.sh" [[ "$SELECTED" == *"povray"* ]] && run_module "POV-Ray" "$APPS/povray.sh" [[ "$SELECTED" == *"blender"* ]] && run_module "Blender" "$APPS/blender.sh" [[ "$SELECTED" == *"toot"* ]] && run_module "toot" "$APPS/toot.sh" [[ "$SELECTED" == *"db-clients"* ]] && run_module "DB Clients" "$APPS/db-clients.sh" [[ "$SELECTED" == *"mysql"* ]] && run_module "MySQL / MariaDB" "$APPS/mysql.sh" [[ "$SELECTED" == *"productivity"* ]] && run_module "Productivity" "$APPS/productivity.sh" [[ "$SELECTED" == *"yt-dlp"* ]] && run_module "yt-dlp" "$APPS/yt-dlp.sh" [[ "$SELECTED" == *"sox"* ]] && run_module "SoX" "$APPS/sox.sh" [[ "$SELECTED" == *"imagemagick"* ]] && run_module "ImageMagick" "$APPS/imagemagick.sh" [[ "$SELECTED" == *"ffmpeg"* ]] && run_module "FFmpeg extras" "$APPS/ffmpeg.sh" [[ "$SELECTED" == *"localtunnel"* ]] && run_module "LocalTunnel" "$APPS/localtunnel.sh" [[ "$SELECTED" == *"butter"* ]] && run_module "butter" "$APPS/butter.sh" [[ "$SELECTED" == *"tlp"* ]] && run_module "TLP" "$APPS/tlp.sh" [[ "$SELECTED" == *"steam"* ]] && run_module "Steam" "$APPS/steam.sh" [[ "$SELECTED" == *"vesktop"* ]] && run_module "Vesktop" "$APPS/vesktop.sh" [[ "$SELECTED" == *"spotify"* ]] && run_module "Spotify" "$APPS/spotify.sh" [[ "$SELECTED" == *"prism"* ]] && run_module "PrismLauncher" "$APPS/prismlauncher.sh" [[ "$SELECTED" == *"vintagestory"* ]] && run_module "Vintage Story" "$APPS/vintagestory.sh" [[ "$SELECTED" == *"localsend"* ]] && run_module "LocalSend" "$APPS/localsend.sh" [[ "$SELECTED" == *"croc"* ]] && run_module "croc" "$APPS/croc.sh" [[ "$SELECTED" == *"onlyoffice"* ]] && run_module "OnlyOffice" "$APPS/onlyoffice.sh" [[ "$SELECTED" == *"wireshark"* ]] && run_module "Wireshark" "$APPS/wireshark.sh" [[ "$SELECTED" == *"k8s"* ]] && run_module "Kubernetes Tools" "$APPS/k8s.sh" [[ "$SELECTED" == *"docker"* ]] && run_module "Docker" "$APPS/docker.sh" [[ "$SELECTED" == *"podman"* ]] && run_module "Podman" "$APPS/podman.sh" [[ "$SELECTED" == *"cockpit"* ]] && run_module "Cockpit" "$APPS/cockpit.sh" [[ "$SELECTED" == *"ssh-server"* ]] && run_module "SSH Server" "$APPS/ssh-server.sh" [[ "$SELECTED" == *"freeipa-client"* ]] && run_module "FreeIPA Client" "$APPS/freeipa-client.sh" [[ "$SELECTED" == *"freeipa-server"* ]] && run_module "FreeIPA Server" "$APPS/freeipa-server.sh" [[ "$SELECTED" == *"freeipa-image"* ]] && run_module "FreeIPA Image" "$APPS/freeipa-image-builder.sh" [[ "$SELECTED" == *"python"* ]] && run_module "Python Tools" "$MODULES/optional-Modules/python.sh" [[ "$SELECTED" == *"zfs"* ]] && run_module "ZFS" "$MODULES/optional-Modules/zfs.sh" [[ "$SELECTED" == *"wprs"* ]] && run_module "WPRS" "$MODULES/optional-Modules/wprs.sh" dialog --backtitle "$BACKTITLE" \ --title " Done " \ --msgbox "\n All selected modules installed.\n\n Log: $LOG\n\n A reboot may be required for some changes.\n" 11 56 clear printf "\n Done. Log: %s\n\n" "$LOG"