Replace whiptail menus with numbered text toggle selection

All interactive prompts now use numbered toggles (type a number to
toggle on/off, Enter to confirm) instead of requiring whiptail.
Removed whiptail from apt dependencies in ubuntu-post-install.sh.

https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx
This commit is contained in:
Claude
2026-03-21 02:05:08 +00:00
parent 47e91baf69
commit c83439f576
2 changed files with 312 additions and 365 deletions
-39
View File
@@ -94,19 +94,6 @@ POSTINSTALL_SCRIPT="$SCRIPT_DIR/ubuntu-post-install.sh"
HAS_POSTINSTALL=false
[[ -f "$POSTINSTALL_SCRIPT" ]] && HAS_POSTINSTALL=true
if command -v whiptail &>/dev/null; then
WHIP_ARGS=()
$HAS_POSTINSTALL && WHIP_ARGS+=(
"postinstall" "Full system setup — Ubuntu apps, security, backups" OFF
)
WHIP_ARGS+=( "aistack" "AI stack — select services on next screen" ON )
SELECTED=$(whiptail --title "Local AI Setup" \
--checklist "SPACE = toggle TAB = move ENTER = confirm" \
12 68 "${#WHIP_ARGS[@]}" "${WHIP_ARGS[@]}" 3>&1 1>&2 2>&3) || die "Cancelled."
INSTALL_AI=false
[[ "$SELECTED" == *"postinstall"* ]] && INSTALL_POSTINSTALL=true
[[ "$SELECTED" == *"aistack"* ]] && INSTALL_AI=true
else
echo ""
echo -e " ${BOLD}[1] Select what to run${NC}"
echo " (Type a number to toggle, Enter to confirm)"
@@ -125,7 +112,6 @@ else
done
INSTALL_POSTINSTALL=$SEL_POST
INSTALL_AI=$SEL_AI
fi
$INSTALL_AI || $INSTALL_POSTINSTALL \
|| die "Nothing selected — run again and select at least one option."
@@ -137,30 +123,6 @@ if $INSTALL_AI; then
docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^$1$" && echo ON || echo OFF
}
if command -v whiptail &>/dev/null; then
SELECTED_SVCS=$(whiptail --title "AI Stack — Select Services" \
--checklist "Ollama always installed (core). SPACE = toggle ENTER = confirm" \
20 72 9 \
"open-webui" "Open WebUI — Chat interface (like ChatGPT, uses Ollama)" "$(existing_svc open-webui)" \
"rag" "RAG + ChromaDB— Code & document search (needed by MCP)" "$(existing_svc rag-server)" \
"mcp" "MCP Server — Claude Code tools (bash, files, git, search)" "$(existing_svc mcp-server)" \
"searxng" "SearXNG — Private local web search engine" "$(existing_svc searxng)" \
"gitea" "Gitea — Self-hosted Git server" "$(existing_svc gitea)" \
"invokeai" "InvokeAI — Image generation (Stable Diffusion)" "$(existing_svc invokeai)" \
"portainer" "Portainer — Docker management web UI" "$(existing_svc portainer)" \
"kiwix" "Kiwix — Offline Wikipedia, Stack Overflow, Arch Wiki" "$(existing_svc kiwix)" \
3>&1 1>&2 2>&3) || die "Cancelled."
SVC_WEBUI=false; [[ "$SELECTED_SVCS" == *'"open-webui"'* ]] && SVC_WEBUI=true
SVC_RAG=false; [[ "$SELECTED_SVCS" == *'"rag"'* ]] && SVC_RAG=true
SVC_MCP=false; [[ "$SELECTED_SVCS" == *'"mcp"'* ]] && SVC_MCP=true
SVC_SEARXNG=false; [[ "$SELECTED_SVCS" == *'"searxng"'* ]] && SVC_SEARXNG=true
SVC_GITEA=false; [[ "$SELECTED_SVCS" == *'"gitea"'* ]] && SVC_GITEA=true
SVC_INVOKEAI=false; [[ "$SELECTED_SVCS" == *'"invokeai"'* ]] && SVC_INVOKEAI=true
SVC_PORTAINER=false;[[ "$SELECTED_SVCS" == *'"portainer"'* ]] && SVC_PORTAINER=true
SVC_KIWIX=false; [[ "$SELECTED_SVCS" == *'"kiwix"'* ]] && SVC_KIWIX=true
else
# Text toggle fallback
echo ""
echo -e " ${BOLD}[1a] AI Stack — select services to install${NC}"
echo " Ollama always included (required core). Type number to toggle, Enter to confirm."
@@ -192,7 +154,6 @@ if $INSTALL_AI; then
done
SVC_WEBUI=$_W; SVC_RAG=$_R; SVC_MCP=$_M; SVC_SEARXNG=$_S
SVC_GITEA=$_G; SVC_INVOKEAI=$_I; SVC_PORTAINER=$_P; SVC_KIWIX=$_K
fi
# Enforce dependencies: MCP needs RAG
$SVC_MCP && ! $SVC_RAG && { warn "MCP Server requires RAG — enabling RAG+ChromaDB"; SVC_RAG=true; }
+172 -186
View File
@@ -137,7 +137,6 @@ run_disaster_recovery() {
tree \
zip \
unzip \
whiptail \
2>/dev/null || echo " ⚠ Some packages may have failed"
# Install Kopia for backup management
@@ -325,55 +324,31 @@ run_disaster_recovery() {
echo "Found ${#SERVICES_FOUND[@]} services in backup."
echo ""
# Check if whiptail is available for nice UI
if command -v whiptail &> /dev/null; then
# Build whiptail checklist arguments
CHECKLIST_ARGS=()
for svc in "${SERVICES_FOUND[@]}"; do
CHECKLIST_ARGS+=("$svc" "" "ON")
done
# Show checklist - returns selected items
SELECTED=$(whiptail --title "Select Services to Restore" \
--checklist "Use SPACE to select/deselect, ENTER to confirm:" \
20 60 12 \
"${CHECKLIST_ARGS[@]}" \
3>&1 1>&2 2>&3)
# Parse selected services (whiptail returns quoted strings)
SELECTED_SERVICES=()
for item in $SELECTED; do
# Remove quotes
svc=$(echo "$item" | tr -d '"')
SELECTED_SERVICES+=("$svc")
done
else
# Fallback: text-based selection
echo "Select services to restore:"
echo " [A] All services"
echo " [N] None (skip restore)"
echo " [S] Select individually"
# Numbered toggle selection for services to restore
echo "Select services to restore (all selected by default):"
echo " Type a number to toggle, Enter to confirm."
echo ""
read -p "Choice (A/N/S) [A]: " RESTORE_CHOICE
case "${RESTORE_CHOICE^^}" in
N)
SELECTED_SERVICES=()
;;
S)
SELECTED_SERVICES=()
for svc in "${SERVICES_FOUND[@]}"; do
read -p " Restore $svc? (y/n) [y]: " RESTORE_SVC
if [ "$RESTORE_SVC" != "n" ] && [ "$RESTORE_SVC" != "N" ]; then
SELECTED_SERVICES+=("$svc")
declare -a _SEL_FLAGS
for i in "${!SERVICES_FOUND[@]}"; do _SEL_FLAGS[$i]=true; done
while true; do
echo ""
for i in "${!SERVICES_FOUND[@]}"; do
printf " [%s] %d. %s\n" "$( ${_SEL_FLAGS[$i]} && echo '*' || echo ' ')" "$((i+1))" "${SERVICES_FOUND[$i]}"
done
echo ""
read -p " Toggle [number] or Enter to confirm: " _T
if [[ -z "$_T" ]]; then
break
elif [[ "$_T" =~ ^[0-9]+$ ]] && [[ "$_T" -ge 1 ]] && [[ "$_T" -le "${#SERVICES_FOUND[@]}" ]]; then
idx=$((_T-1))
${_SEL_FLAGS[$idx]} && _SEL_FLAGS[$idx]=false || _SEL_FLAGS[$idx]=true
fi
done
;;
*)
SELECTED_SERVICES=("${SERVICES_FOUND[@]}")
;;
esac
fi
SELECTED_SERVICES=()
for i in "${!SERVICES_FOUND[@]}"; do
${_SEL_FLAGS[$i]} && SELECTED_SERVICES+=("${SERVICES_FOUND[$i]}")
done
unset _SEL_FLAGS _T
# Restore selected services
echo ""
@@ -635,54 +610,34 @@ run_migration() {
SELECTED_CONTAINERS=()
if command -v whiptail &> /dev/null && [ ${#CONTAINERS_FOUND[@]} -gt 3 ]; then
# Use whiptail for many containers
CHECKLIST_ARGS=()
for container in "${CONTAINERS_FOUND[@]}"; do
CHECKLIST_ARGS+=("$container" "" "ON")
done
SELECTED=$(whiptail --title "Select Containers to Migrate" \
--checklist "Use SPACE to select/deselect, ENTER to confirm:" \
20 60 12 \
"${CHECKLIST_ARGS[@]}" \
3>&1 1>&2 2>&3)
if [ $? -eq 0 ] && [ -n "$SELECTED" ]; then
# Parse whiptail output
for container in $SELECTED; do
# Remove quotes
container="${container//\"/}"
SELECTED_CONTAINERS+=("$container")
done
fi
else
# Text-based selection
echo "Select containers to migrate:"
echo " [A] All containers"
echo " [S] Select individually"
echo " [N] None (cancel)"
# Numbered toggle selection for containers to migrate (all selected by default)
echo "Select containers to migrate (all selected by default):"
echo " Type a number to toggle, Enter to confirm, or 0 to cancel."
echo ""
read -p "Choice (A/S/N) [A]: " MIGRATE_CHOICE
case "${MIGRATE_CHOICE^^}" in
N)
declare -a _CONT_FLAGS
for i in "${!CONTAINERS_FOUND[@]}"; do _CONT_FLAGS[$i]=true; done
while true; do
echo ""
for i in "${!CONTAINERS_FOUND[@]}"; do
printf " [%s] %d. %s\n" "$( ${_CONT_FLAGS[$i]} && echo '*' || echo ' ')" "$((i+1))" "${CONTAINERS_FOUND[$i]}"
done
echo ""
read -p " Toggle [number], 0=cancel, Enter to confirm: " _T
if [[ -z "$_T" ]]; then
break
elif [[ "$_T" == "0" ]]; then
echo "Migration cancelled."
unset _CONT_FLAGS _T
return 0
;;
S)
for container in "${CONTAINERS_FOUND[@]}"; do
read -p " Migrate $container? (y/n) [y]: " MIGRATE_THIS
if [ "$MIGRATE_THIS" != "n" ] && [ "$MIGRATE_THIS" != "N" ]; then
SELECTED_CONTAINERS+=("$container")
elif [[ "$_T" =~ ^[0-9]+$ ]] && [[ "$_T" -ge 1 ]] && [[ "$_T" -le "${#CONTAINERS_FOUND[@]}" ]]; then
idx=$((_T-1))
${_CONT_FLAGS[$idx]} && _CONT_FLAGS[$idx]=false || _CONT_FLAGS[$idx]=true
fi
done
;;
*)
SELECTED_CONTAINERS=("${CONTAINERS_FOUND[@]}")
;;
esac
fi
for i in "${!CONTAINERS_FOUND[@]}"; do
${_CONT_FLAGS[$i]} && SELECTED_CONTAINERS+=("${CONTAINERS_FOUND[$i]}")
done
unset _CONT_FLAGS _T
if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
echo "No containers selected."
@@ -3793,8 +3748,6 @@ else
# SERVICE SELECTION MENU
# ============================================================================
# Use whiptail for service selection if available
if command -v whiptail &> /dev/null; then
# Detect existing services
echo "Detecting existing services..."
declare -A EXISTING_SERVICES
@@ -3823,102 +3776,149 @@ else
[ -d "$DOCKER_DIR/watchtower" ] && EXISTING_SERVICES[WATCHTOWER]="ON"
command -v fail2ban-client &> /dev/null && EXISTING_SERVICES[FAIL2BAN]="ON"
# Ask user what action to perform
ACTION=$(whiptail --title "Service Management" --menu \
"Choose an action:" 15 60 3 \
"1" "Install new services" \
"2" "Uninstall existing services" \
"3" "Cancel and skip" \
3>&1 1>&2 2>&3)
# Action selection
echo ""
echo " Service Management"
echo " ──────────────────"
echo " 1. Install new services"
echo " 2. Uninstall existing services"
echo " 3. Cancel and skip"
echo ""
read -p " Choose action [1]: " ACTION
ACTION="${ACTION:-1}"
SELECTED_SERVICES=""
case "$ACTION" in
1)
# INSTALL MODE
MENU_MODE="INSTALL"
# Build checklist - mark existing services as ON
SELECTED_SERVICES=$(whiptail --title "Select Docker Services to Install" \
--checklist "Use SPACE to select, ENTER to confirm. [*] = already installed" 25 78 17 \
"IMMICH" "Photo & video backup (like Google Photos)" ${EXISTING_SERVICES[IMMICH]:-OFF} \
"AUDIOBOOKSHELF" "Audiobook & podcast server" ${EXISTING_SERVICES[AUDIOBOOKSHELF]:-OFF} \
"EMBY" "Media server for movies, TV, music" ${EXISTING_SERVICES[EMBY]:-OFF} \
"ARM" "Automatic Ripping Machine for DVDs/Blu-rays" ${EXISTING_SERVICES[ARM]:-OFF} \
"FILEBROWSER" "Web-based file manager" ${EXISTING_SERVICES[FILEBROWSER]:-OFF} \
"MAGICMIRROR" "Smart mirror / dashboard display" ${EXISTING_SERVICES[MAGICMIRROR]:-OFF} \
"ACTUALBUDGET" "Personal finance management with bank sync" ${EXISTING_SERVICES[ACTUALBUDGET]:-OFF} \
"KEYCLOAK" "Identity & Access Management (SSO)" ${EXISTING_SERVICES[KEYCLOAK]:-OFF} \
"CADDY" "Reverse proxy with automatic HTTPS" ${EXISTING_SERVICES[CADDY]:-OFF} \
"FAIL2BAN" "Intrusion prevention system" ${EXISTING_SERVICES[FAIL2BAN]:-OFF} \
"LYRION" "Music streaming server (LMS)" ${EXISTING_SERVICES[LYRION]:-OFF} \
"MEALIE" "Recipe manager & meal planner" ${EXISTING_SERVICES[MEALIE]:-OFF} \
"MINECRAFT" "Minecraft game server" ${EXISTING_SERVICES[MINECRAFT]:-OFF} \
"JELLYFIN" "Free media server (Emby alternative)" ${EXISTING_SERVICES[JELLYFIN]:-OFF} \
"FRIGATE" "AI-powered NVR for security cameras" ${EXISTING_SERVICES[FRIGATE]:-OFF} \
"NTFY" "Push notifications server" ${EXISTING_SERVICES[NTFY]:-OFF} \
"UPTIMEKUMA" "Service monitoring dashboard" ${EXISTING_SERVICES[UPTIMEKUMA]:-OFF} \
"WGEASY" "WireGuard VPN with web UI" ${EXISTING_SERVICES[WGEASY]:-OFF} \
"TRACCAR" "GPS tracking server" ${EXISTING_SERVICES[TRACCAR]:-OFF} \
"PORTAINER" "Docker management web UI" ${EXISTING_SERVICES[PORTAINER]:-OFF} \
"MESHCENTRAL" "Remote management server" ${EXISTING_SERVICES[MESHCENTRAL]:-OFF} \
"FINDMYDEVICE" "Device tracking (like Find My)" ${EXISTING_SERVICES[FINDMYDEVICE]:-OFF} \
"FRIGATE_NOTIFY" "Push notifications for Frigate" ${EXISTING_SERVICES[FRIGATE_NOTIFY]:-OFF} \
"WATCHTOWER" "Automatic container updates" ${EXISTING_SERVICES[WATCHTOWER]:-OFF} \
3>&1 1>&2 2>&3)
# Build ordered list of services; mark existing ones as selected
_SVC_KEYS=(IMMICH AUDIOBOOKSHELF EMBY ARM FILEBROWSER MAGICMIRROR ACTUALBUDGET
KEYCLOAK CADDY FAIL2BAN LYRION MEALIE MINECRAFT JELLYFIN FRIGATE
NTFY UPTIMEKUMA WGEASY TRACCAR PORTAINER MESHCENTRAL FINDMYDEVICE
FRIGATE_NOTIFY WATCHTOWER)
_SVC_LABELS=(
"IMMICH — Photo & video backup (like Google Photos)"
"AUDIOBOOKSHELF— Audiobook & podcast server"
"EMBY — Media server for movies, TV, music"
"ARM — Automatic Ripping Machine for DVDs/Blu-rays"
"FILEBROWSER — Web-based file manager"
"MAGICMIRROR — Smart mirror / dashboard display"
"ACTUALBUDGET — Personal finance management with bank sync"
"KEYCLOAK — Identity & Access Management (SSO)"
"CADDY — Reverse proxy with automatic HTTPS"
"FAIL2BAN — Intrusion prevention system"
"LYRION — Music streaming server (LMS)"
"MEALIE — Recipe manager & meal planner"
"MINECRAFT — Minecraft game server"
"JELLYFIN — Free media server (Emby alternative)"
"FRIGATE — AI-powered NVR for security cameras"
"NTFY — Push notifications server"
"UPTIMEKUMA — Service monitoring dashboard"
"WGEASY — WireGuard VPN with web UI"
"TRACCAR — GPS tracking server"
"PORTAINER — Docker management web UI"
"MESHCENTRAL — Remote management server"
"FINDMYDEVICE — Device tracking (like Find My)"
"FRIGATE_NOTIFY— Push notifications for Frigate"
"WATCHTOWER — Automatic container updates"
)
declare -a _SVC_FLAGS
for i in "${!_SVC_KEYS[@]}"; do
[ -n "${EXISTING_SERVICES[${_SVC_KEYS[$i]}]:-}" ] && _SVC_FLAGS[$i]=true || _SVC_FLAGS[$i]=false
done
echo ""
echo " Select services to install ([*] = already installed)"
echo " Type number to toggle, Enter to confirm."
while true; do
echo ""
for i in "${!_SVC_KEYS[@]}"; do
_mark="$( ${_SVC_FLAGS[$i]} && echo '*' || echo ' ')"
_exist="$( [ -n "${EXISTING_SERVICES[${_SVC_KEYS[$i]}]:-}" ] && echo ' [installed]' || echo '')"
printf " [%s] %2d. %s%s\n" "$_mark" "$((i+1))" "${_SVC_LABELS[$i]}" "$_exist"
done
echo ""
read -p " Toggle [number] or Enter to confirm: " _T
if [[ -z "$_T" ]]; then
break
elif [[ "$_T" =~ ^[0-9]+$ ]] && [[ "$_T" -ge 1 ]] && [[ "$_T" -le "${#_SVC_KEYS[@]}" ]]; then
idx=$((_T-1))
${_SVC_FLAGS[$idx]} && _SVC_FLAGS[$idx]=false || _SVC_FLAGS[$idx]=true
fi
done
for i in "${!_SVC_KEYS[@]}"; do
${_SVC_FLAGS[$i]} && SELECTED_SERVICES="$SELECTED_SERVICES ${_SVC_KEYS[$i]}"
done
unset _SVC_FLAGS _SVC_KEYS _SVC_LABELS _T _mark _exist
;;
2)
# UNINSTALL MODE
MENU_MODE="UNINSTALL"
# Only show services that exist
UNINSTALL_OPTIONS=""
[ -n "${EXISTING_SERVICES[IMMICH]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS IMMICH \"Photo & video backup\" ON"
[ -n "${EXISTING_SERVICES[AUDIOBOOKSHELF]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS AUDIOBOOKSHELF \"Audiobook server\" ON"
[ -n "${EXISTING_SERVICES[EMBY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS EMBY \"Media server\" ON"
[ -n "${EXISTING_SERVICES[ARM]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ARM \"Automatic Ripping Machine\" ON"
[ -n "${EXISTING_SERVICES[FILEBROWSER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FILEBROWSER \"Web file manager\" ON"
[ -n "${EXISTING_SERVICES[MAGICMIRROR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MAGICMIRROR \"Smart mirror\" ON"
[ -n "${EXISTING_SERVICES[ACTUALBUDGET]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ACTUALBUDGET \"Personal finance\" ON"
[ -n "${EXISTING_SERVICES[KEYCLOAK]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS KEYCLOAK \"Identity management\" ON"
[ -n "${EXISTING_SERVICES[CADDY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS CADDY \"Reverse proxy\" ON"
[ -n "${EXISTING_SERVICES[FAIL2BAN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FAIL2BAN \"Intrusion prevention\" ON"
[ -n "${EXISTING_SERVICES[LYRION]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS LYRION \"Music server\" ON"
[ -n "${EXISTING_SERVICES[MEALIE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MEALIE \"Recipe manager\" ON"
[ -n "${EXISTING_SERVICES[MINECRAFT]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MINECRAFT \"Game server\" ON"
[ -n "${EXISTING_SERVICES[JELLYFIN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS JELLYFIN \"Media server\" ON"
[ -n "${EXISTING_SERVICES[FRIGATE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FRIGATE \"NVR cameras\" ON"
[ -n "${EXISTING_SERVICES[NTFY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS NTFY \"Push notifications\" ON"
[ -n "${EXISTING_SERVICES[UPTIMEKUMA]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS UPTIMEKUMA \"Service monitoring\" ON"
[ -n "${EXISTING_SERVICES[WGEASY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS WGEASY \"WireGuard VPN\" ON"
[ -n "${EXISTING_SERVICES[TRACCAR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS TRACCAR \"GPS tracking\" ON"
[ -n "${EXISTING_SERVICES[PORTAINER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS PORTAINER \"Docker management\" ON"
[ -n "${EXISTING_SERVICES[MESHCENTRAL]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MESHCENTRAL \"Remote management\" ON"
[ -n "${EXISTING_SERVICES[FINDMYDEVICE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FINDMYDEVICE \"Device tracking\" ON"
[ -n "${EXISTING_SERVICES[FRIGATE_NOTIFY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FRIGATE_NOTIFY \"Frigate notifications\" ON"
[ -n "${EXISTING_SERVICES[WATCHTOWER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS WATCHTOWER \"Container updates\" ON"
# Build list from only installed services
_SVC_KEYS=(); _SVC_LABELS=()
_add_if_exists() { [ -n "${EXISTING_SERVICES[$1]:-}" ] && _SVC_KEYS+=("$1") && _SVC_LABELS+=("$2"); }
_add_if_exists IMMICH "IMMICH — Photo & video backup"
_add_if_exists AUDIOBOOKSHELF "AUDIOBOOKSHELF — Audiobook server"
_add_if_exists EMBY "EMBY — Media server"
_add_if_exists ARM "ARM — Automatic Ripping Machine"
_add_if_exists FILEBROWSER "FILEBROWSER — Web file manager"
_add_if_exists MAGICMIRROR "MAGICMIRROR — Smart mirror"
_add_if_exists ACTUALBUDGET "ACTUALBUDGET — Personal finance"
_add_if_exists KEYCLOAK "KEYCLOAK — Identity management"
_add_if_exists CADDY "CADDY — Reverse proxy"
_add_if_exists FAIL2BAN "FAIL2BAN — Intrusion prevention"
_add_if_exists LYRION "LYRION — Music server"
_add_if_exists MEALIE "MEALIE — Recipe manager"
_add_if_exists MINECRAFT "MINECRAFT — Game server"
_add_if_exists JELLYFIN "JELLYFIN — Media server"
_add_if_exists FRIGATE "FRIGATE — NVR cameras"
_add_if_exists NTFY "NTFY — Push notifications"
_add_if_exists UPTIMEKUMA "UPTIMEKUMA — Service monitoring"
_add_if_exists WGEASY "WGEASY — WireGuard VPN"
_add_if_exists TRACCAR "TRACCAR — GPS tracking"
_add_if_exists PORTAINER "PORTAINER — Docker management"
_add_if_exists MESHCENTRAL "MESHCENTRAL — Remote management"
_add_if_exists FINDMYDEVICE "FINDMYDEVICE — Device tracking"
_add_if_exists FRIGATE_NOTIFY "FRIGATE_NOTIFY — Frigate notifications"
_add_if_exists WATCHTOWER "WATCHTOWER — Container updates"
unset -f _add_if_exists
if [ -z "$UNINSTALL_OPTIONS" ]; then
whiptail --title "No Services Found" --msgbox "No services detected to uninstall." 8 50
if [ ${#_SVC_KEYS[@]} -eq 0 ]; then
echo "No installed services detected to uninstall."
SELECTED_SERVICES=""
MENU_MODE="CANCEL"
else
SELECTED_SERVICES=$(eval "whiptail --title 'Select Services to Uninstall' \
--checklist 'WARNING: Data will be backed up but services will be removed' 25 78 17 \
$UNINSTALL_OPTIONS \
3>&1 1>&2 2>&3")
declare -a _SVC_FLAGS
for i in "${!_SVC_KEYS[@]}"; do _SVC_FLAGS[$i]=true; done
echo ""
echo " WARNING: selected services will be removed (data backed up first)."
echo " Type number to toggle, Enter to confirm."
while true; do
echo ""
for i in "${!_SVC_KEYS[@]}"; do
printf " [%s] %2d. %s\n" "$( ${_SVC_FLAGS[$i]} && echo '*' || echo ' ')" "$((i+1))" "${_SVC_LABELS[$i]}"
done
echo ""
read -p " Toggle [number] or Enter to confirm: " _T
if [[ -z "$_T" ]]; then
break
elif [[ "$_T" =~ ^[0-9]+$ ]] && [[ "$_T" -ge 1 ]] && [[ "$_T" -le "${#_SVC_KEYS[@]}" ]]; then
idx=$((_T-1))
${_SVC_FLAGS[$idx]} && _SVC_FLAGS[$idx]=false || _SVC_FLAGS[$idx]=true
fi
done
for i in "${!_SVC_KEYS[@]}"; do
${_SVC_FLAGS[$i]} && SELECTED_SERVICES="$SELECTED_SERVICES ${_SVC_KEYS[$i]}"
done
unset _SVC_FLAGS _SVC_KEYS _SVC_LABELS _T
fi
;;
3|*)
# Cancel
SELECTED_SERVICES=""
MENU_MODE="CANCEL"
;;
esac
# Check if user cancelled
if [ $? -ne 0 ]; then
echo "Service selection cancelled. Skipping Docker applications."
SELECTED_SERVICES=""
fi
# Parse selections (whiptail returns quoted strings)
# Only set to "n" if not already set (preserve any earlier choices)
# Set installation flags from SELECTED_SERVICES
WHIPTAIL_USED=true
: ${INSTALL_IMMICH:="n"}
: ${INSTALL_AUDIOBOOKSHELF:="n"}
: ${INSTALL_EMBY:="n"}
@@ -3944,7 +3944,6 @@ else
: ${INSTALL_FRIGATE_NOTIFY:="n"}
: ${INSTALL_WATCHTOWER:="n"}
# Set installation flags based on selections
if echo "$SELECTED_SERVICES" | grep -q "IMMICH"; then INSTALL_IMMICH="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "AUDIOBOOKSHELF"; then INSTALL_AUDIOBOOKSHELF="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "EMBY"; then INSTALL_EMBY="y"; fi
@@ -3959,7 +3958,7 @@ else
if echo "$SELECTED_SERVICES" | grep -q "MEALIE"; then INSTALL_MEALIE="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "MINECRAFT"; then INSTALL_MINECRAFT="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "JELLYFIN"; then INSTALL_JELLYFIN="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "FRIGATE\""; then INSTALL_FRIGATE="y"; fi
if echo "$SELECTED_SERVICES" | grep -q " FRIGATE "; then INSTALL_FRIGATE="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "NTFY"; then INSTALL_NTFY="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "UPTIMEKUMA"; then INSTALL_UPTIMEKUMA="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "WGEASY"; then INSTALL_WGEASY="y"; fi
@@ -4062,24 +4061,11 @@ else
fi
# After uninstall, exit the Docker apps section
WHIPTAIL_USED=true
elif [ "$MENU_MODE" = "INSTALL" ]; then
echo ""
echo "Selected services:"
echo "$SELECTED_SERVICES" | tr '"' '\n' | grep -v '^$' | sed 's/^/ - /'
echo "$SELECTED_SERVICES" | tr ' ' '\n' | grep -v '^$' | sed 's/^/ - /'
echo ""
# Mark that whiptail was used - skip all individual prompts
WHIPTAIL_USED=true
else
# Cancel or other mode
WHIPTAIL_USED=false
fi
else
# Whiptail not available - will use individual prompts
WHIPTAIL_USED=false
MENU_MODE="INSTALL"
fi
# Skip installation section if we just did uninstalls