#!/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 # - Manages Authelia accounts # - Controls service authentication requirements # 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" COMPOSE_FILE="docker-compose.yml" COMPOSE_BACKUP="docker-compose.${TIMESTAMP}.bak" # Check if yq is installed check_yq() { if ! command -v yq &> /dev/null; then echo -e "${YELLOW}Warning: 'yq' is not installed. While not required, it provides better YAML handling.${NC}" echo -e "${YELLOW}Installation instructions: https://github.com/mikefarah/yq#install${NC}" return 1 fi return 0 } # 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 '$AUTHELIA_CONFIG_EXAMPLE' doesn't exist${NC}" return 1 fi if ! check_file "$ENV_FILE"; then echo -e "${RED}Error: Environment file '$ENV_FILE' doesn't exist. Cannot retrieve domain settings.${NC}" return 1 fi # Get the tailnet domain and hostname from .env local TAILNET_DOMAIN=$(grep -oP "^TAILSCALE_TAILNET_DOMAIN=\K.*" "$ENV_FILE" | tr -d '"' | tr -d "'") local TAILSCALE_HOSTNAME=$(grep -oP "^TAILSCALE_HOSTNAME=\K.*" "$ENV_FILE" | tr -d '"' | tr -d "'") local FULL_HOSTNAME="${TAILSCALE_HOSTNAME}.${TAILNET_DOMAIN}" local WILDCARD_DOMAIN="*.${TAILNET_DOMAIN}" if [ -z "$TAILNET_DOMAIN" ] || [ -z "$TAILSCALE_HOSTNAME" ]; then echo -e "${RED}Error: Could not read TAILSCALE_TAILNET_DOMAIN or TAILSCALE_HOSTNAME from $ENV_FILE${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 '$AUTHELIA_CONFIG' doesn't exist, creating from example...${NC}" cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG" echo -e "${GREEN}Created new Authelia configuration file.${NC}" # Proceed to update the newly created file else echo -e "${BLUE}This will update your Authelia configuration '$AUTHELIA_CONFIG' based on '$AUTHELIA_CONFIG_EXAMPLE'${NC}" echo -e "${BLUE}Your Tailscale domain settings from '$ENV_FILE' will be applied.${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 only if it exists create_backup "$AUTHELIA_CONFIG" "$AUTHELIA_CONFIG_BACKUP" fi # Check for yq if check_yq; then echo -e "${BLUE}Using 'yq' to update Authelia configuration...${NC}" # Create a temporary file from the example local TEMP_CONFIG="${AUTHELIA_CONFIG}.tmp" cp "$AUTHELIA_CONFIG_EXAMPLE" "$TEMP_CONFIG" # Preserve specific existing values if the original config exists if [ -f "$AUTHELIA_CONFIG_BACKUP" ]; then # Use backup as source of truth for existing values echo -e "${BLUE}Attempting to preserve existing secrets and notifier settings...${NC}" local existing_jwt_secret=$(yq e '.identity_validation.reset_password.jwt_secret // ""' "$AUTHELIA_CONFIG_BACKUP") local existing_session_secret=$(yq e '.session.secret // ""' "$AUTHELIA_CONFIG_BACKUP") local existing_storage_key=$(yq e '.storage.encryption_key // ""' "$AUTHELIA_CONFIG_BACKUP") local existing_redis_pass=$(yq e '.session.redis.password // ""' "$AUTHELIA_CONFIG_BACKUP") local existing_notifier=$(yq e '.notifier // ""' "$AUTHELIA_CONFIG_BACKUP") # Update secrets in temp file if they existed in the backup if [[ -n "$existing_jwt_secret" && "$existing_jwt_secret" != '""' && "$existing_jwt_secret" != "null" ]]; then yq e -i '.identity_validation.reset_password.jwt_secret = strenv(existing_jwt_secret)' --env existing_jwt_secret="$existing_jwt_secret" "$TEMP_CONFIG" fi if [[ -n "$existing_session_secret" && "$existing_session_secret" != '""' && "$existing_session_secret" != "null" ]]; then yq e -i '.session.secret = strenv(existing_session_secret)' --env existing_session_secret="$existing_session_secret" "$TEMP_CONFIG" fi if [[ -n "$existing_storage_key" && "$existing_storage_key" != '""' && "$existing_storage_key" != "null" ]]; then yq e -i '.storage.encryption_key = strenv(existing_storage_key)' --env existing_storage_key="$existing_storage_key" "$TEMP_CONFIG" fi if [[ -n "$existing_redis_pass" && "$existing_redis_pass" != '""' && "$existing_redis_pass" != "null" ]]; then yq e -i '.session.redis.password = strenv(existing_redis_pass)' --env existing_redis_pass="$existing_redis_pass" "$TEMP_CONFIG" fi # Preserve entire notifier block if it existed if [[ -n "$existing_notifier" && "$existing_notifier" != "{}" && "$existing_notifier" != "null" ]]; then yq e -i '.notifier = load("'"$AUTHELIA_CONFIG_BACKUP"'").notifier' "$TEMP_CONFIG" fi fi # Apply domain settings from .env using yq echo -e "${BLUE}Applying Tailscale domain settings: ${CYAN}$FULL_HOSTNAME${NC}" yq e -i '.session.cookies[0].domain = strenv(TAILNET_DOMAIN)' --env TAILNET_DOMAIN="$TAILNET_DOMAIN" "$TEMP_CONFIG" yq e -i '.session.cookies[0].authelia_url = "https://" + strenv(FULL_HOSTNAME)' --env FULL_HOSTNAME="$FULL_HOSTNAME" "$TEMP_CONFIG" yq e -i '.session.cookies[0].default_redirection_url = "https://" + strenv(FULL_HOSTNAME) + "/home"' --env FULL_HOSTNAME="$FULL_HOSTNAME" "$TEMP_CONFIG" yq e -i '.access_control.rules[0].domain = strenv(WILDCARD_DOMAIN)' --env WILDCARD_DOMAIN="$WILDCARD_DOMAIN" "$TEMP_CONFIG" yq e -i '.access_control.rules[1].domain = strenv(TAILNET_DOMAIN)' --env TAILNET_DOMAIN="$TAILNET_DOMAIN" "$TEMP_CONFIG" # Replace the original file with the updated temporary file mv "$TEMP_CONFIG" "$AUTHELIA_CONFIG" echo -e "${GREEN}Authelia configuration updated using yq.${NC}" else echo -e "${YELLOW}Warning: 'yq' not found. Falling back to 'sed' for Authelia configuration.${NC}" echo -e "${YELLOW}This method is less robust and might not preserve all existing settings perfectly.${NC}" # Copy example over if updating existing file (backup already created) if [ -f "$AUTHELIA_CONFIG_BACKUP" ]; then cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG" fi # Use sed to replace placeholders - less reliable than yq # Note: This assumes the example file uses specific placeholders like 'your-tailnet.ts.net' echo -e "${BLUE}Applying Tailscale domain settings using sed: ${CYAN}$FULL_HOSTNAME${NC}" sed -i "s/\*.your-tailnet.ts.net/\*.$TAILNET_DOMAIN/g" "$AUTHELIA_CONFIG" sed -i "s/your-tailnet.ts.net/$TAILNET_DOMAIN/g" "$AUTHELIA_CONFIG" # Replace base domain too sed -i "s|tailscale-nas.your-tailnet.ts.net|$FULL_HOSTNAME|g" "$AUTHELIA_CONFIG" # Replace full hostname # Attempt to preserve secrets using sed (very fragile) if [ -f "$AUTHELIA_CONFIG_BACKUP" ]; then echo -e "${YELLOW}Attempting to preserve secrets using sed (may be unreliable)...${NC}" local existing_jwt_secret=$(grep -oP 'jwt_secret:\s*\K\S+' "$AUTHELIA_CONFIG_BACKUP" || echo "") local existing_session_secret=$(grep -oP 'session:\s*\n\s*secret:\s*\K\S+' "$AUTHELIA_CONFIG_BACKUP" || echo "") # More specific grep local existing_storage_key=$(grep -oP 'storage:\s*\n\s*encryption_key:\s*\K\S+' "$AUTHELIA_CONFIG_BACKUP" || echo "") # More specific grep local existing_redis_pass=$(grep -oP 'redis:\s*\n\s*host:.*\n\s*port:.*\n\s*password:\s*\K\S+' "$AUTHELIA_CONFIG_BACKUP" || echo "") # More specific grep if [[ -n "$existing_jwt_secret" ]]; then sed -i "s|jwt_secret:.*|jwt_secret: $existing_jwt_secret|" "$AUTHELIA_CONFIG"; fi if [[ -n "$existing_session_secret" ]]; then sed -i "/session:/,/redis:/ s|secret:.*|secret: $existing_session_secret|" "$AUTHELIA_CONFIG"; fi # Target within session block if [[ -n "$existing_storage_key" ]]; then sed -i "/storage:/,/authentication_backend:/ s|encryption_key:.*|encryption_key: $existing_storage_key|" "$AUTHELIA_CONFIG"; fi # Target within storage block if [[ -n "$existing_redis_pass" ]]; then sed -i "/redis:/,/database_index:/ s|password:.*|password: $existing_redis_pass|" "$AUTHELIA_CONFIG"; fi # Target within redis block fi echo -e "${GREEN}Authelia configuration updated using sed.${NC}" fi echo -e "${GREEN}${BOLD}Authelia Configuration Update Complete!${NC}" if [ -f "$AUTHELIA_CONFIG_BACKUP" ]; then echo -e "${BLUE}${BOLD}Note:${NC} Original config backed up to: $AUTHELIA_CONFIG_BACKUP" fi } ################################################## # PART 3: Update Service Configurations ################################################## update_arr_config() { local container=$1 local path=$2 echo -e "${BLUE}Updating ${container} configuration...${NC}" until [ -f "${CONFIG_ROOT:-.}"/"$container"/config.xml ]; do sleep 1; done sed -i.bak "s/<\/UrlBase>/\/$path<\/UrlBase>/" "${CONFIG_ROOT:-.}"/"$container"/config.xml && rm "${CONFIG_ROOT:-.}"/"$container"/config.xml.bak CONTAINER_NAME_UPPER=$(echo "$container" | tr '[:lower:]' '[:upper:]') sed -i.bak 's/^'"${CONTAINER_NAME_UPPER}"'_API_KEY=.*/'"${CONTAINER_NAME_UPPER}"'_API_KEY='"$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/"$container"/config.xml)"'/' .env && rm .env.bak echo -e "${GREEN}Update of ${container} configuration complete, restarting...${NC}" docker compose restart "$container" } update_qbittorrent_config() { local container=$1 echo -e "${BLUE}Updating ${container} configuration...${NC}" docker compose stop "$container" until [ -f "${CONFIG_ROOT:-.}"/"$container"/qBittorrent/qBittorrent.conf ]; do sleep 1; done sed -i.bak '/WebUI\\ServerDomains=*/a WebUI\\Password_PBKDF2="@ByteArray(ARQ77eY1NUZaQsuDHbIMCA==:0WMRkYTUWVT9wVvdDtHAjU9b3b7uB8NR1Gur2hmQCvCDpm39Q+PsJRJPaCU51dEiz+dTzh8qbPsL8WkFljQYFQ==)"' "${CONFIG_ROOT:-.}"/"$container"/qBittorrent/qBittorrent.conf && rm "${CONFIG_ROOT:-.}"/"$container"/qBittorrent/qBittorrent.conf.bak echo -e "${GREEN}Update of ${container} configuration complete, restarting...${NC}" docker compose start "$container" } update_bazarr_config() { local container=$1 echo -e "${BLUE}Updating ${container} configuration...${NC}" until [ -f "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml ]; do sleep 1; done sed -i.bak "s/base_url: ''/base_url: '\/$container'/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak sed -i.bak "s/use_radarr: false/use_radarr: true/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak sed -i.bak "s/use_sonarr: false/use_sonarr: true/" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak until [ -f "${CONFIG_ROOT:-.}"/sonarr/config.xml ]; do sleep 1; done SONARR_API_KEY=$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/sonarr/config.xml) sed -i.bak "/sonarr:/,/^radarr:/ { s/apikey: .*/apikey: $SONARR_API_KEY/; s/base_url: .*/base_url: \/sonarr/; s/ip: .*/ip: sonarr/ }" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak until [ -f "${CONFIG_ROOT:-.}"/radarr/config.xml ]; do sleep 1; done RADARR_API_KEY=$(sed -n 's/.*\(.*\)<\/ApiKey>.*/\1/p' "${CONFIG_ROOT:-.}"/radarr/config.xml) sed -i.bak "/radarr:/,/^sonarr:/ { s/apikey: .*/apikey: $RADARR_API_KEY/; s/base_url: .*/base_url: \/radarr/; s/ip: .*/ip: radarr/ }" "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml && rm "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml.bak sed -i.bak 's/^BAZARR_API_KEY=.*/BAZARR_API_KEY='"$(sed -n 's/.*apikey: \(.*\)*/\1/p' "${CONFIG_ROOT:-.}"/"$container"/config/config/config.yaml | head -n 1)"'/' .env && rm .env.bak echo -e "${GREEN}Update of ${container} configuration complete, restarting...${NC}" docker compose restart "$container" } 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 echo -e "${BLUE}Checking for running containers to update...${NC}" for container in $(docker ps --format '{{.Names}}'); do if [[ "$container" =~ ^(radarr|sonarr|lidarr|prowlarr)$ ]]; then update_arr_config "$container" "$container" elif [[ "$container" =~ ^(bazarr)$ ]]; then update_bazarr_config "$container" elif [[ "$container" =~ ^(qbittorrent)$ ]]; then update_qbittorrent_config "$container" fi done echo -e "\n${GREEN}${BOLD}Service Configuration Update Complete!${NC}" } ################################################## # Additional utility functions ################################################## generate_passphrase() { local words=( "apple" "banana" "cherry" "dragon" "eagle" "forest" "guitar" "harbor" "island" "jungle" "kitchen" "lemon" "mountain" "notebook" "ocean" "planet" "quiet" "river" "summer" "tiger" "umbrella" "village" "winter" "xylophone" "yellow" "zebra" "anchor" "beaver" "candle" "dolphin" "elephant" "falcon" "giraffe" "hamster" "iguana" "jaguar" ) local separators=( "-" "_" "." "+" "=" "*" "~" "^" "@" "#" "%" "&" "!" "?" ) local random_num=$((RANDOM % 900 + 100)) local selected_words=() for i in {1..3}; do local index=$((RANDOM % ${#words[@]})) selected_words+=(${words[$index]}) done local separator1=${separators[$((RANDOM % ${#separators[@]}))]} local separator2=${separators[$((RANDOM % ${#separators[@]}))]} echo "${selected_words[0]}${separator1}${selected_words[1]}${separator2}${selected_words[2]}${random_num}" } ################################################## # PART 4: Authentication Management ################################################## # Get the current auth status for a service get_auth_status() { local service=$1 # Use yq if available for more reliable YAML parsing if command -v yq &> /dev/null; then local middlewares=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service.middlewares=\"))" "$COMPOSE_FILE" 2>/dev/null) if [ -n "$middlewares" ]; then if echo "$middlewares" | grep -q "authelia-auth"; then echo "enabled" else echo "disabled" fi else echo "unknown" fi else # Fall back to grep if yq isn't available if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth" "$COMPOSE_FILE"; then echo "enabled" elif grep -q "traefik.http.routers.$service.middlewares=" "$COMPOSE_FILE"; then echo "disabled" else echo "unknown" fi fi } enable_auth() { local service=$1 echo -e "${BLUE}Enabling authentication for $service...${NC}" # Check if service exists in the compose file # Use yq if available for more reliable parsing if command -v yq &> /dev/null; then # Check if service exists local service_exists=$(yq e ".services.$service" "$COMPOSE_FILE" 2>/dev/null) if [ -z "$service_exists" ] || [ "$service_exists" == "null" ]; then echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" return 1 fi # Check if auth is already enabled local middlewares=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service.middlewares=\"))" "$COMPOSE_FILE" 2>/dev/null) if [ -n "$middlewares" ] && echo "$middlewares" | grep -q "authelia-auth"; then echo -e "${GREEN}Authentication already enabled for $service${NC}" return 0 fi # Create a temporary file for editing with yq local temp_file="${COMPOSE_FILE}.tmp" # Check if there's already a middlewares label if [ -n "$middlewares" ]; then # Modify the existing middlewares label to include authelia-auth if echo "$middlewares" | grep -q "middlewares=$"; then # Empty middlewares yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=authelia-auth@docker\" // .)" "$COMPOSE_FILE" > "$temp_file" elif echo "$middlewares" | grep -q "middlewares=.*@docker"; then # Has other middlewares with @docker local current_value=$(echo "$middlewares" | sed 's/.*middlewares=\(.*\)@docker.*/\1/') yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${current_value},authelia-auth@docker\" // .)" "$COMPOSE_FILE" > "$temp_file" else # Has middlewares without @docker local current_value=$(echo "$middlewares" | sed 's/.*middlewares=\(.*\).*/\1/') yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${current_value},authelia-auth@docker\" // .)" "$COMPOSE_FILE" > "$temp_file" fi else # Need to add a new middlewares label # Check if the service has any labels local has_labels=$(yq e ".services.$service.labels" "$COMPOSE_FILE" 2>/dev/null) if [ -z "$has_labels" ] || [ "$has_labels" == "null" ]; then # Add a new labels array with the middlewares yq e ".services.$service.labels = [\"traefik.http.routers.$service.middlewares=authelia-auth@docker\"]" "$COMPOSE_FILE" > "$temp_file" else # Add the middlewares to the existing labels yq e ".services.$service.labels += [\"traefik.http.routers.$service.middlewares=authelia-auth@docker\"]" "$COMPOSE_FILE" > "$temp_file" fi fi # Replace the original file if the temp file was created successfully if [ $? -eq 0 ] && [ -f "$temp_file" ]; then mv "$temp_file" "$COMPOSE_FILE" echo -e "${GREEN}Authentication enabled for $service${NC}" return 0 else echo -e "${RED}Failed to update the compose file with yq. Falling back to sed.${NC}" rm -f "$temp_file" # Clean up if the temp file exists fi fi # Fall back to the existing implementation if yq is not available or failed if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" return 1 fi local service_start=$(grep -n "container_name: $service" "$COMPOSE_FILE" | cut -d':' -f1) if [ -z "$service_start" ]; then echo -e "${RED}Could not find service $service in $COMPOSE_FILE${NC}" return 1 fi local next_service=$(tail -n +$((service_start+1)) "$COMPOSE_FILE" | grep -n "container_name:" | head -1 | cut -d':' -f1) if [ -n "$next_service" ]; then next_service=$((service_start + next_service)) else next_service=$(wc -l < "$COMPOSE_FILE") fi local service_section=$(sed -n "${service_start},${next_service}p" "$COMPOSE_FILE") if echo "$service_section" | grep -q "traefik.http.routers.$service.middlewares="; then local middlewares_line=$(echo "$service_section" | grep "traefik.http.routers.$service.middlewares=") if echo "$middlewares_line" | grep -q "authelia-auth@docker"; then echo -e "${GREEN}Authentication already enabled for $service${NC}" return 0 fi if echo "$middlewares_line" | grep -q "middlewares=$"; then sed -i "s|traefik.http.routers.$service.middlewares=|traefik.http.routers.$service.middlewares=authelia-auth@docker|" "$COMPOSE_FILE" elif echo "$middlewares_line" | grep -q "middlewares=.*@docker"; then sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)@docker|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE" else sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE" fi else local labels_line=$(echo "$service_section" | grep -n "labels:" | cut -d':' -f1) if [ -z "$labels_line" ]; then echo -e "${RED}Could not find labels section for service $service${NC}" return 1 fi local rule_line=$(echo "$service_section" | grep -n "traefik.http.routers.$service.rule=" | cut -d':' -f1) if [ -n "$rule_line" ]; then rule_line=$((service_start + rule_line)) sed -i "${rule_line}a \ \ \ \ \ \ - traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE" else echo -e "${RED}Could not find rule line for service $service${NC}" return 1 fi fi echo -e "${GREEN}Authentication enabled for $service${NC}" return 0 } disable_auth() { local service=$1 echo -e "${BLUE}Disabling authentication for $service...${NC}" # Use yq if available for more reliable parsing if command -v yq &> /dev/null; then # Check if service exists local service_exists=$(yq e ".services.$service" "$COMPOSE_FILE" 2>/dev/null) if [ -z "$service_exists" ] || [ "$service_exists" == "null" ]; then echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" return 1 fi # Check if auth is already disabled local middlewares=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service.middlewares=\"))" "$COMPOSE_FILE" 2>/dev/null) if [ -n "$middlewares" ] && ! echo "$middlewares" | grep -q "authelia-auth"; then echo -e "${GREEN}Authentication already disabled for $service${NC}" return 0 fi # Create a temporary file for editing with yq local temp_file="${COMPOSE_FILE}.tmp" # Extract current middlewares if [ -n "$middlewares" ]; then if echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker$"; then # Only authelia-auth@docker, replace with empty yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=\" // .)" "$COMPOSE_FILE" > "$temp_file" elif echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker,"; then # authelia-auth@docker at start with comma local remaining=$(echo "$middlewares" | sed 's/.*middlewares=authelia-auth@docker,\(.*\)/\1/') yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${remaining}\" // .)" "$COMPOSE_FILE" > "$temp_file" elif echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker$"; then # authelia-auth@docker at end with comma local remaining=$(echo "$middlewares" | sed 's/\(.*\),authelia-auth@docker$/\1/') yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${remaining}\" // .)" "$COMPOSE_FILE" > "$temp_file" elif echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker,.*"; then # authelia-auth@docker in the middle with commas local remaining=$(echo "$middlewares" | sed 's/\(.*\),authelia-auth@docker,\(.*\)/\1,\2/') yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${remaining}\" // .)" "$COMPOSE_FILE" > "$temp_file" else echo -e "${RED}Could not determine how to remove authelia-auth from middlewares for $service${NC}" return 1 fi # Replace the original file if the temp file was created successfully if [ $? -eq 0 ] && [ -f "$temp_file" ]; then mv "$temp_file" "$COMPOSE_FILE" echo -e "${GREEN}Authentication disabled for $service${NC}" return 0 else echo -e "${RED}Failed to update the compose file with yq. Falling back to sed.${NC}" rm -f "$temp_file" # Clean up if the temp file exists fi else echo -e "${GREEN}No middlewares found for $service, authentication is already disabled${NC}" return 0 fi fi # Fall back to the existing implementation if yq is not available or failed if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" return 1 fi if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth@docker" "$COMPOSE_FILE"; then if grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE"; then sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE" elif grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker," "$COMPOSE_FILE"; then sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker,|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE" elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker" "$COMPOSE_FILE"; then sed -i "s|,authelia-auth@docker||" "$COMPOSE_FILE" elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker,.*" "$COMPOSE_FILE"; then sed -i "s|,authelia-auth@docker,|,|" "$COMPOSE_FILE" else echo -e "${RED}Could not determine how to remove authelia-auth from middlewares for $service${NC}" return 1 fi echo -e "${GREEN}Authentication disabled for $service${NC}" else echo -e "${GREEN}Authentication already disabled for $service${NC}" fi return 0 } list_services() { print_header "Services Authentication Status" # Check if file exists if ! check_file "$COMPOSE_FILE"; then echo -e "${RED}Error: $COMPOSE_FILE doesn't exist${NC}" return 1 fi echo -e "${BLUE}Checking services in $COMPOSE_FILE...${NC}" echo -e "${CYAN}SERVICE\t\tAUTH STATUS${NC}" echo -e "${CYAN}-------\t\t-----------${NC}" local service_count=0 # Get all router names from the labels # This pattern is specific to how your docker-compose.yml format works local router_lines=$(grep -n "traefik.http.routers" "$COMPOSE_FILE") # Process each router line to get service names while IFS= read -r line; do local line_num=$(echo "$line" | cut -d: -f1) local router_config=$(echo "$line" | cut -d: -f2-) # Extract service name from router definition if [[ "$router_config" =~ traefik\.http\.routers\.([^.]+) ]]; then local service="${BASH_REMATCH[1]}" # Skip infrastructure containers if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then continue fi # Skip duplicate entries - only handle each service once if [[ "$processed_services" == *"$service"* ]]; then continue fi processed_services="$processed_services $service" # Find if this router has a middlewares configuration, with or without authelia local status="unknown" # Look for middlewares for this service if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth" "$COMPOSE_FILE"; then status="enabled" elif grep -q "traefik.http.routers.$service.middlewares=" "$COMPOSE_FILE"; then # Has middlewares but no authelia-auth if ! grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth" "$COMPOSE_FILE"; then status="disabled" fi fi printf "${BOLD}%-20s${NC}" "$service" case "$status" in "enabled") echo -e "${GREEN}Enabled${NC}" service_count=$((service_count + 1)) ;; "disabled") echo -e "${YELLOW}Disabled${NC}" service_count=$((service_count + 1)) ;; *) echo -e "${RED}Unknown${NC}" ;; esac fi done <<< "$router_lines" if [ $service_count -eq 0 ]; then echo -e "${YELLOW}No services found with authentication status.${NC}" echo -e "${YELLOW}This could indicate an issue with detecting middlewares in your docker-compose.yml.${NC}" fi } cleanup_backups() { print_header "Backup Files Cleanup" echo -e "${BLUE}Searching for backup files...${NC}" local env_backups=$(find . -maxdepth 1 -name ".env.*.bak" | sort) local compose_backups=$(find . -maxdepth 1 -name "docker-compose.*.bak" | sort) local authelia_backups=$(find ./authelia -maxdepth 1 -name "configuration.*.bak" 2>/dev/null | sort) local env_count=$(echo "$env_backups" | grep -c "^") local compose_count=$(echo "$compose_backups" | grep -c "^") local authelia_count=$(echo "$authelia_backups" | grep -c "^") echo -e "${CYAN}Found:${NC}" echo -e " - ${CYAN}$env_count .env backup files${NC}" echo -e " - ${CYAN}$compose_count docker-compose.yml backup files${NC}" echo -e " - ${CYAN}$authelia_count Authelia configuration backup files${NC}" local total_count=$((env_count + compose_count + authelia_count)) if [ $total_count -eq 0 ]; then echo -e "${GREEN}No backup files found.${NC}" return 0 fi echo -e "${YELLOW}Do you want to delete all backup files? [y/N]:${NC}" read -r answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then echo -e "${YELLOW}Would you like to keep the most recent backup of each type? [Y/n]:${NC}" read -r keep_recent if [[ "$keep_recent" =~ ^[Nn]$ ]]; then echo -e "${RED}Backup cleanup cancelled.${NC}" return 0 fi if [ $env_count -gt 1 ]; then local keep_env=$(echo "$env_backups" | tail -1) local del_env=$(echo "$env_backups" | grep -v "$keep_env") echo -e "${BLUE}Keeping most recent .env backup: ${CYAN}$keep_env${NC}" echo -e "${BLUE}Deleting ${CYAN}$((env_count - 1))${BLUE} older .env backups${NC}" for file in $del_env; do rm "$file" done fi if [ $compose_count -gt 1 ]; then local keep_compose=$(echo "$compose_backups" | tail -1) local del_compose=$(echo "$compose_backups" | grep -v "$keep_compose") echo -e "${BLUE}Keeping most recent docker-compose.yml backup: ${CYAN}$keep_compose${NC}" echo -e "${BLUE}Deleting ${CYAN}$((compose_count - 1))${BLUE} older docker-compose.yml backups${NC}" for file in $del_compose; do rm "$file" done fi if [ $authelia_count -gt 1 ]; then local keep_authelia=$(echo "$authelia_backups" | tail -1) local del_authelia=$(echo "$authelia_backups" | grep -v "$keep_authelia") echo -e "${BLUE}Keeping most recent Authelia config backup: ${CYAN}$keep_authelia${NC}" echo -e "${BLUE}Deleting ${CYAN}$((authelia_count - 1))${BLUE} older Authelia config backups${NC}" for file in $del_authelia; do rm "$file" done fi echo -e "${GREEN}Cleanup completed with most recent backups retained.${NC}" else for file in $env_backups $compose_backups $authelia_backups; do rm "$file" done echo -e "${GREEN}All $total_count backup files have been deleted.${NC}" fi } manage_auth() { print_header "Authentication Management" echo -e "${BLUE}This tool lets you control which services require authentication${NC}" echo -e "${BLUE}Choose an option:${NC}" echo -e " ${CYAN}1. ${NC}List services and their authentication status" echo -e " ${CYAN}2. ${NC}Enable authentication for a service" echo -e " ${CYAN}3. ${NC}Disable authentication for a service" echo -e " ${CYAN}4. ${NC}Enable authentication for all services" echo -e " ${CYAN}5. ${NC}Disable authentication for all services" echo -e " ${CYAN}6. ${NC}Return to main menu" echo local choice while true; do echo -e "${YELLOW}Enter your choice [1-6]: ${NC}" read -r choice case "$choice" in 1) list_services ;; 2) create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" echo -e "${BLUE}Enter the service name to enable authentication for:${NC}" read -r service if [ -z "$service" ]; then echo -e "${RED}No service name provided.${NC}" continue fi enable_auth "$service" echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down${NC}" echo -e " ${CYAN}docker compose up -d${NC}" ;; 3) create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" echo -e "${BLUE}Enter the service name to disable authentication for:${NC}" read -r service if [ -z "$service" ]; then echo -e "${RED}No service name provided.${NC}" continue fi disable_auth "$service" echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down${NC}" echo -e " ${CYAN}docker compose up -d${NC}" ;; 4) create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" echo -e "${BLUE}Enabling authentication for all services...${NC}" local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') for service in $services; do if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then continue fi enable_auth "$service" done echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down${NC}" echo -e " ${CYAN}docker compose up -d${NC}" ;; 5) create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" echo -e "${BLUE}Disabling authentication for all services...${NC}" local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') for service in $services; do if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then continue fi disable_auth "$service" done echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down${NC}" echo -e " ${CYAN}docker compose up -d${NC}" ;; 6) return 0 ;; *) echo -e "${RED}Invalid choice. Please try again.${NC}" ;; esac echo done } ################################################## # PART 5: Authelia Account Management ################################################## manage_authelia_accounts() { print_header "Authelia Account Management" local users_file="${CONFIG_ROOT:-.}/authelia/users_database.yml" if [ ! -f "$users_file" ]; then echo -e "${RED}Error: users_database.yml not found at $users_file${NC}" echo -e "${YELLOW}Would you like to create a new users database file? [y/N]:${NC}" read -r answer if [[ "$answer" =~ ^[Yy]$ ]]; then cat > "$users_file" </dev/null) if [ -z "$password_hash" ]; then echo -e "${RED}Error: Failed to generate password hash. Is the authelia container available?${NC}" echo -e "${YELLOW}Trying direct docker run method...${NC}" password_hash=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$password" 2>/dev/null) if [ -z "$password_hash" ]; then echo -e "${RED}Error: Both methods failed to generate a password hash. Skipping user.${NC}" continue fi fi password_hash=$(echo "$password_hash" | sed 's/^Digest: //') if [[ ! "$password_hash" =~ ^\$argon2id.*$ ]]; then echo -e "${RED}Error: Generated hash does not have the expected format. Actual value:${NC}" echo -e "${YELLOW}$password_hash${NC}" echo -e "${RED}Skipping user creation.${NC}" continue fi echo -e "${GREEN}Password hash generated successfully.${NC}" if grep -q "^[[:space:]]*${username}:" "$users_file"; then sed -i "/^[[:space:]]*${username}:/,/^[[:space:]]*[a-zA-Z0-9_-]\+:/ s/^/# UPDATED: /" "$users_file" sed -i "0,/^# UPDATED: [[:space:]]*[a-zA-Z0-9_-]\+:/ s/^# UPDATED: //" "$users_file" fi cat >> "$users_file" <${NC} Enable Authelia authentication for a specific service." echo -e " ${CYAN}disable-auth ${NC} Disable Authelia authentication for a specific service." echo -e " ${CYAN}enable-all-auth${NC} Enable Authelia authentication for all applicable services." echo -e " ${CYAN}disable-all-auth${NC} Disable Authelia authentication for all applicable services." echo -e " ${CYAN}cleanup${NC} Interactively clean up old backup files (.bak)." echo -e " ${CYAN}all${NC} Run 'update-env', 'update-authelia', and 'update-services'." echo -e " ${CYAN}help${NC} Show this help message." echo -e "" echo -e "${BLUE}Examples:${NC}" echo -e " $0 update-authelia" echo -e " $0 enable-auth sonarr" echo -e " $0 all" echo -e "" echo -e "${YELLOW}Note:${NC} Some commands require Docker to be running and may restart containers." echo -e "${YELLOW}Authentication changes require a stack restart ('docker compose down && docker compose up -d').${NC}" } # Check if any arguments were provided if [ $# -eq 0 ]; then show_help exit 0 fi # Process command line arguments case "$1" in update-env) update_env_file ;; update-authelia) update_authelia_config ;; update-services) update_service_configs ;; manage-accounts) manage_authelia_accounts # This function remains interactive ;; list-auth) list_services ;; enable-auth) if [ -z "$2" ]; then echo -e "${RED}Error: No service specified.${NC}" >&2 echo -e "Usage: $0 enable-auth " >&2 exit 1 fi if ! check_file "$COMPOSE_FILE"; then exit 1; fi create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" enable_auth "$2" echo -e "\n${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down && docker compose up -d${NC}" ;; disable-auth) if [ -z "$2" ]; then echo -e "${RED}Error: No service specified.${NC}" >&2 echo -e "Usage: $0 disable-auth " >&2 exit 1 fi if ! check_file "$COMPOSE_FILE"; then exit 1; fi create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" disable_auth "$2" echo -e "\n${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down && docker compose up -d${NC}" ;; enable-all-auth) if ! check_file "$COMPOSE_FILE"; then exit 1; fi create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" echo -e "${BLUE}Enabling authentication for all applicable services...${NC}" # Use yq if available for more reliable service list, otherwise fallback to grep local services_list if check_yq; then services_list=$(yq e '.services | keys | .[]' "$COMPOSE_FILE") else services_list=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $NF}') # Less reliable fi for service in $services_list; do # Skip infrastructure/excluded containers if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then continue fi enable_auth "$service" done echo -e "\n${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down && docker compose up -d${NC}" ;; disable-all-auth) if ! check_file "$COMPOSE_FILE"; then exit 1; fi create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" echo -e "${BLUE}Disabling authentication for all applicable services...${NC}" local services_list if check_yq; then services_list=$(yq e '.services | keys | .[]' "$COMPOSE_FILE") else services_list=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $NF}') # Less reliable fi for service in $services_list; do # Skip infrastructure/excluded containers if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then continue fi disable_auth "$service" done echo -e "\n${YELLOW}Remember to restart the stack for changes to take effect:${NC}" echo -e " ${CYAN}docker compose down && docker compose up -d${NC}" ;; cleanup) cleanup_backups ;; all) print_header "Running All Updates" update_env_file update_authelia_config update_service_configs echo -e "\n${GREEN}${BOLD}All core updates completed!${NC}" echo -e "${BLUE}Review output for any required actions (e.g., setting new .env variables).${NC}" echo -e "${BLUE}Consider running 'manage-accounts' if needed.${NC}" echo -e "${YELLOW}Remember to restart relevant services or the full stack if necessary.${NC}" ;; help|-h|--help) show_help ;; *) echo -e "${RED}Unknown command: $1${NC}" >&2 echo -e "${BLUE}Run '$0 help' for usage information.${NC}" >&2 exit 1 ;; esac exit 0