feat(update-setup): Update to support the new authelia/configuration.yml
Some checks failed
/ validate-docker-compose (push) Has been cancelled

This commit is contained in:
Jose Daniel G. Percy 2025-04-26 15:20:06 +08:00
parent 026d24a3ae
commit ba889f9c38

View File

@ -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
# Get the current policy for a service from Authelia config
# Usage: get_authelia_policy <service_name> <config_file>
# Output: policy (e.g., one_factor, bypass), not_found, or error
get_authelia_policy() {
local service=$1
local config_file=$2
echo -e "${BLUE}Updating ${container} configuration...${NC}"
until [ -f "${CONFIG_ROOT:-.}"/"$container"/config.xml ]; do sleep 1; done
sed -i.bak "s/<UrlBase><\/UrlBase>/<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>\(.*\)<\/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>\(.*\)<\/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>\(.*\)<\/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 <service_name> <policy> <config_file>
set_authelia_policy() {
local service=$1
echo -e "${BLUE}Disabling authentication for $service...${NC}"
local policy=$2
local config_file=$3
local backup_file="${config_file}.${TIMESTAMP}.bak"
# 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}"
if ! check_file "$config_file"; then
echo -e "${RED}Error: Authelia configuration file '$config_file' not found.${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}"
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
return 0
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
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
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
else
echo -e "${RED}Error: Rule for service '$service' not found. Cannot add rule using sed.${NC}"
return 1
fi
fi
}
list_services() {
print_header "Services Authentication Status"
# List services and their Authelia policy status
list_authelia_services() {
print_header "Authelia Service Policy Status"
# Check if file exists
if ! check_file "$COMPOSE_FILE"; then
echo -e "${RED}Error: $COMPOSE_FILE doesn't exist${NC}"
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 services in $COMPOSE_FILE...${NC}"
echo -e "${CYAN}SERVICE\t\tAUTH STATUS${NC}"
echo -e "${CYAN}-------\t\t-----------${NC}"
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
# 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")
# 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 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-)
# 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 "'")
# Extract service name from router definition
if [[ "$router_config" =~ traefik\.http\.routers\.([^.]+) ]]; then
local service="${BASH_REMATCH[1]}"
if [[ "$path_regex" =~ ^\^/([a-zA-Z0-9_-]+)\.\* ]]; 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"
# 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 '.' -)"
printf "${BOLD}%-20s${NC}" "$service"
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"
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"
# 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 found with authentication status.${NC}"
echo -e "${YELLOW}This could indicate an issue with detecting middlewares in your docker-compose.yml.${NC}"
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"
# Interactive menu for managing Authelia policies
manage_authelia_policies() {
print_header "Authelia Policy 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
if ! check_file "$AUTHELIA_CONFIG"; then
echo -e "${RED}Error: Authelia configuration file '$AUTHELIA_CONFIG' not found.${NC}"
return 1
fi
local choice
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 <service>${NC} Enable Authelia authentication for a specific service."
echo -e " ${CYAN}disable-auth <service>${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 <svc> <pol>${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 <service>" >&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 <service_name> <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 <service>" >&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