#!/bin/bash # Script to guide user through service selection for n8n-installer # Source utility functions, if any, assuming it's in the same directory # and .env is in the parent directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" ENV_FILE="$PROJECT_ROOT/.env" # Source the utilities file source "$(dirname "$0")/utils.sh" # UTILS_SCRIPT="$SCRIPT_DIR/utils.sh" # Uncomment if utils.sh contains relevant functions # if [ -f "$UTILS_SCRIPT" ]; then # source "$UTILS_SCRIPT" # fi # Function to check if whiptail is installed check_whiptail() { if ! command -v whiptail &> /dev/null; then log_error "'whiptail' is not installed." log_info "This tool is required for the interactive service selection." log_info "On Debian/Ubuntu, you can install it using: sudo apt-get install whiptail" log_info "Please install whiptail and try again." exit 1 fi } # Call the check check_whiptail # Store original DEBIAN_FRONTEND and set to dialog for whiptail ORIGINAL_DEBIAN_FRONTEND="$DEBIAN_FRONTEND" export DEBIAN_FRONTEND=dialog # --- Read current COMPOSE_PROFILES from .env --- CURRENT_PROFILES_VALUE="" if [ -f "$ENV_FILE" ]; then LINE_CONTENT=$(grep "^COMPOSE_PROFILES=" "$ENV_FILE" || echo "") if [ -n "$LINE_CONTENT" ]; then # Get value after '=', remove potential surrounding quotes CURRENT_PROFILES_VALUE=$(echo "$LINE_CONTENT" | cut -d'=' -f2- | sed 's/^"//' | sed 's/"$//') fi fi # Prepare comma-separated current profiles for easy matching, adding leading/trailing commas current_profiles_for_matching=",$CURRENT_PROFILES_VALUE," # --- Define available services and their descriptions --- # Base service definitions (tag, description) base_services_data=( "cloudflare-tunnel" "Cloudflare Tunnel (Zero-Trust Secure Access)" "comfyui" "ComfyUI (Node-based Stable Diffusion UI)" "crawl4ai" "Crawl4ai (Web Crawler for AI)" "dify" "Dify (AI Application Development Platform with LLMOps)" "flowise" "Flowise (AI Agent Builder)" "gotenberg" "Gotenberg (Document Conversion API)" "langfuse" "Langfuse Suite (AI Observability - includes Clickhouse, Minio)" "letta" "Letta (Agent Server & SDK)" "libretranslate" "LibreTranslate (Self-hosted translation API - 50+ languages)" "monitoring" "Monitoring Suite (Prometheus, Grafana, cAdvisor, Node-Exporter)" "n8n" "n8n, n8n-worker, n8n-import (Workflow Automation)" "neo4j" "Neo4j (Graph Database)" "ollama" "Ollama (Local LLM Runner - select hardware in next step)" "open-webui" "Open WebUI (ChatGPT-like Interface)" "paddleocr" "PaddleOCR (OCR API Server)" "portainer" "Portainer (Docker management UI)" "postiz" "Postiz (Social publishing platform)" "python-runner" "Python Runner (Run your custom Python code from ./python-runner)" "qdrant" "Qdrant (Vector Database)" "ragapp" "RAGApp (Open-source RAG UI + API)" "searxng" "SearXNG (Private Metasearch Engine)" "supabase" "Supabase (Backend as a Service)" "weaviate" "Weaviate (Vector Database with API Key Auth)" ) services=() # This will be the final array for whiptail # Populate the services array for whiptail based on current profiles or defaults idx=0 while [ $idx -lt ${#base_services_data[@]} ]; do tag="${base_services_data[idx]}" description="${base_services_data[idx+1]}" status="OFF" # Default to OFF if [ -n "$CURRENT_PROFILES_VALUE" ] && [ "$CURRENT_PROFILES_VALUE" != '""' ]; then # Check if .env has profiles if [[ "$tag" == "ollama" ]]; then if [[ "$current_profiles_for_matching" == *",cpu,"* || \ "$current_profiles_for_matching" == *",gpu-nvidia,"* || \ "$current_profiles_for_matching" == *",gpu-amd,"* ]]; then status="ON" fi elif [[ "$current_profiles_for_matching" == *",$tag,"* ]]; then status="ON" fi else # .env has no COMPOSE_PROFILES or it's empty/just quotes, use hardcoded defaults case "$tag" in "n8n"|"flowise"|"monitoring") status="ON" ;; *) status="OFF" ;; esac fi services+=("$tag" "$description" "$status") idx=$((idx + 2)) done # Use whiptail to display the checklist num_services=$(( ${#services[@]} / 3 )) CHOICES=$(whiptail --title "Service Selection Wizard" --checklist \ "Choose the services you want to deploy.\nUse ARROW KEYS to navigate, SPACEBAR to select/deselect, ENTER to confirm." 32 90 $num_services \ "${services[@]}" \ 3>&1 1>&2 2>&3) # Restore original DEBIAN_FRONTEND if [ -n "$ORIGINAL_DEBIAN_FRONTEND" ]; then export DEBIAN_FRONTEND="$ORIGINAL_DEBIAN_FRONTEND" else unset DEBIAN_FRONTEND fi # Exit if user pressed Cancel or Esc exitstatus=$? if [ $exitstatus -ne 0 ]; then log_info "Service selection cancelled by user. Exiting wizard." log_info "No changes made to service profiles. Default services will be used." # Set COMPOSE_PROFILES to empty to ensure only core services run if [ ! -f "$ENV_FILE" ]; then touch "$ENV_FILE" fi if grep -q "^COMPOSE_PROFILES=" "$ENV_FILE"; then sed -i.bak "/^COMPOSE_PROFILES=/d" "$ENV_FILE" fi echo "COMPOSE_PROFILES=" >> "$ENV_FILE" exit 0 fi # Process selected services selected_profiles=() ollama_selected=0 ollama_profile="" if [ -n "$CHOICES" ]; then # Whiptail returns a string like "tag1" "tag2" "tag3" # We need to remove quotes and convert to an array temp_choices=() eval "temp_choices=($CHOICES)" for choice in "${temp_choices[@]}"; do if [ "$choice" == "ollama" ]; then ollama_selected=1 else selected_profiles+=("$choice") fi done fi # If Ollama was selected, prompt for the hardware profile if [ $ollama_selected -eq 1 ]; then # Determine default selected Ollama hardware profile from .env default_ollama_hardware="cpu" # Fallback default ollama_hw_on_cpu="OFF" ollama_hw_on_gpu_nvidia="OFF" ollama_hw_on_gpu_amd="OFF" # Check current_profiles_for_matching which includes commas, e.g., ",cpu," if [[ "$current_profiles_for_matching" == *",cpu,"* ]]; then ollama_hw_on_cpu="ON" default_ollama_hardware="cpu" elif [[ "$current_profiles_for_matching" == *",gpu-nvidia,"* ]]; then ollama_hw_on_gpu_nvidia="ON" default_ollama_hardware="gpu-nvidia" elif [[ "$current_profiles_for_matching" == *",gpu-amd,"* ]]; then ollama_hw_on_gpu_amd="ON" default_ollama_hardware="gpu-amd" else # If ollama was selected in the main list, but no specific hardware profile was previously set, # default to CPU ON for the radiolist. ollama_hw_on_cpu="ON" default_ollama_hardware="cpu" fi ollama_hardware_options=( "cpu" "CPU (Recommended for most users)" "$ollama_hw_on_cpu" "gpu-nvidia" "NVIDIA GPU (Requires NVIDIA drivers & CUDA)" "$ollama_hw_on_gpu_nvidia" "gpu-amd" "AMD GPU (Requires ROCm drivers)" "$ollama_hw_on_gpu_amd" ) CHOSEN_OLLAMA_PROFILE=$(whiptail --title "Ollama Hardware Profile" --default-item "$default_ollama_hardware" --radiolist \ "Choose the hardware profile for Ollama. This will be added to your Docker Compose profiles." 15 78 3 \ "${ollama_hardware_options[@]}" \ 3>&1 1>&2 2>&3) ollama_exitstatus=$? if [ $ollama_exitstatus -eq 0 ] && [ -n "$CHOSEN_OLLAMA_PROFILE" ]; then selected_profiles+=("$CHOSEN_OLLAMA_PROFILE") ollama_profile="$CHOSEN_OLLAMA_PROFILE" # Store for user message log_info "Ollama hardware profile selected: $CHOSEN_OLLAMA_PROFILE" else log_info "Ollama hardware profile selection cancelled or no choice made. Ollama will not be configured with a specific hardware profile." # ollama_selected remains 1, but no specific profile is added. # This means "ollama" won't be in COMPOSE_PROFILES unless a hardware profile is chosen. ollama_selected=0 # Mark as not fully selected if profile choice is cancelled fi fi if [ ${#selected_profiles[@]} -eq 0 ]; then log_info "No optional services selected." COMPOSE_PROFILES_VALUE="" else log_info "You have selected the following service profiles to be deployed:" # Join the array into a comma-separated string COMPOSE_PROFILES_VALUE=$(IFS=,; echo "${selected_profiles[*]}") for profile in "${selected_profiles[@]}"; do # Check if the current profile is an Ollama hardware profile that was chosen if [[ "$profile" == "cpu" || "$profile" == "gpu-nvidia" || "$profile" == "gpu-amd" ]]; then if [ "$profile" == "$ollama_profile" ]; then # ollama_profile stores the CHOSEN_OLLAMA_PROFILE from this wizard run echo " - Ollama ($profile profile)" else # This handles a (highly unlikely) non-Ollama service named "cpu", "gpu-nvidia", or "gpu-amd" echo " - $profile" fi else echo " - $profile" fi done fi # Update or add COMPOSE_PROFILES in .env file # Ensure .env file exists (it should have been created by 03_generate_secrets.sh or exist from previous run) if [ ! -f "$ENV_FILE" ]; then log_warning "'.env' file not found at $ENV_FILE. Creating it." touch "$ENV_FILE" fi # If Cloudflare Tunnel is selected, prompt for the token and write to .env cloudflare_selected=0 for profile in "${selected_profiles[@]}"; do if [ "$profile" == "cloudflare-tunnel" ]; then cloudflare_selected=1 break fi done if [ $cloudflare_selected -eq 1 ]; then existing_cf_token="" if grep -q "^CLOUDFLARE_TUNNEL_TOKEN=" "$ENV_FILE"; then existing_cf_token=$(grep "^CLOUDFLARE_TUNNEL_TOKEN=" "$ENV_FILE" | cut -d'=' -f2- | sed 's/^\"//' | sed 's/\"$//') fi if [ -n "$existing_cf_token" ]; then log_info "Cloudflare Tunnel token found in .env; reusing it." # Do not prompt; keep existing token as-is else log_info "Cloudflare Tunnel selected. Please provide your Cloudflare Tunnel token." echo "" read -p "Cloudflare Tunnel Token: " input_cf_token token_to_write="$input_cf_token" # Update the .env with the token (may be empty if user skipped) if grep -q "^CLOUDFLARE_TUNNEL_TOKEN=" "$ENV_FILE"; then sed -i.bak "/^CLOUDFLARE_TUNNEL_TOKEN=/d" "$ENV_FILE" fi echo "CLOUDFLARE_TUNNEL_TOKEN=\"$token_to_write\"" >> "$ENV_FILE" if [ -n "$token_to_write" ]; then log_success "Cloudflare Tunnel token saved to .env." echo "" echo "🔒 After confirming the tunnel works, consider closing ports 80, 443, and 7687 in your firewall." else log_warning "Cloudflare Tunnel token was left empty. You can set it later in .env." fi fi fi # Remove existing COMPOSE_PROFILES line if it exists if grep -q "^COMPOSE_PROFILES=" "$ENV_FILE"; then # Using a different delimiter for sed because a profile name might contain '/' (unlikely here) sed -i.bak "\|^COMPOSE_PROFILES=|d" "$ENV_FILE" fi # Add the new COMPOSE_PROFILES line echo "COMPOSE_PROFILES=${COMPOSE_PROFILES_VALUE}" >> "$ENV_FILE" if [ -z "$COMPOSE_PROFILES_VALUE" ]; then log_info "Only core services (Caddy, Postgres, Redis) will be started." else log_info "The following Docker Compose profiles will be active: ${COMPOSE_PROFILES_VALUE}" fi # Make the script executable (though install.sh calls it with bash) chmod +x "$SCRIPT_DIR/04_wizard.sh" exit 0