From 5d7a16264722080854a7f520030e8e055f830fcf Mon Sep 17 00:00:00 2001 From: aki Date: Sat, 26 Apr 2025 02:45:09 +0800 Subject: [PATCH] feat(auth): Add authentication management script and update permissions for setup script --- .env.example | 16 --- docker-compose.yml | 49 ++------ manage-auth.sh | 302 +++++++++++++++++++++++++++++++++++++++++++++ update-setup.sh | 0 4 files changed, 314 insertions(+), 53 deletions(-) create mode 100755 manage-auth.sh mode change 100644 => 100755 update-setup.sh diff --git a/.env.example b/.env.example index 78c0e23..b9d30f2 100644 --- a/.env.example +++ b/.env.example @@ -14,22 +14,6 @@ GROUP_ID=1000 # Your local timezone (e.g., America/New_York, Europe/London, Asia/Manila). See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones TIMEZONE="America/New_York" -# --- Authentication Settings --- -# Control which services require Authelia authentication (true/false) -# Set to 'false' to disable authentication for specific services -AUTH_SONARR=true -AUTH_RADARR=true -AUTH_BAZARR=true -AUTH_PROWLARR=true -AUTH_JELLYSEERR=true -AUTH_QBITTORRENT=true -AUTH_LIDARR=true -AUTH_JELLYFIN=false -AUTH_HOMEPAGE=true -AUTH_FLARESOLVERR=true -AUTH_SABNZBD=true -AUTH_CALIBRE=true - # --- Host Paths --- # Base directory on host for storing service configuration files. '.' stores them in subdirectories within the project folder. CONFIG_ROOT="." diff --git a/docker-compose.yml b/docker-compose.yml index c030ac1..0614364 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,4 @@ services: - middlewares: - # This is a "no-op" service just to hold middleware definitions - image: traefik/whoami:latest - container_name: middlewares - restart: "no" - labels: - # Authentication middleware - used when AUTH_SERVICE=true - - traefik.http.middlewares.auth-required.forwardAuth.address=http://authelia:9091/api/verify?rd=https://${APP_HOSTNAME}/ - - traefik.http.middlewares.auth-required.forwardAuth.trustForwardHeader=true - - traefik.http.middlewares.auth-required.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email - - # No authentication middleware - used when AUTH_SERVICE=false - - traefik.http.middlewares.auth-bypass.headers.customResponseHeaders.X-Auth-Skip=true - - # Map true/false to the actual middleware - - traefik.http.middlewares.true.chain.middlewares=auth-required - - traefik.http.middlewares.false.chain.middlewares=auth-bypass - profiles: - - disabled # This service never actually starts - traefik: image: ghcr.io/traefik/traefik:3.3 container_name: traefik @@ -103,13 +83,8 @@ services: - traefik.enable=true - traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`) - traefik.http.routers.sonarr.entrypoints=web - - traefik.http.routers.sonarr.middlewares=${AUTH_SONARR:-true}@docker + - traefik.http.routers.sonarr.middlewares=authelia-auth@docker - traefik.http.services.sonarr.loadbalancer.server.port=8989 - # Define middleware chains for auth control - these are global definitions - - traefik.http.middlewares.true.forwardAuth.address=http://authelia:9091/api/verify?rd=https://${APP_HOSTNAME}/ - - traefik.http.middlewares.true.forwardAuth.trustForwardHeader=true - - traefik.http.middlewares.true.forwardAuth.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email - - traefik.http.middlewares.false.headers.customResponseHeaders.X-Auth-Skip=true - homepage.group=Media - homepage.name=Sonarr - homepage.icon=sonarr.png @@ -138,7 +113,7 @@ services: - traefik.enable=true - traefik.http.routers.radarr.rule=PathPrefix(`/radarr`) - traefik.http.routers.radarr.entrypoints=web - - traefik.http.routers.radarr.middlewares=${AUTH_RADARR:-true}@docker + - traefik.http.routers.radarr.middlewares=authelia-auth@docker - traefik.http.services.radarr.loadbalancer.server.port=7878 - homepage.group=Media - homepage.name=Radarr @@ -168,7 +143,7 @@ services: - traefik.enable=true - traefik.http.routers.lidarr.rule=PathPrefix(`/lidarr`) - traefik.http.routers.lidarr.entrypoints=web - - traefik.http.routers.lidarr.middlewares=${AUTH_LIDARR:-true}@docker + - traefik.http.routers.lidarr.middlewares=authelia-auth@docker - traefik.http.services.lidarr.loadbalancer.server.port=8686 - homepage.group=Media - homepage.name=Lidarr @@ -200,7 +175,7 @@ services: - traefik.enable=true - traefik.http.routers.bazarr.rule=PathPrefix(`/bazarr`) - traefik.http.routers.bazarr.entrypoints=web - - traefik.http.routers.bazarr.middlewares=${AUTH_BAZARR:-true}@docker + - traefik.http.routers.bazarr.middlewares=authelia-auth@docker - traefik.http.services.bazarr.loadbalancer.server.port=6767 - homepage.group=Download - homepage.name=Bazarr @@ -236,7 +211,7 @@ services: - traefik.http.routers.jellyseerr.rule=PathPrefix(`/jellyseerr`) - traefik.http.routers.jellyseerr.entrypoints=web - traefik.http.services.jellyseerr.loadbalancer.server.port=5055 - - traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders,${AUTH_JELLYSEERR:-true}@docker + - traefik.http.routers.jellyseerr.middlewares=jellyseerr-stripprefix,jellyseerr-rewrite,jellyseerr-rewriteHeaders,authelia-auth@docker - traefik.http.middlewares.jellyseerr-stripprefix.stripPrefix.prefixes=/jellyseerr - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].header=location - traefik.http.middlewares.jellyseerr-rewriteHeaders.plugin.rewriteHeaders.rewrites[0].regex=^/(.+)$ @@ -309,7 +284,7 @@ services: - traefik.enable=true - traefik.http.routers.prowlarr.rule=PathPrefix(`/prowlarr`) - traefik.http.routers.prowlarr.entrypoints=web - - traefik.http.routers.prowlarr.middlewares=${AUTH_PROWLARR:-true}@docker + - traefik.http.routers.prowlarr.middlewares=authelia-auth@docker - traefik.http.services.prowlarr.loadbalancer.server.port=9696 - homepage.group=Download - homepage.name=Prowlarr @@ -333,7 +308,7 @@ services: - traefik.enable=true - traefik.http.routers.flaresolverr.rule=PathPrefix(`/flaresolverr`) - traefik.http.routers.flaresolverr.entrypoints=web - - traefik.http.routers.flaresolverr.middlewares=${AUTH_FLARESOLVERR:-true}@docker + - traefik.http.routers.flaresolverr.middlewares=authelia-auth@docker - traefik.http.services.flaresolverr.loadbalancer.server.port=8191 profiles: - flaresolverr @@ -360,7 +335,7 @@ services: - traefik.http.routers.qbittorrent.rule=PathPrefix(`/qbittorrent`) - traefik.http.routers.qbittorrent.entrypoints=web - traefik.http.services.qbittorrent.loadbalancer.server.port=8080 - - traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix,${AUTH_QBITTORRENT:-true}@docker + - traefik.http.routers.qbittorrent.middlewares=qbittorrent-strip-slash,qbittorrent-stripprefix,authelia-auth@docker - traefik.http.middlewares.qbittorrent-stripprefix.stripPrefix.prefixes=/qbittorrent - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.regex=(^.*\/qbittorrent$$) - traefik.http.middlewares.qbittorrent-strip-slash.redirectregex.replacement=$$1/ @@ -405,7 +380,7 @@ services: - traefik.enable=true - traefik.http.routers.sabnzbd.rule=PathPrefix(`/sabnzbd`) - traefik.http.routers.sabnzbd.entrypoints=web - - traefik.http.routers.sabnzbd.middlewares=${AUTH_SABNZBD:-true}@docker + - traefik.http.routers.sabnzbd.middlewares=authelia-auth@docker - traefik.http.services.sabnzbd.loadbalancer.server.port=8080 - homepage.group=Download - homepage.name=Sabnzbd @@ -441,7 +416,7 @@ services: - traefik.enable=true - traefik.http.routers.jellyfin.rule=PathPrefix(`/jellyfin`) - traefik.http.routers.jellyfin.entrypoints=web - - traefik.http.routers.jellyfin.middlewares=${AUTH_JELLYFIN:-false}@docker + - traefik.http.routers.jellyfin.middlewares=authelia-auth@docker - traefik.http.services.jellyfin.loadbalancer.server.port=8096 - homepage.group=Media - homepage.name=Jellyfin @@ -470,7 +445,7 @@ services: - traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Scheme=https - traefik.http.middlewares.calibre-headers.headers.customRequestHeaders.X-Script-Name=/calibre - traefik.http.middlewares.calibre-stripprefixregex.stripPrefixRegex.regex=/calibre - - traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex,${AUTH_CALIBRE:-true}@docker + - traefik.http.routers.calibre.middlewares=calibre-headers,calibre-stripprefixregex,authelia-auth@docker - traefik.http.routers.calibre.rule=PathPrefix(`/calibre`) - traefik.http.routers.calibre.entrypoints=web - traefik.http.services.calibre.loadbalancer.server.port=8083 @@ -548,7 +523,7 @@ services: - traefik.http.routers.homepage.entrypoints=web - traefik.http.routers.homepage.priority=10 - traefik.http.middlewares.homepage-stripprefix.stripPrefix.prefixes=/home - - traefik.http.routers.homepage.middlewares=homepage-stripprefix,${AUTH_HOMEPAGE:-true}@docker + - traefik.http.routers.homepage.middlewares=homepage-stripprefix,authelia-auth@docker - homepage.group=Dashboard - homepage.name=Homepage - homepage.icon=homepage.png diff --git a/manage-auth.sh b/manage-auth.sh new file mode 100755 index 0000000..8b5f1af --- /dev/null +++ b/manage-auth.sh @@ -0,0 +1,302 @@ +#!/bin/bash + +# Authentication Management Script for docker-compose-nas +# Enables or disables Authelia authentication for specific services +# Created: April 26, 2025 + +set -e + +# Color definitions +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# Files +COMPOSE_FILE="docker-compose.yml" +TIMESTAMP=$(date +"%Y%m%d-%H%M%S") +BACKUP_FILE="docker-compose.${TIMESTAMP}.bak" + +# Print section header +print_header() { + echo -e "\n${CYAN}${BOLD}$1${NC}" + echo -e "${CYAN}$(printf '=%.0s' $(seq 1 ${#1}))${NC}" +} + +# Create a backup of the docker-compose.yml file +create_backup() { + echo -e "${BLUE}Creating backup of $COMPOSE_FILE as $BACKUP_FILE...${NC}" + cp "$COMPOSE_FILE" "$BACKUP_FILE" +} + +# Get the current auth status for a service +get_auth_status() { + local service=$1 + # Check if the service has authelia-auth middleware + 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 + # Service has middlewares, but no authelia-auth + echo "disabled" + else + echo "unknown" + fi +} + +# Enable authentication for a service +enable_auth() { + local service=$1 + echo -e "${BLUE}Enabling authentication for $service...${NC}" + + # Check if the service exists in the compose file + if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then + echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" + return 1 + fi + + # Update the middlewares configuration + # This is a bit complex because different services have different middleware configurations + # We need to find the existing middlewares line and add authelia-auth if it doesn't exist + + # First, find the service section + 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 + + # Find the next service or EOF + 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 + + # Extract the service section + local service_section=$(sed -n "${service_start},${next_service}p" "$COMPOSE_FILE") + + # Find if there's a middlewares line + if echo "$service_section" | grep -q "traefik.http.routers.$service.middlewares="; then + # Get the middlewares line + local middlewares_line=$(echo "$service_section" | grep "traefik.http.routers.$service.middlewares=") + + # Check if authelia-auth is already in the middlewares + if echo "$middlewares_line" | grep -q "authelia-auth@docker"; then + echo -e "${GREEN}Authentication already enabled for $service${NC}" + return 0 + fi + + # Add authelia-auth to the middlewares + # Different patterns depending on what's in the middlewares line + if echo "$middlewares_line" | grep -q "middlewares=$"; then + # Empty middlewares + 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 + # Already has some middleware with @docker + sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)@docker|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE" + else + # Has middlewares but no @docker suffix + sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE" + fi + else + # No middlewares line, add one + 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 + + # Insert the middlewares line after the rule line + 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 authentication for a service +disable_auth() { + local service=$1 + echo -e "${BLUE}Disabling authentication for $service...${NC}" + + # Check if the service exists in the compose file + if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then + echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" + return 1 + fi + + # Update the middlewares configuration by removing authelia-auth + + # First, find if there's a middlewares line with authelia-auth + if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth@docker" "$COMPOSE_FILE"; then + # Simple case: only authelia-auth + if grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE"; then + # Remove authelia-auth@docker, leaving only the label prefix + sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE" + # authelia-auth is at the beginning with a comma + elif grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker," "$COMPOSE_FILE"; then + # Remove authelia-auth@docker, + sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker,|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE" + # authelia-auth is at the end with a comma before it + elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker" "$COMPOSE_FILE"; then + # Remove ,authelia-auth@docker + sed -i "s|,authelia-auth@docker||" "$COMPOSE_FILE" + # authelia-auth is in the middle + elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker,.*" "$COMPOSE_FILE"; then + # Remove ,authelia-auth@docker + 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 and their auth status +list_services() { + print_header "Services Authentication Status" + + echo -e "${BLUE}Checking services in $COMPOSE_FILE...${NC}" + echo -e "${CYAN}SERVICE\t\tAUTH STATUS${NC}" + echo -e "${CYAN}-------\t\t-----------${NC}" + + # Find all container names + local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') + + for service in $services; do + # Skip infrastructure containers + if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then + continue + fi + + local status=$(get_auth_status "$service") + + # Pad service name for better formatting + printf "${BOLD}%-20s${NC}" "$service" + + case "$status" in + "enabled") + echo -e "${GREEN}Enabled${NC}" + ;; + "disabled") + echo -e "${YELLOW}Disabled${NC}" + ;; + *) + echo -e "${RED}Unknown${NC}" + ;; + esac + done +} + +# Display help +show_help() { + print_header "Authentication Management Script" + echo -e "Usage: $0 [options]" + echo -e "" + echo -e "Options:" + echo -e " ${CYAN}list${NC} List all services and their auth status" + echo -e " ${CYAN}enable ${NC} Enable authentication for a service" + echo -e " ${CYAN}disable ${NC} Disable authentication for a service" + echo -e " ${CYAN}enable-all${NC} Enable authentication for all services" + echo -e " ${CYAN}disable-all${NC} Disable authentication for all services" + echo -e " ${CYAN}help${NC} Show this help message" + echo -e "" + echo -e "Examples:" + echo -e " $0 list" + echo -e " $0 enable jellyfin" + echo -e " $0 disable homepage" + echo -e " $0 enable-all" + echo -e "" +} + +# Main function +main() { + if [ $# -eq 0 ]; then + show_help + exit 0 + fi + + # Create backup of compose file + create_backup + + case "$1" in + "list") + list_services + ;; + "enable") + if [ -z "$2" ]; then + echo -e "${RED}Error: No service specified${NC}" + echo -e "Usage: $0 enable " + exit 1 + fi + enable_auth "$2" + ;; + "disable") + if [ -z "$2" ]; then + echo -e "${RED}Error: No service specified${NC}" + echo -e "Usage: $0 disable " + exit 1 + fi + disable_auth "$2" + ;; + "enable-all") + print_header "Enabling Authentication for All Services" + + local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') + for service in $services; do + # Skip infrastructure containers + if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then + continue + fi + + enable_auth "$service" + done + ;; + "disable-all") + print_header "Disabling Authentication for All Services" + + local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') + for service in $services; do + # Skip infrastructure containers + if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then + continue + fi + + disable_auth "$service" + done + ;; + "help") + show_help + ;; + *) + echo -e "${RED}Error: Unknown command: $1${NC}" + show_help + exit 1 + ;; + esac + + echo -e "\n${GREEN}${BOLD}Done!${NC}" + echo -e "${BLUE}Original configuration backed up to: $BACKUP_FILE${NC}" + 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}" +} + +main "$@" \ No newline at end of file diff --git a/update-setup.sh b/update-setup.sh old mode 100644 new mode 100755