From a74707dc1f4a0fa2233645add60b62d3483421a6 Mon Sep 17 00:00:00 2001 From: aki Date: Sat, 26 Apr 2025 02:59:46 +0800 Subject: [PATCH] fix(authelia): Authentication management and middleware errors - Fix middleware "true@docker" does not exist errors - Integrate authentication management directly into update-setup.sh - Add command-line support for managing service authentication - Add backup file cleanup functionality - Update README with new authentication management instructions - Remove standalone manage-auth.sh script --- README.md | 55 ++++-- manage-auth.sh | 302 ------------------------------- update-setup.sh | 468 ++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 470 insertions(+), 355 deletions(-) delete mode 100755 manage-auth.sh diff --git a/README.md b/README.md index ed1d094..51f6632 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The core idea is to manage media libraries (movies, TV shows, music), automate d - [(Optional) VPN Configuration](#optional-vpn-configuration) - [(Optional) Traefik DNS Challenge](#optional-traefik-dns-challenge) - [Service Access](#service-access) - - [Configuring Authentication Per Service](#configuring-authentication-per-service) + - [Managing Service Authentication](#managing-service-authentication) - [Optional Services](#optional-services) - [Troubleshooting](#troubleshooting) - [Middleware Not Found Errors](#middleware-not-found-errors) @@ -362,34 +362,57 @@ Authelia uses the `authelia/users_database.yml` file to manage users. ## Service Access -With the default Tailscale setup and Authelia enabled, services are securely accessible via HTTPS using your Tailscale node's name or IP. Authentication is controlled by the `AUTH_*` environment variables. +With the default Tailscale setup and Authelia enabled, services are securely accessible via HTTPS using your Tailscale node's name or IP. Authentication is controlled by the included `update-setup.sh` script. * **Login Portal:** `https:///` (Redirects unauthenticated users here for secured services) -* **Homepage Dashboard:** `https:///home` (Requires login if `AUTH_HOMEPAGE=true`) -* **Sonarr:** `https:///sonarr` (Requires login if `AUTH_SONARR=true`) -* **Radarr:** `https:///radarr` (Requires login if `AUTH_RADARR=true`) -* **qBittorrent:** `https:///qbittorrent` (Requires login if `AUTH_QBITTORRENT=true`) -* **Jellyfin:** `https:///jellyfin` (Requires login if `AUTH_JELLYFIN=true`, default is `false`) +* **Homepage Dashboard:** `https:///home` (Requires login by default) +* **Sonarr:** `https:///sonarr` (Requires login by default) +* **Radarr:** `https:///radarr` (Requires login by default) +* **qBittorrent:** `https:///qbittorrent` (Requires login by default) +* **Jellyfin:** `https:///jellyfin` (Requires login by default) * ...and so on. Replace `` with your Tailscale device name (e.g., `tailscale-nas.your-tailnet.ts.net`) or its Tailscale IP address. If you configure DNS for your `APP_HOSTNAME` variable to point to the Tailscale IP, you can use `https:///`. -### Configuring Authentication Per Service +### Managing Service Authentication -You can control which services require authentication by setting the appropriate variables in your `.env` file: +You can control which services require authentication using the updated `update-setup.sh` script: ```bash -# Example: Allow Jellyfin and qBittorrent without authentication, require it for others -AUTH_JELLYFIN=false -AUTH_QBITTORRENT=false -AUTH_SONARR=true -AUTH_RADARR=true -# ...and so on +# List all services and their authentication status +./update-setup.sh list-auth + +# Disable authentication for Jellyfin (no login required) +./update-setup.sh disable-auth jellyfin + +# Enable authentication for Jellyfin (login required) +./update-setup.sh enable-auth jellyfin + +# Disable authentication for all services +./update-setup.sh disable-all-auth + +# Enable authentication for all services +./update-setup.sh enable-all-auth + +# Clean up backup files (keeps most recent by default) +./update-setup.sh cleanup + +# View all available commands +./update-setup.sh help ``` -If a variable is not explicitly set, authentication defaults to `true` for that service (except for Jellyfin, which defaults to `false`). +You can also manage authentication through the interactive menu by running `./update-setup.sh` and selecting option 5. + +After making changes, restart your stack for the changes to take effect: + +```bash +docker compose down +docker compose up -d +``` + +This approach gives you complete control over which services require authentication, without needing to manually edit configuration files. ## Optional Services diff --git a/manage-auth.sh b/manage-auth.sh deleted file mode 100755 index 8b5f1af..0000000 --- a/manage-auth.sh +++ /dev/null @@ -1,302 +0,0 @@ -#!/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 index 15fa6d5..f6fd93b 100755 --- a/update-setup.sh +++ b/update-setup.sh @@ -5,6 +5,7 @@ # - Updates Authelia configuration from example file # - Configures services with correct paths and API keys # - Manages Authelia accounts +# - Controls service authentication requirements # Created: April 26, 2025 set -e @@ -27,6 +28,8 @@ ENV_BACKUP=".env.${TIMESTAMP}.bak" AUTHELIA_CONFIG="authelia/configuration.yml" AUTHELIA_CONFIG_EXAMPLE="authelia/configuration.example.yml" AUTHELIA_CONFIG_BACKUP="authelia/configuration.${TIMESTAMP}.bak" +COMPOSE_FILE="docker-compose.yml" +COMPOSE_BACKUP="docker-compose.${TIMESTAMP}.bak" # Print section header print_header() { @@ -336,7 +339,6 @@ update_service_configs() { # Additional utility functions ################################################## -# Function to generate a random passphrase with random separators and numbers generate_passphrase() { local words=( "apple" "banana" "cherry" "dragon" "eagle" "forest" "guitar" "harbor" @@ -348,32 +350,345 @@ generate_passphrase() { local separators=( "-" "_" "." "+" "=" "*" "~" "^" "@" "#" "%" "&" "!" "?" ) - # Generate a random number between 100 and 999 local random_num=$((RANDOM % 900 + 100)) - # Select 3 random words local selected_words=() for i in {1..3}; do local index=$((RANDOM % ${#words[@]})) selected_words+=(${words[$index]}) done - # Select 2 random separators local separator1=${separators[$((RANDOM % ${#separators[@]}))]} local separator2=${separators[$((RANDOM % ${#separators[@]}))]} - # Combine to form passphrase: word1-word2_word3123 echo "${selected_words[0]}${separator1}${selected_words[1]}${separator2}${selected_words[2]}${random_num}" } ################################################## -# PART 6: Authelia Account Management +# PART 4: Authentication Management +################################################## + +get_auth_status() { + local service=$1 + if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth" "$COMPOSE_FILE"; then + echo "enabled" + elif grep -q "traefik.http.routers.$service.middlewares=" "$COMPOSE_FILE"; then + echo "disabled" + else + echo "unknown" + fi +} + +enable_auth() { + local service=$1 + echo -e "${BLUE}Enabling authentication for $service...${NC}" + + if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then + echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" + return 1 + fi + + local service_start=$(grep -n "container_name: $service" "$COMPOSE_FILE" | cut -d':' -f1) + if [ -z "$service_start" ]; then + echo -e "${RED}Could not find service $service in $COMPOSE_FILE${NC}" + return 1 + fi + + local next_service=$(tail -n +$((service_start+1)) "$COMPOSE_FILE" | grep -n "container_name:" | head -1 | cut -d':' -f1) + if [ -n "$next_service" ]; then + next_service=$((service_start + next_service)) + else + next_service=$(wc -l < "$COMPOSE_FILE") + fi + + local service_section=$(sed -n "${service_start},${next_service}p" "$COMPOSE_FILE") + + if echo "$service_section" | grep -q "traefik.http.routers.$service.middlewares="; then + local middlewares_line=$(echo "$service_section" | grep "traefik.http.routers.$service.middlewares=") + + if echo "$middlewares_line" | grep -q "authelia-auth@docker"; then + echo -e "${GREEN}Authentication already enabled for $service${NC}" + return 0 + fi + + if echo "$middlewares_line" | grep -q "middlewares=$"; then + sed -i "s|traefik.http.routers.$service.middlewares=|traefik.http.routers.$service.middlewares=authelia-auth@docker|" "$COMPOSE_FILE" + elif echo "$middlewares_line" | grep -q "middlewares=.*@docker"; then + sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)@docker|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE" + else + sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE" + fi + else + local labels_line=$(echo "$service_section" | grep -n "labels:" | cut -d':' -f1) + if [ -z "$labels_line" ]; then + echo -e "${RED}Could not find labels section for service $service${NC}" + return 1 + fi + + local rule_line=$(echo "$service_section" | grep -n "traefik.http.routers.$service.rule=" | cut -d':' -f1) + if [ -n "$rule_line" ]; then + rule_line=$((service_start + rule_line)) + sed -i "${rule_line}a \ \ \ \ \ \ - traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE" + else + echo -e "${RED}Could not find rule line for service $service${NC}" + return 1 + fi + fi + + echo -e "${GREEN}Authentication enabled for $service${NC}" + return 0 +} + +disable_auth() { + local service=$1 + echo -e "${BLUE}Disabling authentication for $service...${NC}" + + if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then + echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}" + return 1 + fi + + if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth@docker" "$COMPOSE_FILE"; then + if grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE"; then + sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE" + elif grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker," "$COMPOSE_FILE"; then + sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker,|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE" + elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker" "$COMPOSE_FILE"; then + sed -i "s|,authelia-auth@docker||" "$COMPOSE_FILE" + elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker,.*" "$COMPOSE_FILE"; then + sed -i "s|,authelia-auth@docker,|,|" "$COMPOSE_FILE" + else + echo -e "${RED}Could not determine how to remove authelia-auth from middlewares for $service${NC}" + return 1 + fi + + echo -e "${GREEN}Authentication disabled for $service${NC}" + else + echo -e "${GREEN}Authentication already disabled for $service${NC}" + fi + + return 0 +} + +list_services() { + print_header "Services Authentication Status" + + echo -e "${BLUE}Checking services in $COMPOSE_FILE...${NC}" + echo -e "${CYAN}SERVICE\t\tAUTH STATUS${NC}" + echo -e "${CYAN}-------\t\t-----------${NC}" + + local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') + local service_count=0 + + for service in $services; do + if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then + continue + fi + + local status=$(get_auth_status "$service") + + 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 + done + + if [ $service_count -eq 0 ]; then + echo -e "${YELLOW}No services found with authentication status.${NC}" + fi +} + +cleanup_backups() { + print_header "Backup Files Cleanup" + + echo -e "${BLUE}Searching for backup files...${NC}" + + local env_backups=$(find . -maxdepth 1 -name ".env.*.bak" | sort) + local compose_backups=$(find . -maxdepth 1 -name "docker-compose.*.bak" | sort) + local authelia_backups=$(find ./authelia -maxdepth 1 -name "configuration.*.bak" 2>/dev/null | sort) + + local env_count=$(echo "$env_backups" | grep -c "^") + local compose_count=$(echo "$compose_backups" | grep -c "^") + local authelia_count=$(echo "$authelia_backups" | grep -c "^") + + echo -e "${CYAN}Found:${NC}" + echo -e " - ${CYAN}$env_count .env backup files${NC}" + echo -e " - ${CYAN}$compose_count docker-compose.yml backup files${NC}" + echo -e " - ${CYAN}$authelia_count Authelia configuration backup files${NC}" + + local total_count=$((env_count + compose_count + authelia_count)) + + if [ $total_count -eq 0 ]; then + echo -e "${GREEN}No backup files found.${NC}" + return 0 + fi + + echo -e "${YELLOW}Do you want to delete all backup files? [y/N]:${NC}" + read -r answer + if [[ ! "$answer" =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Would you like to keep the most recent backup of each type? [Y/n]:${NC}" + read -r keep_recent + + if [[ "$keep_recent" =~ ^[Nn]$ ]]; then + echo -e "${RED}Backup cleanup cancelled.${NC}" + return 0 + fi + + if [ $env_count -gt 1 ]; then + local keep_env=$(echo "$env_backups" | tail -1) + local del_env=$(echo "$env_backups" | grep -v "$keep_env") + echo -e "${BLUE}Keeping most recent .env backup: ${CYAN}$keep_env${NC}" + echo -e "${BLUE}Deleting ${CYAN}$((env_count - 1))${BLUE} older .env backups${NC}" + for file in $del_env; do + rm "$file" + done + fi + + if [ $compose_count -gt 1 ]; then + local keep_compose=$(echo "$compose_backups" | tail -1) + local del_compose=$(echo "$compose_backups" | grep -v "$keep_compose") + echo -e "${BLUE}Keeping most recent docker-compose.yml backup: ${CYAN}$keep_compose${NC}" + echo -e "${BLUE}Deleting ${CYAN}$((compose_count - 1))${BLUE} older docker-compose.yml backups${NC}" + for file in $del_compose; do + rm "$file" + done + fi + + if [ $authelia_count -gt 1 ]; then + local keep_authelia=$(echo "$authelia_backups" | tail -1) + local del_authelia=$(echo "$authelia_backups" | grep -v "$keep_authelia") + echo -e "${BLUE}Keeping most recent Authelia config backup: ${CYAN}$keep_authelia${NC}" + echo -e "${BLUE}Deleting ${CYAN}$((authelia_count - 1))${BLUE} older Authelia config backups${NC}" + for file in $del_authelia; do + rm "$file" + done + fi + + echo -e "${GREEN}Cleanup completed with most recent backups retained.${NC}" + else + for file in $env_backups $compose_backups $authelia_backups; do + rm "$file" + done + echo -e "${GREEN}All $total_count backup files have been deleted.${NC}" + fi +} + +manage_auth() { + print_header "Authentication Management" + + echo -e "${BLUE}This tool lets you control which services require authentication${NC}" + echo -e "${BLUE}Choose an option:${NC}" + echo -e " ${CYAN}1. ${NC}List services and their authentication status" + echo -e " ${CYAN}2. ${NC}Enable authentication for a service" + echo -e " ${CYAN}3. ${NC}Disable authentication for a service" + echo -e " ${CYAN}4. ${NC}Enable authentication for all services" + echo -e " ${CYAN}5. ${NC}Disable authentication for all services" + echo -e " ${CYAN}6. ${NC}Return to main menu" + echo + + local choice + while true; do + echo -e "${YELLOW}Enter your choice [1-6]: ${NC}" + read -r choice + + case "$choice" in + 1) + list_services + ;; + 2) + create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" + + echo -e "${BLUE}Enter the service name to enable authentication for:${NC}" + read -r service + if [ -z "$service" ]; then + echo -e "${RED}No service name provided.${NC}" + continue + fi + enable_auth "$service" + + echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" + echo -e " ${CYAN}docker compose down${NC}" + echo -e " ${CYAN}docker compose up -d${NC}" + ;; + 3) + create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" + + echo -e "${BLUE}Enter the service name to disable authentication for:${NC}" + read -r service + if [ -z "$service" ]; then + echo -e "${RED}No service name provided.${NC}" + continue + fi + disable_auth "$service" + + echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" + echo -e " ${CYAN}docker compose down${NC}" + echo -e " ${CYAN}docker compose up -d${NC}" + ;; + 4) + create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" + + echo -e "${BLUE}Enabling authentication for all services...${NC}" + local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') + for service in $services; do + if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then + continue + fi + + enable_auth "$service" + done + + echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" + echo -e " ${CYAN}docker compose down${NC}" + echo -e " ${CYAN}docker compose up -d${NC}" + ;; + 5) + create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP" + + echo -e "${BLUE}Disabling authentication for all services...${NC}" + local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}') + for service in $services; do + if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then + continue + fi + + disable_auth "$service" + done + + echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}" + echo -e " ${CYAN}docker compose down${NC}" + echo -e " ${CYAN}docker compose up -d${NC}" + ;; + 6) + return 0 + ;; + *) + echo -e "${RED}Invalid choice. Please try again.${NC}" + ;; + esac + + echo + done +} + +################################################## +# PART 5: Authelia Account Management ################################################## manage_authelia_accounts() { print_header "Authelia Account Management" - # Check if the users_database.yml file exists local users_file="${CONFIG_ROOT:-.}/authelia/users_database.yml" if [ ! -f "$users_file" ]; then @@ -381,17 +696,14 @@ manage_authelia_accounts() { echo -e "${YELLOW}Would you like to create a new users database file? [y/N]:${NC}" read -r answer if [[ "$answer" =~ ^[Yy]$ ]]; then - # Create minimal users database file cat > "$users_file" </dev/null) if [ -z "$password_hash" ]; then @@ -494,10 +795,8 @@ EOL fi fi - # Strip the "Digest: " prefix if present password_hash=$(echo "$password_hash" | sed 's/^Digest: //') - # Verify hash format if [[ ! "$password_hash" =~ ^\$argon2id.*$ ]]; then echo -e "${RED}Error: Generated hash does not have the expected format. Actual value:${NC}" echo -e "${YELLOW}$password_hash${NC}" @@ -507,15 +806,11 @@ EOL echo -e "${GREEN}Password hash generated successfully.${NC}" - # Check if user already exists in the file and update if grep -q "^[[:space:]]*${username}:" "$users_file"; then - # User exists, update entry by commenting out old lines and adding new ones sed -i "/^[[:space:]]*${username}:/,/^[[:space:]]*[a-zA-Z0-9_-]\+:/ s/^/# UPDATED: /" "$users_file" - # Remove the last comment marker if it matched a different user sed -i "0,/^# UPDATED: [[:space:]]*[a-zA-Z0-9_-]\+:/ s/^# UPDATED: //" "$users_file" fi - # Add user to the file cat >> "$users_file" <" + 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${NC}" + echo -e " ${CYAN}docker compose up -d${NC}" + exit 0 + ;; + "disable-auth") + if [ -z "$2" ]; then + echo -e "${RED}Error: No service specified${NC}" + echo -e "Usage: $0 disable-auth " + 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${NC}" + echo -e " ${CYAN}docker compose up -d${NC}" + exit 0 + ;; + "enable-all-auth") + 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 "\n${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}" + exit 0 + ;; + "disable-all-auth") + 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 "\n${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}" + exit 0 + ;; + "cleanup") + cleanup_backups + exit 0 + ;; + "help") + print_header "Docker Compose NAS - Setup Tool" + echo -e "${BLUE}Usage: $0 [command] [arguments]${NC}" + echo -e "${BLUE}Commands:${NC}" + echo -e " ${CYAN}list-auth${NC} List authentication status for all services" + echo -e " ${CYAN}enable-auth ${NC} Enable authentication for a specific service" + echo -e " ${CYAN}disable-auth ${NC} Disable authentication for a specific service" + echo -e " ${CYAN}enable-all-auth${NC} Enable authentication for all services" + echo -e " ${CYAN}disable-all-auth${NC} Disable authentication for all services" + echo -e " ${CYAN}cleanup${NC} Clean up backup files" + echo -e " ${CYAN}help${NC} Show this help message" + echo -e "" + echo -e "${BLUE}Without arguments, the script will start in interactive mode.${NC}" + exit 0 + ;; + *) + echo -e "${RED}Unknown command: $1${NC}" + echo -e "${BLUE}Run '$0 help' for usage information${NC}" + exit 1 + ;; + esac +fi + print_header "Docker Compose NAS - Setup Tool" echo -e "${BLUE}This tool will help you configure your Docker Compose NAS setup${NC}" echo -e "${BLUE}Choose one or more options to run:${NC}" @@ -561,13 +951,15 @@ echo -e " ${CYAN}1. ${NC}Update .env file from .env.example" echo -e " ${CYAN}2. ${NC}Update Authelia configuration" echo -e " ${CYAN}3. ${NC}Update service configurations" echo -e " ${CYAN}4. ${NC}Manage Authelia accounts" -echo -e " ${CYAN}5. ${NC}Run ALL updates" +echo -e " ${CYAN}5. ${NC}Manage service authentication" +echo -e " ${CYAN}6. ${NC}Clean up backup files" +echo -e " ${CYAN}7. ${NC}Run ALL updates" echo -e " ${CYAN}q. ${NC}Quit" echo CHOICE="0" while [[ "$CHOICE" != "q" ]]; do - echo -e "${YELLOW}Enter your choice [1-5 or q to quit]: ${NC}" + echo -e "${YELLOW}Enter your choice [1-7 or q to quit]: ${NC}" read -r CHOICE case "$CHOICE" in @@ -575,12 +967,14 @@ while [[ "$CHOICE" != "q" ]]; do 2) update_authelia_config ;; 3) update_service_configs ;; 4) manage_authelia_accounts ;; - 5) + 5) manage_auth ;; + 6) cleanup_backups ;; + 7) update_env_file update_authelia_config update_service_configs manage_authelia_accounts - CHOICE="q" # Exit after running all + CHOICE="q" ;; q|Q) echo -e "${GREEN}Exiting setup tool.${NC}"