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
+8 -47
View File
@@ -94,24 +94,11 @@ POSTINSTALL_SCRIPT="$SCRIPT_DIR/ubuntu-post-install.sh"
HAS_POSTINSTALL=false HAS_POSTINSTALL=false
[[ -f "$POSTINSTALL_SCRIPT" ]] && HAS_POSTINSTALL=true [[ -f "$POSTINSTALL_SCRIPT" ]] && HAS_POSTINSTALL=true
if command -v whiptail &>/dev/null; then echo ""
WHIP_ARGS=() echo -e " ${BOLD}[1] Select what to run${NC}"
$HAS_POSTINSTALL && WHIP_ARGS+=( echo " (Type a number to toggle, Enter to confirm)"
"postinstall" "Full system setup — Ubuntu apps, security, backups" OFF SEL_POST=false; SEL_AI=true
) while true; do
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)"
SEL_POST=false; SEL_AI=true
while true; do
echo "" echo ""
$HAS_POSTINSTALL && printf " [%s] 1. Full system setup — Ubuntu apps, security, backups\n" "$($SEL_POST && echo '*' || echo ' ')" $HAS_POSTINSTALL && printf " [%s] 1. Full system setup — Ubuntu apps, security, backups\n" "$($SEL_POST && echo '*' || echo ' ')"
printf " [%s] 2. AI stack — select services on next screen\n" "$($SEL_AI && echo '*' || echo ' ')" printf " [%s] 2. AI stack — select services on next screen\n" "$($SEL_AI && echo '*' || echo ' ')"
@@ -122,10 +109,9 @@ else
2) $SEL_AI && SEL_AI=false || SEL_AI=true ;; 2) $SEL_AI && SEL_AI=false || SEL_AI=true ;;
"") break ;; "") break ;;
esac esac
done done
INSTALL_POSTINSTALL=$SEL_POST INSTALL_POSTINSTALL=$SEL_POST
INSTALL_AI=$SEL_AI INSTALL_AI=$SEL_AI
fi
$INSTALL_AI || $INSTALL_POSTINSTALL \ $INSTALL_AI || $INSTALL_POSTINSTALL \
|| die "Nothing selected — run again and select at least one option." || 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 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 ""
echo -e " ${BOLD}[1a] AI Stack — select services to install${NC}" echo -e " ${BOLD}[1a] AI Stack — select services to install${NC}"
echo " Ollama always included (required core). Type number to toggle, Enter to confirm." echo " Ollama always included (required core). Type number to toggle, Enter to confirm."
@@ -192,7 +154,6 @@ if $INSTALL_AI; then
done done
SVC_WEBUI=$_W; SVC_RAG=$_R; SVC_MCP=$_M; SVC_SEARXNG=$_S SVC_WEBUI=$_W; SVC_RAG=$_R; SVC_MCP=$_M; SVC_SEARXNG=$_S
SVC_GITEA=$_G; SVC_INVOKEAI=$_I; SVC_PORTAINER=$_P; SVC_KIWIX=$_K SVC_GITEA=$_G; SVC_INVOKEAI=$_I; SVC_PORTAINER=$_P; SVC_KIWIX=$_K
fi
# Enforce dependencies: MCP needs RAG # Enforce dependencies: MCP needs RAG
$SVC_MCP && ! $SVC_RAG && { warn "MCP Server requires RAG — enabling RAG+ChromaDB"; SVC_RAG=true; } $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 \ tree \
zip \ zip \
unzip \ unzip \
whiptail \
2>/dev/null || echo " ⚠ Some packages may have failed" 2>/dev/null || echo " ⚠ Some packages may have failed"
# Install Kopia for backup management # Install Kopia for backup management
@@ -325,55 +324,31 @@ run_disaster_recovery() {
echo "Found ${#SERVICES_FOUND[@]} services in backup." echo "Found ${#SERVICES_FOUND[@]} services in backup."
echo "" echo ""
# Check if whiptail is available for nice UI # Numbered toggle selection for services to restore
if command -v whiptail &> /dev/null; then echo "Select services to restore (all selected by default):"
# Build whiptail checklist arguments echo " Type a number to toggle, Enter to confirm."
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"
echo "" echo ""
read -p "Choice (A/N/S) [A]: " RESTORE_CHOICE declare -a _SEL_FLAGS
for i in "${!SERVICES_FOUND[@]}"; do _SEL_FLAGS[$i]=true; done
case "${RESTORE_CHOICE^^}" in while true; do
N) echo ""
SELECTED_SERVICES=() for i in "${!SERVICES_FOUND[@]}"; do
;; printf " [%s] %d. %s\n" "$( ${_SEL_FLAGS[$i]} && echo '*' || echo ' ')" "$((i+1))" "${SERVICES_FOUND[$i]}"
S) done
SELECTED_SERVICES=() echo ""
for svc in "${SERVICES_FOUND[@]}"; do read -p " Toggle [number] or Enter to confirm: " _T
read -p " Restore $svc? (y/n) [y]: " RESTORE_SVC if [[ -z "$_T" ]]; then
if [ "$RESTORE_SVC" != "n" ] && [ "$RESTORE_SVC" != "N" ]; then break
SELECTED_SERVICES+=("$svc") 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 fi
done done
;; SELECTED_SERVICES=()
*) for i in "${!SERVICES_FOUND[@]}"; do
SELECTED_SERVICES=("${SERVICES_FOUND[@]}") ${_SEL_FLAGS[$i]} && SELECTED_SERVICES+=("${SERVICES_FOUND[$i]}")
;; done
esac unset _SEL_FLAGS _T
fi
# Restore selected services # Restore selected services
echo "" echo ""
@@ -635,54 +610,34 @@ run_migration() {
SELECTED_CONTAINERS=() SELECTED_CONTAINERS=()
if command -v whiptail &> /dev/null && [ ${#CONTAINERS_FOUND[@]} -gt 3 ]; then # Numbered toggle selection for containers to migrate (all selected by default)
# Use whiptail for many containers echo "Select containers to migrate (all selected by default):"
CHECKLIST_ARGS=() echo " Type a number to toggle, Enter to confirm, or 0 to cancel."
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)"
echo "" echo ""
read -p "Choice (A/S/N) [A]: " MIGRATE_CHOICE declare -a _CONT_FLAGS
for i in "${!CONTAINERS_FOUND[@]}"; do _CONT_FLAGS[$i]=true; done
case "${MIGRATE_CHOICE^^}" in while true; do
N) 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." echo "Migration cancelled."
unset _CONT_FLAGS _T
return 0 return 0
;; elif [[ "$_T" =~ ^[0-9]+$ ]] && [[ "$_T" -ge 1 ]] && [[ "$_T" -le "${#CONTAINERS_FOUND[@]}" ]]; then
S) idx=$((_T-1))
for container in "${CONTAINERS_FOUND[@]}"; do ${_CONT_FLAGS[$idx]} && _CONT_FLAGS[$idx]=false || _CONT_FLAGS[$idx]=true
read -p " Migrate $container? (y/n) [y]: " MIGRATE_THIS
if [ "$MIGRATE_THIS" != "n" ] && [ "$MIGRATE_THIS" != "N" ]; then
SELECTED_CONTAINERS+=("$container")
fi fi
done done
;; for i in "${!CONTAINERS_FOUND[@]}"; do
*) ${_CONT_FLAGS[$i]} && SELECTED_CONTAINERS+=("${CONTAINERS_FOUND[$i]}")
SELECTED_CONTAINERS=("${CONTAINERS_FOUND[@]}") done
;; unset _CONT_FLAGS _T
esac
fi
if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then if [ ${#SELECTED_CONTAINERS[@]} -eq 0 ]; then
echo "No containers selected." echo "No containers selected."
@@ -3793,8 +3748,6 @@ else
# SERVICE SELECTION MENU # SERVICE SELECTION MENU
# ============================================================================ # ============================================================================
# Use whiptail for service selection if available
if command -v whiptail &> /dev/null; then
# Detect existing services # Detect existing services
echo "Detecting existing services..." echo "Detecting existing services..."
declare -A EXISTING_SERVICES declare -A EXISTING_SERVICES
@@ -3823,102 +3776,149 @@ else
[ -d "$DOCKER_DIR/watchtower" ] && EXISTING_SERVICES[WATCHTOWER]="ON" [ -d "$DOCKER_DIR/watchtower" ] && EXISTING_SERVICES[WATCHTOWER]="ON"
command -v fail2ban-client &> /dev/null && EXISTING_SERVICES[FAIL2BAN]="ON" command -v fail2ban-client &> /dev/null && EXISTING_SERVICES[FAIL2BAN]="ON"
# Ask user what action to perform # Action selection
ACTION=$(whiptail --title "Service Management" --menu \ echo ""
"Choose an action:" 15 60 3 \ echo " Service Management"
"1" "Install new services" \ echo " ──────────────────"
"2" "Uninstall existing services" \ echo " 1. Install new services"
"3" "Cancel and skip" \ echo " 2. Uninstall existing services"
3>&1 1>&2 2>&3) echo " 3. Cancel and skip"
echo ""
read -p " Choose action [1]: " ACTION
ACTION="${ACTION:-1}"
SELECTED_SERVICES=""
case "$ACTION" in case "$ACTION" in
1) 1)
# INSTALL MODE
MENU_MODE="INSTALL" MENU_MODE="INSTALL"
# Build checklist - mark existing services as ON # Build ordered list of services; mark existing ones as selected
SELECTED_SERVICES=$(whiptail --title "Select Docker Services to Install" \ _SVC_KEYS=(IMMICH AUDIOBOOKSHELF EMBY ARM FILEBROWSER MAGICMIRROR ACTUALBUDGET
--checklist "Use SPACE to select, ENTER to confirm. [*] = already installed" 25 78 17 \ KEYCLOAK CADDY FAIL2BAN LYRION MEALIE MINECRAFT JELLYFIN FRIGATE
"IMMICH" "Photo & video backup (like Google Photos)" ${EXISTING_SERVICES[IMMICH]:-OFF} \ NTFY UPTIMEKUMA WGEASY TRACCAR PORTAINER MESHCENTRAL FINDMYDEVICE
"AUDIOBOOKSHELF" "Audiobook & podcast server" ${EXISTING_SERVICES[AUDIOBOOKSHELF]:-OFF} \ FRIGATE_NOTIFY WATCHTOWER)
"EMBY" "Media server for movies, TV, music" ${EXISTING_SERVICES[EMBY]:-OFF} \ _SVC_LABELS=(
"ARM" "Automatic Ripping Machine for DVDs/Blu-rays" ${EXISTING_SERVICES[ARM]:-OFF} \ "IMMICH — Photo & video backup (like Google Photos)"
"FILEBROWSER" "Web-based file manager" ${EXISTING_SERVICES[FILEBROWSER]:-OFF} \ "AUDIOBOOKSHELF— Audiobook & podcast server"
"MAGICMIRROR" "Smart mirror / dashboard display" ${EXISTING_SERVICES[MAGICMIRROR]:-OFF} \ "EMBY — Media server for movies, TV, music"
"ACTUALBUDGET" "Personal finance management with bank sync" ${EXISTING_SERVICES[ACTUALBUDGET]:-OFF} \ "ARM — Automatic Ripping Machine for DVDs/Blu-rays"
"KEYCLOAK" "Identity & Access Management (SSO)" ${EXISTING_SERVICES[KEYCLOAK]:-OFF} \ "FILEBROWSER — Web-based file manager"
"CADDY" "Reverse proxy with automatic HTTPS" ${EXISTING_SERVICES[CADDY]:-OFF} \ "MAGICMIRROR — Smart mirror / dashboard display"
"FAIL2BAN" "Intrusion prevention system" ${EXISTING_SERVICES[FAIL2BAN]:-OFF} \ "ACTUALBUDGET — Personal finance management with bank sync"
"LYRION" "Music streaming server (LMS)" ${EXISTING_SERVICES[LYRION]:-OFF} \ "KEYCLOAK — Identity & Access Management (SSO)"
"MEALIE" "Recipe manager & meal planner" ${EXISTING_SERVICES[MEALIE]:-OFF} \ "CADDY — Reverse proxy with automatic HTTPS"
"MINECRAFT" "Minecraft game server" ${EXISTING_SERVICES[MINECRAFT]:-OFF} \ "FAIL2BAN — Intrusion prevention system"
"JELLYFIN" "Free media server (Emby alternative)" ${EXISTING_SERVICES[JELLYFIN]:-OFF} \ "LYRION — Music streaming server (LMS)"
"FRIGATE" "AI-powered NVR for security cameras" ${EXISTING_SERVICES[FRIGATE]:-OFF} \ "MEALIE — Recipe manager & meal planner"
"NTFY" "Push notifications server" ${EXISTING_SERVICES[NTFY]:-OFF} \ "MINECRAFT — Minecraft game server"
"UPTIMEKUMA" "Service monitoring dashboard" ${EXISTING_SERVICES[UPTIMEKUMA]:-OFF} \ "JELLYFIN — Free media server (Emby alternative)"
"WGEASY" "WireGuard VPN with web UI" ${EXISTING_SERVICES[WGEASY]:-OFF} \ "FRIGATE — AI-powered NVR for security cameras"
"TRACCAR" "GPS tracking server" ${EXISTING_SERVICES[TRACCAR]:-OFF} \ "NTFY — Push notifications server"
"PORTAINER" "Docker management web UI" ${EXISTING_SERVICES[PORTAINER]:-OFF} \ "UPTIMEKUMA — Service monitoring dashboard"
"MESHCENTRAL" "Remote management server" ${EXISTING_SERVICES[MESHCENTRAL]:-OFF} \ "WGEASY — WireGuard VPN with web UI"
"FINDMYDEVICE" "Device tracking (like Find My)" ${EXISTING_SERVICES[FINDMYDEVICE]:-OFF} \ "TRACCAR — GPS tracking server"
"FRIGATE_NOTIFY" "Push notifications for Frigate" ${EXISTING_SERVICES[FRIGATE_NOTIFY]:-OFF} \ "PORTAINER — Docker management web UI"
"WATCHTOWER" "Automatic container updates" ${EXISTING_SERVICES[WATCHTOWER]:-OFF} \ "MESHCENTRAL — Remote management server"
3>&1 1>&2 2>&3) "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) 2)
# UNINSTALL MODE
MENU_MODE="UNINSTALL" MENU_MODE="UNINSTALL"
# Only show services that exist # Build list from only installed services
UNINSTALL_OPTIONS="" _SVC_KEYS=(); _SVC_LABELS=()
[ -n "${EXISTING_SERVICES[IMMICH]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS IMMICH \"Photo & video backup\" ON" _add_if_exists() { [ -n "${EXISTING_SERVICES[$1]:-}" ] && _SVC_KEYS+=("$1") && _SVC_LABELS+=("$2"); }
[ -n "${EXISTING_SERVICES[AUDIOBOOKSHELF]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS AUDIOBOOKSHELF \"Audiobook server\" ON" _add_if_exists IMMICH "IMMICH — Photo & video backup"
[ -n "${EXISTING_SERVICES[EMBY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS EMBY \"Media server\" ON" _add_if_exists AUDIOBOOKSHELF "AUDIOBOOKSHELF — Audiobook server"
[ -n "${EXISTING_SERVICES[ARM]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ARM \"Automatic Ripping Machine\" ON" _add_if_exists EMBY "EMBY — Media server"
[ -n "${EXISTING_SERVICES[FILEBROWSER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FILEBROWSER \"Web file manager\" ON" _add_if_exists ARM "ARM — Automatic Ripping Machine"
[ -n "${EXISTING_SERVICES[MAGICMIRROR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MAGICMIRROR \"Smart mirror\" ON" _add_if_exists FILEBROWSER "FILEBROWSER — Web file manager"
[ -n "${EXISTING_SERVICES[ACTUALBUDGET]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS ACTUALBUDGET \"Personal finance\" ON" _add_if_exists MAGICMIRROR "MAGICMIRROR — Smart mirror"
[ -n "${EXISTING_SERVICES[KEYCLOAK]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS KEYCLOAK \"Identity management\" ON" _add_if_exists ACTUALBUDGET "ACTUALBUDGET — Personal finance"
[ -n "${EXISTING_SERVICES[CADDY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS CADDY \"Reverse proxy\" ON" _add_if_exists KEYCLOAK "KEYCLOAK — Identity management"
[ -n "${EXISTING_SERVICES[FAIL2BAN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FAIL2BAN \"Intrusion prevention\" ON" _add_if_exists CADDY "CADDY — Reverse proxy"
[ -n "${EXISTING_SERVICES[LYRION]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS LYRION \"Music server\" ON" _add_if_exists FAIL2BAN "FAIL2BAN — Intrusion prevention"
[ -n "${EXISTING_SERVICES[MEALIE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MEALIE \"Recipe manager\" ON" _add_if_exists LYRION "LYRION — Music server"
[ -n "${EXISTING_SERVICES[MINECRAFT]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MINECRAFT \"Game server\" ON" _add_if_exists MEALIE "MEALIE — Recipe manager"
[ -n "${EXISTING_SERVICES[JELLYFIN]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS JELLYFIN \"Media server\" ON" _add_if_exists MINECRAFT "MINECRAFT — Game server"
[ -n "${EXISTING_SERVICES[FRIGATE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FRIGATE \"NVR cameras\" ON" _add_if_exists JELLYFIN "JELLYFIN — Media server"
[ -n "${EXISTING_SERVICES[NTFY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS NTFY \"Push notifications\" ON" _add_if_exists FRIGATE "FRIGATE — NVR cameras"
[ -n "${EXISTING_SERVICES[UPTIMEKUMA]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS UPTIMEKUMA \"Service monitoring\" ON" _add_if_exists NTFY "NTFY — Push notifications"
[ -n "${EXISTING_SERVICES[WGEASY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS WGEASY \"WireGuard VPN\" ON" _add_if_exists UPTIMEKUMA "UPTIMEKUMA — Service monitoring"
[ -n "${EXISTING_SERVICES[TRACCAR]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS TRACCAR \"GPS tracking\" ON" _add_if_exists WGEASY "WGEASY — WireGuard VPN"
[ -n "${EXISTING_SERVICES[PORTAINER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS PORTAINER \"Docker management\" ON" _add_if_exists TRACCAR "TRACCAR — GPS tracking"
[ -n "${EXISTING_SERVICES[MESHCENTRAL]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS MESHCENTRAL \"Remote management\" ON" _add_if_exists PORTAINER "PORTAINER — Docker management"
[ -n "${EXISTING_SERVICES[FINDMYDEVICE]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FINDMYDEVICE \"Device tracking\" ON" _add_if_exists MESHCENTRAL "MESHCENTRAL — Remote management"
[ -n "${EXISTING_SERVICES[FRIGATE_NOTIFY]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS FRIGATE_NOTIFY \"Frigate notifications\" ON" _add_if_exists FINDMYDEVICE "FINDMYDEVICE — Device tracking"
[ -n "${EXISTING_SERVICES[WATCHTOWER]}" ] && UNINSTALL_OPTIONS="$UNINSTALL_OPTIONS WATCHTOWER \"Container updates\" ON" _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 if [ ${#_SVC_KEYS[@]} -eq 0 ]; then
whiptail --title "No Services Found" --msgbox "No services detected to uninstall." 8 50 echo "No installed services detected to uninstall."
SELECTED_SERVICES="" SELECTED_SERVICES=""
MENU_MODE="CANCEL"
else else
SELECTED_SERVICES=$(eval "whiptail --title 'Select Services to Uninstall' \ declare -a _SVC_FLAGS
--checklist 'WARNING: Data will be backed up but services will be removed' 25 78 17 \ for i in "${!_SVC_KEYS[@]}"; do _SVC_FLAGS[$i]=true; done
$UNINSTALL_OPTIONS \ echo ""
3>&1 1>&2 2>&3") 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 fi
;; ;;
3|*) 3|*)
# Cancel
SELECTED_SERVICES="" SELECTED_SERVICES=""
MENU_MODE="CANCEL" MENU_MODE="CANCEL"
;; ;;
esac esac
# Check if user cancelled # Set installation flags from SELECTED_SERVICES
if [ $? -ne 0 ]; then WHIPTAIL_USED=true
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)
: ${INSTALL_IMMICH:="n"} : ${INSTALL_IMMICH:="n"}
: ${INSTALL_AUDIOBOOKSHELF:="n"} : ${INSTALL_AUDIOBOOKSHELF:="n"}
: ${INSTALL_EMBY:="n"} : ${INSTALL_EMBY:="n"}
@@ -3944,7 +3944,6 @@ else
: ${INSTALL_FRIGATE_NOTIFY:="n"} : ${INSTALL_FRIGATE_NOTIFY:="n"}
: ${INSTALL_WATCHTOWER:="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 "IMMICH"; then INSTALL_IMMICH="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "AUDIOBOOKSHELF"; then INSTALL_AUDIOBOOKSHELF="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 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 "MEALIE"; then INSTALL_MEALIE="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "MINECRAFT"; then INSTALL_MINECRAFT="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 "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 "NTFY"; then INSTALL_NTFY="y"; fi
if echo "$SELECTED_SERVICES" | grep -q "UPTIMEKUMA"; then INSTALL_UPTIMEKUMA="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 if echo "$SELECTED_SERVICES" | grep -q "WGEASY"; then INSTALL_WGEASY="y"; fi
@@ -4062,24 +4061,11 @@ else
fi fi
# After uninstall, exit the Docker apps section # After uninstall, exit the Docker apps section
WHIPTAIL_USED=true
elif [ "$MENU_MODE" = "INSTALL" ]; then elif [ "$MENU_MODE" = "INSTALL" ]; then
echo "" echo ""
echo "Selected services:" echo "Selected services:"
echo "$SELECTED_SERVICES" | tr '"' '\n' | grep -v '^$' | sed 's/^/ - /' echo "$SELECTED_SERVICES" | tr ' ' '\n' | grep -v '^$' | sed 's/^/ - /'
echo "" 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 fi
# Skip installation section if we just did uninstalls # Skip installation section if we just did uninstalls