#!/bin/bash # Combined update script for docker-compose-nas # - Updates .env file from .env.example while preserving values # - Updates Authelia configuration from example file # - Configures services with correct paths and API keys # Created: April 26, 2025 set -e # Color definitions RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' BLUE='\033[0;34m' MAGENTA='\033[0;35m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # No Color # Files ENV_FILE=".env" ENV_EXAMPLE=".env.example" TIMESTAMP=$(date +"%Y%m%d-%H%M%S") ENV_BACKUP=".env.${TIMESTAMP}.bak" AUTHELIA_CONFIG="authelia/configuration.yml" AUTHELIA_CONFIG_EXAMPLE="authelia/configuration.example.yml" AUTHELIA_CONFIG_BACKUP="authelia/configuration.${TIMESTAMP}.bak" # Print section header print_header() { echo -e "\n${CYAN}${BOLD}$1${NC}" echo -e "${CYAN}$(printf '=%.0s' $(seq 1 ${#1}))${NC}" } # Check if a file exists check_file() { if [ ! -f "$1" ]; then echo -e "${RED}Error: $1 doesn't exist${NC}" return 1 fi return 0 } # Function to create a backup create_backup() { echo -e "${BLUE}Creating backup of $1 as $2...${NC}" cp "$1" "$2" } ################################################## # PART 1: Update .env file from .env.example ################################################## update_env_file() { print_header "Environment File Update Tool" # Check if files exist if [ ! -f "$ENV_FILE" ]; then echo -e "${RED}Error: $ENV_FILE doesn't exist${NC}" echo -e "Creating a new $ENV_FILE from $ENV_EXAMPLE" cp "$ENV_EXAMPLE" "$ENV_FILE" echo -e "${GREEN}Done! Please review and fill in required values in $ENV_FILE${NC}" return 0 fi if ! check_file "$ENV_EXAMPLE"; then return 1 fi echo -e "${BLUE}This will update your $ENV_FILE based on the structure in $ENV_EXAMPLE${NC}" echo -e "${BLUE}Your existing values will be preserved where possible${NC}" echo -e "${BLUE}Backup will be created as: $ENV_BACKUP${NC}" echo -e "${YELLOW}Continue? [y/N]:${NC}" read -r answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then echo -e "${RED}Environment update cancelled.${NC}" return 0 fi # Create backup of current .env create_backup "$ENV_FILE" "$ENV_BACKUP" # Store current env values echo -e "${BLUE}Reading current environment values...${NC}" declare -A current_values declare -A current_keys_present while IFS='=' read -r key value; do # Skip comments and empty lines if [[ ! "$key" =~ ^#.*$ ]] && [[ ! -z "$key" ]]; then # Clean up any comments after the value value=$(echo "$value" | sed 's/[[:space:]]*#.*$//') # Trim leading/trailing whitespace key=$(echo "$key" | xargs) value=$(echo "$value" | xargs) # Store in associative array if key is not empty if [[ ! -z "$key" ]]; then current_values["$key"]="$value" # Track that this key existed in original file, regardless of value current_keys_present["$key"]=1 fi fi done < "$ENV_FILE" # Create new env file from example echo -e "${BLUE}Creating new $ENV_FILE from $ENV_EXAMPLE...${NC}" cp "$ENV_EXAMPLE" "$ENV_FILE.new" # Track which keys from the current env have been used declare -A used_keys # Track new keys that need attention new_keys=() # Track keys with special warnings special_keys=() # Process the template and fill in values from current env while IFS= read -r line; do if [[ "$line" =~ ^([A-Za-z0-9_]+)=(.*)$ ]]; then key="${BASH_REMATCH[1]}" default_value="${BASH_REMATCH[2]}" # Mark the key as used if it exists in the original file if [[ -n "${current_keys_present[$key]}" ]]; then used_keys["$key"]=1 # Replace the line with the current value if one exists if [[ -n "${current_values[$key]}" ]]; then sed -i "s|^$key=.*$|$key=${current_values[$key]}|" "$ENV_FILE.new" fi # If key doesn't exist in original file and has empty/placeholder value elif [[ -z "$default_value" ]] || [[ "$default_value" == '""' ]] || [[ "$default_value" == "''" ]]; then new_keys+=("$key") # Special attention for Authelia keys if [[ "$key" == AUTHELIA_*_SECRET* ]] || [[ "$key" == AUTHELIA_*_KEY* ]]; then special_keys+=("$key") fi fi fi done < "$ENV_FILE.new" # Create section for unused/deprecated keys at the bottom of the file echo -e "\n\n# --- DEPRECATED OR UNUSED KEYS (Kept for Reference) ---" >> "$ENV_FILE.new" echo -e "# Keys below were in your original .env but aren't in the current .env.example" >> "$ENV_FILE.new" echo -e "# They may be deprecated or renamed. Review and remove if no longer needed\n" >> "$ENV_FILE.new" unused_keys_count=0 for key in "${!current_values[@]}"; do if [[ -z "${used_keys[$key]}" ]]; then echo "$key=${current_values[$key]} # DEPRECATED/UNUSED - Review" >> "$ENV_FILE.new" unused_keys_count=$((unused_keys_count + 1)) fi done # Replace the old file with the new one mv "$ENV_FILE.new" "$ENV_FILE" # Generate summary echo -e "\n${GREEN}${BOLD}Environment Update Complete!${NC}" echo -e "${BLUE}Summary:${NC}" echo -e " - ${CYAN}Original config backed up to: $ENV_BACKUP${NC}" echo -e " - ${CYAN}Updated .env structure to match .env.example${NC}" echo -e " - ${CYAN}Preserved ${#used_keys[@]} existing values${NC}" if [[ $unused_keys_count -gt 0 ]]; then echo -e " - ${YELLOW}Found $unused_keys_count deprecated/unused keys${NC}" echo -e " ${YELLOW}These have been moved to the bottom of the file with warnings${NC}" fi if [[ ${#new_keys[@]} -gt 0 ]]; then echo -e "\n${YELLOW}${BOLD}NEW KEYS NEEDING ATTENTION:${NC}" echo -e "${YELLOW}The following keys are new and may need values set:${NC}" for key in "${new_keys[@]}"; do echo -e " - ${MAGENTA}$key${NC}" done fi if [[ ${#special_keys[@]} -gt 0 ]]; then echo -e "\n${RED}${BOLD}IMPORTANT SECURITY KEYS:${NC}" echo -e "${RED}The following keys require secure values:${NC}" for key in "${special_keys[@]}"; do echo -e " - ${MAGENTA}$key${NC}" # Specific advice for Authelia keys if [[ "$key" == AUTHELIA_*_SECRET* ]] || [[ "$key" == AUTHELIA_*_KEY* ]]; then echo -e " ${CYAN}Generate with: ${GREEN}openssl rand -hex 32${NC}" fi done fi echo -e "\n${BLUE}Review your updated $ENV_FILE file and adjust any values as needed.${NC}" } ################################################## # PART 2: Update Authelia configuration ################################################## update_authelia_config() { print_header "Authelia Configuration Update Tool" # Check if files exist if ! check_file "$AUTHELIA_CONFIG_EXAMPLE"; then echo -e "${RED}Error: Example configuration file doesn't exist${NC}" return 1 fi # If config file doesn't exist, create it from example if [ ! -f "$AUTHELIA_CONFIG" ]; then echo -e "${YELLOW}Authelia configuration file doesn't exist, creating from example...${NC}" cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG" echo -e "${GREEN}Created new Authelia configuration file.${NC}" return 0 fi echo -e "${BLUE}This will update your Authelia configuration based on the example file${NC}" echo -e "${BLUE}Backup will be created as: $AUTHELIA_CONFIG_BACKUP${NC}" echo -e "${YELLOW}Continue? [y/N]:${NC}" read -r answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then echo -e "${RED}Authelia config update cancelled.${NC}" return 0 fi # Create backup of current config create_backup "$AUTHELIA_CONFIG" "$AUTHELIA_CONFIG_BACKUP" # Copy the example file over the current one cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG" # Get the tailnet domain from .env for proper configuration if [ -f "$ENV_FILE" ]; then TAILNET_DOMAIN=$(grep -o "TAILSCALE_TAILNET_DOMAIN=.*" "$ENV_FILE" | cut -d'=' -f2 | tr -d '"' | tr -d "'") TAILSCALE_HOSTNAME=$(grep -o "TAILSCALE_HOSTNAME=.*" "$ENV_FILE" | cut -d'=' -f2 | tr -d '"' | tr -d "'") REDIS_PASSWORD=$(grep -o "AUTHELIA_REDIS_PASSWORD=.*" "$ENV_FILE" | cut -d'=' -f2 | tr -d '"' | tr -d "'") if [ -n "$TAILNET_DOMAIN" ] && [ -n "$TAILSCALE_HOSTNAME" ]; then # Use the full Tailnet domain (e.g., "example.ts.net") for cookies # not just "ts.net" which is a public suffix and not allowed # Replace domain placeholder with actual Tailnet domain sed -i "s/domain: 'your-tailnet.ts.net'/domain: '$TAILNET_DOMAIN'/g" "$AUTHELIA_CONFIG" # For access control rules, update both wildcards and direct domain sed -i "s/domain: '\*.your-tailnet.ts.net'/domain: '\*.$TAILNET_DOMAIN'/g" "$AUTHELIA_CONFIG" sed -i "s/domain: 'your-tailnet.ts.net'/domain: '$TAILNET_DOMAIN'/g" "$AUTHELIA_CONFIG" # For URLs, use the full hostname sed -i "s/https:\/\/tailscale-nas.your-tailnet.ts.net/https:\/\/$TAILSCALE_HOSTNAME.$TAILNET_DOMAIN/g" "$AUTHELIA_CONFIG" # Ensure Redis password is set correctly if [ -n "$REDIS_PASSWORD" ]; then # Check if redis connection string exists in the config if grep -q "redis:" "$AUTHELIA_CONFIG"; then echo -e "${CYAN}Setting Redis password in Authelia configuration...${NC}" # Make sure the Redis password in configuration matches the one in .env sed -i "s/password: \${AUTHELIA_SESSION_REDIS_PASSWORD}/password: $REDIS_PASSWORD/g" "$AUTHELIA_CONFIG" fi fi echo -e "${GREEN}Configured Authelia with your Tailscale domain:${NC}" echo -e "${CYAN} - Cookie domain: ${GREEN}$TAILNET_DOMAIN${NC}" echo -e "${CYAN} - Access control for: ${GREEN}*.$TAILNET_DOMAIN and $TAILNET_DOMAIN${NC}" echo -e "${CYAN} - Authelia URL: ${GREEN}https://$TAILSCALE_HOSTNAME.$TAILNET_DOMAIN${NC}" echo -e "${CYAN} - Redis password: ${GREEN}Configured${NC}" else echo -e "${YELLOW}Warning: Could not find both TAILSCALE_HOSTNAME and TAILSCALE_TAILNET_DOMAIN in .env${NC}" echo -e "${YELLOW}You may need to manually edit $AUTHELIA_CONFIG to set your domains correctly${NC}" fi fi echo -e "${GREEN}${BOLD}Authelia Configuration Update Complete!${NC}" echo -e "${BLUE}${BOLD}Note:${NC} Original config backed up to: $AUTHELIA_CONFIG_BACKUP" } ################################################## # PART 3: Update Service Configurations ################################################## update_arr_config() { local container=$1 local path=$2 echo -e "${BLUE}Configuring $container URL base and extracting API key...${NC}" # Check if config file exists config_file="${CONFIG_ROOT:-.}/$container/config.xml" timeout=10 count=0 while [ ! -f "$config_file" ] && [ $count -lt $timeout ]; do echo -e "${YELLOW}Waiting for $container config file to be available... ($(($timeout-$count)) seconds remaining)${NC}" sleep 1 count=$((count+1)) done if [ ! -f "$config_file" ]; then echo -e "${RED}Error: Config file for $container not found after $timeout seconds${NC}" echo -e "${YELLOW}Is $container configured properly? Check the container logs${NC}" return 1 fi # Update URL base if ! grep -q "/$path" "$config_file"; then echo -e "${BLUE}Setting URL base to /$path${NC}" sed -i.bak "s|.*|/$path|" "$config_file" 2>/dev/null || sed -i.bak "s||/$path|" "$config_file" 2>/dev/null if [ $? -ne 0 ]; then echo -e "${RED}Failed to update URL base in $config_file${NC}" return 1 fi rm -f "${config_file}.bak" else echo -e "${GREEN}URL base already set to /$path${NC}" fi # Extract API key api_key=$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "$config_file") if [ -z "$api_key" ]; then echo -e "${YELLOW}Could not find API key in $container configuration${NC}" else CONTAINER_NAME_UPPER=$(echo "$container" | tr '[:lower:]' '[:upper:]') current_key=$(grep "^${CONTAINER_NAME_UPPER}_API_KEY=" .env | cut -d'=' -f2) if [ "$current_key" != "$api_key" ]; then echo -e "${BLUE}Updating ${CONTAINER_NAME_UPPER}_API_KEY in .env file${NC}" sed -i.bak "s|^${CONTAINER_NAME_UPPER}_API_KEY=.*|${CONTAINER_NAME_UPPER}_API_KEY=$api_key|" .env rm -f .env.bak else echo -e "${GREEN}${CONTAINER_NAME_UPPER}_API_KEY already up to date in .env file${NC}" fi fi echo -e "${GREEN}Restarting $container to apply changes...${NC}" if ! docker compose restart "$container"; then echo -e "${RED}Failed to restart $container${NC}" return 1 fi echo -e "${GREEN}Successfully configured $container${NC}" return 0 } update_qbittorrent_config() { local container=$1 echo -e "${BLUE}Configuring $container WebUI password...${NC}" # Stop the container to ensure we can modify the config echo -e "${BLUE}Stopping $container to update configuration...${NC}" if ! docker compose stop "$container"; then echo -e "${RED}Failed to stop $container${NC}" return 1 fi # Check if config file exists config_file="${CONFIG_ROOT:-.}/$container/qBittorrent/qBittorrent.conf" timeout=10 count=0 while [ ! -f "$config_file" ] && [ $count -lt $timeout ]; do echo -e "${YELLOW}Waiting for $container config file to be available... ($(($timeout-$count)) seconds remaining)${NC}" sleep 1 count=$((count+1)) done if [ ! -f "$config_file" ]; then echo -e "${RED}Error: Config file for $container not found after $timeout seconds${NC}" echo -e "${YELLOW}Is $container configured properly? Check the container logs${NC}" # Restart the container anyway docker compose start "$container" return 1 fi # Check if password is already set if grep -q "WebUI\\\\Password_PBKDF2=" "$config_file"; then echo -e "${GREEN}Password already configured in $container${NC}" else echo -e "${BLUE}Setting default password for $container WebUI${NC}" sed -i.bak '/WebUI\\\\ServerDomains=*/a WebUI\\\\Password_PBKDF2="@ByteArray(ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==)"' "$config_file" if [ $? -ne 0 ]; then echo -e "${RED}Failed to update password in $config_file${NC}" docker compose start "$container" return 1 fi rm -f "${config_file}.bak" echo -e "${GREEN}Default password set. Username: admin / Password: adminadmin${NC}" echo -e "${YELLOW}You should change this password in the WebUI settings${NC}" fi echo -e "${GREEN}Starting $container with updated configuration...${NC}" if ! docker compose start "$container"; then echo -e "${RED}Failed to start $container${NC}" return 1 fi echo -e "${GREEN}Successfully configured $container${NC}" return 0 } update_bazarr_config() { local container=$1 echo -e "${BLUE}Configuring $container URL base and service connections...${NC}" # Check if config file exists config_file="${CONFIG_ROOT:-.}/$container/config/config/config.yaml" timeout=15 count=0 while [ ! -f "$config_file" ] && [ $count -lt $timeout ]; do echo -e "${YELLOW}Waiting for $container config file to be available... ($(($timeout-$count)) seconds remaining)${NC}" sleep 1 count=$((count+1)) done if [ ! -f "$config_file" ]; then echo -e "${RED}Error: Config file for $container not found after $timeout seconds${NC}" echo -e "${YELLOW}Is $container configured properly? Check the container logs${NC}" return 1 fi # Update URL base if grep -q "base_url: '/$container'" "$config_file"; then echo -e "${GREEN}URL base already set to /$container${NC}" else echo -e "${BLUE}Setting URL base to /$container${NC}" sed -i.bak "s|base_url: .*|base_url: '/$container'|" "$config_file" if [ $? -ne 0 ]; then echo -e "${RED}Failed to update URL base in $config_file${NC}" return 1 fi rm -f "${config_file}.bak" fi # Configure Radarr integration updates_needed=false if grep -q "use_radarr: false" "$config_file"; then echo -e "${BLUE}Enabling Radarr integration${NC}" sed -i.bak "s/use_radarr: false/use_radarr: true/" "$config_file" rm -f "${config_file}.bak" updates_needed=true else echo -e "${GREEN}Radarr integration already enabled${NC}" fi # Configure Sonarr integration if grep -q "use_sonarr: false" "$config_file"; then echo -e "${BLUE}Enabling Sonarr integration${NC}" sed -i.bak "s/use_sonarr: false/use_sonarr: true/" "$config_file" rm -f "${config_file}.bak" updates_needed=true else echo -e "${GREEN}Sonarr integration already enabled${NC}" fi # Get Sonarr API key if needed sonarr_config="${CONFIG_ROOT:-.}/sonarr/config.xml" if [ -f "$sonarr_config" ]; then sonarr_api_key=$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "$sonarr_config") if [ -n "$sonarr_api_key" ]; then echo -e "${BLUE}Setting Sonarr API key and URL in Bazarr config${NC}" sed -i.bak "/sonarr:/,/^radarr:/ { s/apikey: .*/apikey: $sonarr_api_key/; s|base_url: .*|base_url: /sonarr|; s/ip: .*/ip: sonarr/ }" "$config_file" rm -f "${config_file}.bak" updates_needed=true else echo -e "${YELLOW}Could not find Sonarr API key${NC}" fi else echo -e "${YELLOW}Sonarr config not found, skipping Sonarr API configuration${NC}" fi # Get Radarr API key if needed radarr_config="${CONFIG_ROOT:-.}/radarr/config.xml" if [ -f "$radarr_config" ]; then radarr_api_key=$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "$radarr_config") if [ -n "$radarr_api_key" ]; then echo -e "${BLUE}Setting Radarr API key and URL in Bazarr config${NC}" sed -i.bak "/radarr:/,/^sonarr:/ { s/apikey: .*/apikey: $radarr_api_key/; s|base_url: .*|base_url: /radarr|; s/ip: .*/ip: radarr/ }" "$config_file" rm -f "${config_file}.bak" updates_needed=true else echo -e "${YELLOW}Could not find Radarr API key${NC}" fi else echo -e "${YELLOW}Radarr config not found, skipping Radarr API configuration${NC}" fi # Extract Bazarr API key for .env bazarr_api_key=$(sed -n 's/.*apikey: \(.*\)*/\1/p' "$config_file" | head -n 1) if [ -n "$bazarr_api_key" ]; then current_key=$(grep "^BAZARR_API_KEY=" .env | cut -d'=' -f2) if [ "$current_key" != "$bazarr_api_key" ]; then echo -e "${BLUE}Updating BAZARR_API_KEY in .env file${NC}" sed -i.bak "s|^BAZARR_API_KEY=.*|BAZARR_API_KEY=$bazarr_api_key|" .env rm -f .env.bak else echo -e "${GREEN}BAZARR_API_KEY already up to date in .env file${NC}" fi else echo -e "${YELLOW}Could not find Bazarr API key${NC}" fi if [ "$updates_needed" = true ]; then echo -e "${GREEN}Restarting $container to apply changes...${NC}" if ! docker compose restart "$container"; then echo -e "${RED}Failed to restart $container${NC}" return 1 fi else echo -e "${GREEN}No configuration changes needed for $container${NC}" fi echo -e "${GREEN}Successfully configured $container${NC}" return 0 } update_service_configs() { print_header "Service Configuration Update Tool" echo -e "${BLUE}This will update service configurations for running containers${NC}" echo -e "${BLUE}It will set proper URL bases and extract API keys${NC}" echo -e "${YELLOW}Continue? [y/N]:${NC}" read -r answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then echo -e "${RED}Service configuration update cancelled.${NC}" return 0 fi # Check if Docker is running if ! docker ps > /dev/null 2>&1; then echo -e "${RED}Error: Docker is not running or you don't have permission to use it.${NC}" echo -e "${YELLOW}Make sure Docker is running and you have proper permissions.${NC}" return 1 fi # Keep track of what we updated updated_containers=() not_running_containers=() skipped_containers=() echo -e "${BLUE}Checking for containers to update...${NC}" # Define the list of containers we can update declare -a configurable_containers=("radarr" "sonarr" "lidarr" "prowlarr" "bazarr" "qbittorrent") # First check which containers we should be able to update for container in "${configurable_containers[@]}"; do if ! docker ps --format '{{.Names}}' | grep -q "^${container}$"; then not_running_containers+=("$container") fi done # Now update the running containers for container in $(docker ps --format '{{.Names}}'); do if [[ "$container" =~ ^(radarr|sonarr|lidarr|prowlarr)$ ]]; then echo -e "\n${CYAN}Updating ${BOLD}$container${NC} configuration..." if update_arr_config "$container" "$container"; then updated_containers+=("$container") else skipped_containers+=("$container (error during update)") fi elif [[ "$container" =~ ^(bazarr)$ ]]; then echo -e "\n${CYAN}Updating ${BOLD}$container${NC} configuration..." if update_bazarr_config "$container"; then updated_containers+=("$container") else skipped_containers+=("$container (error during update)") fi elif [[ "$container" =~ ^(qbittorrent)$ ]]; then echo -e "\n${CYAN}Updating ${BOLD}$container${NC} configuration..." if update_qbittorrent_config "$container"; then updated_containers+=("$container") else skipped_containers+=("$container (error during update)") fi fi done # Print summary echo -e "\n${GREEN}${BOLD}Service Configuration Update Complete!${NC}" echo -e "${BLUE}Summary:${NC}" if [[ ${#updated_containers[@]} -gt 0 ]]; then echo -e " ${GREEN}Successfully updated configurations for:${NC}" for container in "${updated_containers[@]}"; do echo -e " - ${CYAN}$container${NC}" done else echo -e " ${YELLOW}No container configurations were updated${NC}" fi if [[ ${#not_running_containers[@]} -gt 0 ]]; then echo -e " ${YELLOW}The following containers were not running (skipped):${NC}" for container in "${not_running_containers[@]}"; do echo -e " - ${YELLOW}$container${NC}" done echo -e " ${BLUE}Start these containers and run the script again to configure them${NC}" fi if [[ ${#skipped_containers[@]} -gt 0 ]]; then echo -e " ${RED}Failed to update configurations for:${NC}" for container in "${skipped_containers[@]}"; do echo -e " - ${RED}$container${NC}" done fi # If we've extracted any API keys, show them if grep -q "_API_KEY=" .env; then echo -e "\n${BLUE}${BOLD}API Keys:${NC}" echo -e "${BLUE}The following API keys were extracted and updated in your .env file:${NC}" grep "_API_KEY=" .env | sort | while read -r line; do key=$(echo "$line" | cut -d'=' -f1) echo -e " - ${CYAN}$key${NC}" done fi } ################################################## # MAIN SCRIPT ################################################## # Display main menu print_header "Docker Compose NAS - Setup Tool" echo -e "${BLUE}This tool will help you configure your Docker Compose NAS setup${NC}" echo -e "${BLUE}Choose one or more options to run:${NC}" echo -e " ${CYAN}1. ${NC}Update .env file from .env.example" echo -e " ${CYAN}2. ${NC}Update Authelia configuration" echo -e " ${CYAN}3. ${NC}Update service configurations" echo -e " ${CYAN}4. ${NC}Run ALL updates" echo -e " ${CYAN}q. ${NC}Quit" echo CHOICE="0" while [[ "$CHOICE" != "q" ]]; do echo -e "${YELLOW}Enter your choice [1-4 or q to quit]: ${NC}" read -r CHOICE case "$CHOICE" in 1) update_env_file ;; 2) update_authelia_config ;; 3) update_service_configs ;; 4) update_env_file update_authelia_config update_service_configs CHOICE="q" # Exit after running all ;; q|Q) echo -e "${GREEN}Exiting setup tool.${NC}" exit 0 ;; *) echo -e "${RED}Invalid choice. Please try again.${NC}" ;; esac done echo -e "\n${GREEN}${BOLD}All updates completed!${NC}" echo -e "${BLUE}Be sure to restart any services that were updated:${NC}" echo -e " ${CYAN}docker compose restart${NC}" echo -e "\n${BLUE}If you updated the Authelia configuration, restart Authelia:${NC}" echo -e " ${CYAN}docker compose restart authelia${NC}"