diff --git a/update-setup.sh b/update-setup.sh index 81bb742..dfcff7d 100755 --- a/update-setup.sh +++ b/update-setup.sh @@ -272,503 +272,250 @@ update_authelia_config() { # 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 +# PART 5: Authelia Account Management +################################################## +# PART 4: Authelia Policy Management ################################################## -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" -} +# Get the current policy for a service from Authelia config +# Usage: get_authelia_policy +# Output: policy (e.g., one_factor, bypass), not_found, or error +get_authelia_policy() { + local service=$1 + local config_file=$2 -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}" + if ! check_file "$config_file"; then + echo "error: config file not found" 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 + if check_yq; then + # Find the rule matching the path_regex for the service + # Note: This assumes path_regex is unique enough for the service (e.g., ^/service.*) + local policy=$(yq e ".access_control.rules[] | select(.path_regex == \"^/${service}.*\") | .policy" "$config_file" 2>/dev/null) + if [ -n "$policy" ] && [ "$policy" != "null" ]; then + echo "$policy" else - echo "unknown" + # Check if a rule exists for the service path at all + local rule_exists=$(yq e ".access_control.rules[] | select(.path_regex == \"^/${service}.*\")" "$config_file" 2>/dev/null) + if [ -n "$rule_exists" ] && [ "$rule_exists" != "null" ]; then + echo "error: policy not found in rule" # Rule exists but policy couldn't be read + else + echo "not_found" # No rule found for this service path + fi 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" + # Fall back to grep if yq isn't available (less reliable) + # This is fragile and depends heavily on formatting + local policy_line=$(grep -A 1 "path_regex: '^/${service}.*'" "$config_file" | grep "policy:") + if [ -n "$policy_line" ]; then + echo "$policy_line" | awk '{print $2}' else - echo "unknown" + echo "not_found" 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() { +# Set the policy for a service in Authelia config +# Usage: set_authelia_policy +set_authelia_policy() { 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 -} + local policy=$2 + local config_file=$3 + local backup_file="${config_file}.${TIMESTAMP}.bak" -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}" + if ! check_file "$config_file"; then + echo -e "${RED}Error: Authelia configuration file '$config_file' not found.${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 + + if [[ "$policy" != "one_factor" && "$policy" != "two_factor" && "$policy" != "deny" && "$policy" != "bypass" ]]; then + echo -e "${RED}Error: Invalid policy '$policy'. Must be one of: one_factor, two_factor, deny, bypass.${NC}" + return 1 + fi + + echo -e "${BLUE}Setting policy for service '$service' to '$policy' in '$config_file'...${NC}" + + # Create backup if it doesn't exist for this run + if [ ! -f "$backup_file" ]; then + create_backup "$config_file" "$backup_file" + fi + + # Use yq if available + if check_yq; then + # Check if the rule exists first using the path_regex + local rule_index=$(yq e ".access_control.rules | map(.path_regex == \"^/${service}.*\") | indexOf(true)" "$config_file" 2>/dev/null) + + if [ "$rule_index" == "-1" ] || [ -z "$rule_index" ]; then + echo -e "${YELLOW}Warning: No rule found for service path '^/${service}.*' in '$config_file'.${NC}" + echo -e "${YELLOW}Attempting to add a new rule...${NC}" + + # Get the tailnet domain from .env for the new rule + local TAILNET_DOMAIN=$(grep -oP "^TAILSCALE_TAILNET_DOMAIN=\K.*" "$ENV_FILE" | tr -d '"' | tr -d "'") + if [ -z "$TAILNET_DOMAIN" ]; then + echo -e "${RED}Error: Could not read TAILSCALE_TAILNET_DOMAIN from $ENV_FILE. Cannot add rule.${NC}" + return 1 fi - - # Skip duplicate entries - only handle each service once - if [[ "$processed_services" == *"$service"* ]]; then - continue + local WILDCARD_DOMAIN="*.${TAILNET_DOMAIN}" + + # Add the new rule to the access_control.rules array + # Places it before the generic domain rule if it exists, otherwise at the end + # This assumes a generic rule like "- domain: '*.domain.tld'" exists near the end + yq e -i ".access_control.rules |= select(.domain != \"${WILDCARD_DOMAIN}\" or .path_regex != null) + [{\"domain\": \"${WILDCARD_DOMAIN}\", \"path_regex\": \"^/${service}.*\", \"policy\": \"${policy}\"}] + select(.domain == \"${WILDCARD_DOMAIN}\" and .path_regex == null)" "$config_file" + + if [ $? -eq 0 ]; then + echo -e "${GREEN}Added new rule for '$service' with policy '$policy'.${NC}" + return 0 + else + echo -e "${RED}Error: Failed to add new rule for '$service' using yq.${NC}" + return 1 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" + else + # Rule exists, update the policy at the found index + yq e -i "(.access_control.rules[$rule_index].policy) = \"$policy\"" "$config_file" + if [ $? -eq 0 ]; then + echo -e "${GREEN}Policy for '$service' updated to '$policy'.${NC}" + return 0 + else + echo -e "${RED}Error: Failed to update policy for '$service' using yq.${NC}" + return 1 + fi + fi + else + # Fallback to sed (much less reliable, especially for adding rules) + echo -e "${YELLOW}Warning: 'yq' not found. Using 'sed' which is less reliable for YAML manipulation.${NC}" + # Check if rule exists (simple grep) + if grep -q "path_regex: '^/${service}.*'" "$config_file"; then + # Attempt to find the line number of path_regex and update the policy line below it + local line_num=$(grep -n "path_regex: '^/${service}.*'" "$config_file" | head -n 1 | cut -d: -f1) # Use head -n 1 just in case + if [ -n "$line_num" ]; then + # Assuming policy is the next line (fragile!) + local policy_line_num=$((line_num + 1)) + # Check if the next line actually contains 'policy:' + if sed -n "${policy_line_num}p" "$config_file" | grep -q "policy:"; then + sed -i "${policy_line_num}s/policy:.*/policy: $policy/" "$config_file" + if [ $? -eq 0 ]; then + echo -e "${GREEN}Policy for '$service' updated to '$policy' (using sed).${NC}" + return 0 + else + echo -e "${RED}Error: Failed to update policy line using sed.${NC}" + return 1 + fi + else + echo -e "${RED}Error: Could not reliably find policy line for '$service' using sed (expected on line $policy_line_num).${NC}" + return 1 fi + else + echo -e "${RED}Error: Could not find line number for service '$service' using sed.${NC}" + return 1 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 + else + echo -e "${RED}Error: Rule for service '$service' not found. Cannot add rule using sed.${NC}" + return 1 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 } +# List services and their Authelia policy status +list_authelia_services() { + print_header "Authelia Service Policy Status" + + if ! check_file "$AUTHELIA_CONFIG"; then + echo -e "${RED}Error: Authelia configuration file '$AUTHELIA_CONFIG' not found.${NC}" + return 1 + fi + + echo -e "${BLUE}Checking service policies in $AUTHELIA_CONFIG...${NC}" + echo -e "${CYAN}SERVICE\t\tPOLICY${NC}" + echo -e "${CYAN}-------\t\t------${NC}" + + local service_count=0 + local processed_services="" # Track processed services + + # Use yq to get all rules with path_regex if available + if check_yq; then + # Extract path_regex and policy for rules that have path_regex + local rules_data=$(yq e '.access_control.rules[] | select(has("path_regex")) | {"path": .path_regex, "policy": .policy}' "$AUTHELIA_CONFIG") + + # Process each rule found + while IFS= read -r line; do + # Extract service name from path_regex (e.g., "^/sonarr.*" -> "sonarr") + local path_regex=$(echo "$line" | grep -oP 'path: \K.*' | tr -d '"' | tr -d "'") + local policy=$(echo "$line" | grep -oP 'policy: \K.*' | tr -d '"' | tr -d "'") + + if [[ "$path_regex" =~ ^\^/([a-zA-Z0-9_-]+)\.\* ]]; then + local service="${BASH_REMATCH[1]}" + + # Skip duplicates if multiple rules somehow match the same pattern start + if [[ "$processed_services" == *"$service"* ]]; then + continue + fi + processed_services="$processed_services $service" + + printf "${BOLD}%-20s${NC}" "$service" + case "$policy" in + "one_factor"|"two_factor") + echo -e "${GREEN}${policy}${NC}" + ;; + "bypass") + echo -e "${YELLOW}${policy}${NC}" + ;; + "deny") + echo -e "${RED}${policy}${NC}" + ;; + *) + echo -e "${MAGENTA}Unknown ($policy)${NC}" + ;; + esac + service_count=$((service_count + 1)) + fi + # Use yq -N to prevent splitting lines with spaces + done <<< "$(echo "$rules_data" | yq -N e '.' -)" + + else + # Fallback to grep (less reliable) + echo -e "${YELLOW}Warning: yq not found, using grep (may miss services or show duplicates).${NC}" + # Find lines with path_regex, then try to get the policy line after + grep -n "path_regex: '^/.*" "$AUTHELIA_CONFIG" | while IFS=: read -r line_num line_content; do + if [[ "$line_content" =~ path_regex:\ \'^\/([a-zA-Z0-9_-]+)\.\*\' ]]; then + local service="${BASH_REMATCH[1]}" + # Skip duplicates + if [[ "$processed_services" == *"$service"* ]]; then + continue + fi + processed_services="$processed_services $service" + + # Try to get policy from the next line (very fragile) + local policy_line_num=$((line_num + 1)) + local policy=$(sed -n "${policy_line_num}p" "$AUTHELIA_CONFIG" | grep "policy:" | awk '{print $2}') + + printf "${BOLD}%-20s${NC}" "$service" + if [ -n "$policy" ]; then + case "$policy" in + "one_factor"|"two_factor") echo -e "${GREEN}${policy}${NC}" ;; + "bypass") echo -e "${YELLOW}${policy}${NC}" ;; + "deny") echo -e "${RED}${policy}${NC}" ;; + *) echo -e "${MAGENTA}Unknown ($policy)${NC}" ;; + esac + else + echo -e "${RED}Unknown (could not read policy)${NC}" + fi + service_count=$((service_count + 1)) + fi + done + fi + + if [ $service_count -eq 0 ]; then + echo -e "${YELLOW}No services with path_regex rules found in $AUTHELIA_CONFIG.${NC}" + echo -e "${YELLOW}Only services explicitly defined with path_regex rules are listed.${NC}" + fi + return 0 +} + + cleanup_backups() { print_header "Backup Files Cleanup" @@ -844,104 +591,89 @@ cleanup_backups() { 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 +# Interactive menu for managing Authelia policies +manage_authelia_policies() { + print_header "Authelia Policy Management" + + if ! check_file "$AUTHELIA_CONFIG"; then + echo -e "${RED}Error: Authelia configuration file '$AUTHELIA_CONFIG' not found.${NC}" + return 1 + fi + while true; do - echo -e "${YELLOW}Enter your choice [1-6]: ${NC}" + echo -e "\n${BLUE}Choose an option:${NC}" + echo -e " ${CYAN}1. ${NC}List services and their current Authelia policy" + echo -e " ${CYAN}2. ${NC}Set Authelia policy for a service" + echo -e " ${CYAN}3. ${NC}Return to main menu" + echo + + local choice + echo -e "${YELLOW}Enter your choice [1-3]: ${NC}" read -r choice - + case "$choice" in 1) - list_services + list_authelia_services ;; 2) - create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" - - echo -e "${BLUE}Enter the service name to enable authentication for:${NC}" + echo -e "${BLUE}Enter the service name to set the policy for (e.g., sonarr, radarr):${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}" + + echo -e "${BLUE}Select the desired policy for '$service':${NC}" + echo -e " ${CYAN}1. ${GREEN}one_factor${NC} (Requires login)" + echo -e " ${CYAN}2. ${GREEN}two_factor${NC} (Requires login + 2FA)" + echo -e " ${CYAN}3. ${YELLOW}bypass${NC} (No login required)" + echo -e " ${CYAN}4. ${RED}deny${NC} (Access denied)" + echo -e " ${CYAN}5. ${NC}Cancel" + + local policy_choice + local policy="" + while true; do + echo -e "${YELLOW}Enter policy choice [1-5]: ${NC}" + read -r policy_choice + case "$policy_choice" in + 1) policy="one_factor"; break ;; + 2) policy="two_factor"; break ;; + 3) policy="bypass"; break ;; + 4) policy="deny"; break ;; + 5) policy=""; break ;; # Cancel + *) echo -e "${RED}Invalid choice.${NC}" ;; + esac + done + + if [ -z "$policy" ]; then + echo -e "${YELLOW}Policy change cancelled.${NC}" + continue + fi + + # Call the function to set the policy + set_authelia_policy "$service" "$policy" "$AUTHELIA_CONFIG" + + # Check the return status of set_authelia_policy + if [ $? -eq 0 ]; then + echo -e "\n${YELLOW}Remember to restart Authelia for the policy change to take effect:${NC}" + echo -e " ${CYAN}docker compose restart authelia${NC}" + else + echo -e "${RED}Failed to set policy. Please check errors above.${NC}" + # Optionally offer to restore backup here + fi ;; 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 + echo # Add a newline for better readability between menu iterations done } + ################################################## # PART 5: Authelia Account Management ################################################## @@ -1119,22 +851,22 @@ show_help() { echo -e " ${CYAN}update-services${NC} Update configurations for running *arr/qBittorrent/Bazarr containers" echo -e " (sets URL base, extracts API keys to .env)." echo -e " ${CYAN}manage-accounts${NC} Interactively add/update Authelia users in users_database.yml." - echo -e " ${CYAN}list-auth${NC} List authentication status for all services in docker-compose.yml." - echo -e " ${CYAN}enable-auth ${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}manage-policies${NC} Interactively manage Authelia access policies for services." + echo -e " ${CYAN}list-policies${NC} List services and their current Authelia policy." + echo -e " ${CYAN}set-policy ${NC} Set Authelia policy for a service (e.g., 'one_factor', 'bypass')." 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 set-policy sonarr one_factor" + echo -e " $0 set-policy radarr bypass" + echo -e " $0 manage-policies" 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}" + echo -e "${YELLOW}Policy changes require an Authelia restart ('docker compose restart authelia').${NC}" } # Check if any arguments were provided @@ -1155,77 +887,28 @@ case "$1" in update_service_configs ;; manage-accounts) - manage_authelia_accounts # This function remains interactive + manage_authelia_accounts # Interactive ;; - list-auth) - list_services + manage-policies) + manage_authelia_policies # Interactive ;; - enable-auth) - if [ -z "$2" ]; then - echo -e "${RED}Error: No service specified.${NC}" >&2 - echo -e "Usage: $0 enable-auth " >&2 + list-policies) + list_authelia_services + ;; + set-policy) + if [ -z "$2" ] || [ -z "$3" ]; then + echo -e "${RED}Error: Service name and policy are required.${NC}" >&2 + echo -e "Usage: $0 set-policy ${NC}" >&2 + echo -e "Valid policies: one_factor, two_factor, bypass, deny" >&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 + if ! check_file "$AUTHELIA_CONFIG"; then exit 1; fi + # Backup is handled within set_authelia_policy if needed + set_authelia_policy "$2" "$3" "$AUTHELIA_CONFIG" + if [ $? -eq 0 ]; then + echo -e "\n${YELLOW}Remember to restart Authelia for the policy change to take effect:${NC}" + echo -e " ${CYAN}docker compose restart authelia${NC}" 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