1187 lines
51 KiB
Bash
Executable File
1187 lines
51 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# Combined update script for docker-compose-nas
|
|
# - Updates .env file from .env.example while preserving values
|
|
# - Updates Authelia configuration from example file
|
|
# - Configures services with correct paths and API keys
|
|
# - Manages Authelia accounts
|
|
# - Controls service authentication requirements
|
|
# Created: April 26, 2025
|
|
|
|
set -e
|
|
|
|
# Color definitions
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
MAGENTA='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Files
|
|
ENV_FILE=".env"
|
|
ENV_EXAMPLE=".env.example"
|
|
TIMESTAMP=$(date +"%Y%m%d-%H%M%S")
|
|
ENV_BACKUP=".env.${TIMESTAMP}.bak"
|
|
AUTHELIA_CONFIG="authelia/configuration.yml"
|
|
AUTHELIA_CONFIG_EXAMPLE="authelia/configuration.example.yml"
|
|
AUTHELIA_CONFIG_BACKUP="authelia/configuration.${TIMESTAMP}.bak"
|
|
COMPOSE_FILE="docker-compose.yml"
|
|
COMPOSE_BACKUP="docker-compose.${TIMESTAMP}.bak"
|
|
|
|
# Check if yq is installed
|
|
check_yq() {
|
|
if ! command -v yq &> /dev/null; then
|
|
echo -e "${YELLOW}Warning: 'yq' is not installed. While not required, it provides better YAML handling.${NC}"
|
|
echo -e "${YELLOW}Installation instructions: https://github.com/mikefarah/yq#install${NC}"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Print section header
|
|
print_header() {
|
|
echo -e "\n${CYAN}${BOLD}$1${NC}"
|
|
echo -e "${CYAN}$(printf '=%.0s' $(seq 1 ${#1}))${NC}"
|
|
}
|
|
|
|
# Check if a file exists
|
|
check_file() {
|
|
if [ ! -f "$1" ]; then
|
|
echo -e "${RED}Error: $1 doesn't exist${NC}"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Function to create a backup
|
|
create_backup() {
|
|
echo -e "${BLUE}Creating backup of $1 as $2...${NC}"
|
|
cp "$1" "$2"
|
|
}
|
|
|
|
##################################################
|
|
# PART 1: Update .env file from .env.example
|
|
##################################################
|
|
|
|
update_env_file() {
|
|
print_header "Environment File Update Tool"
|
|
|
|
# Check if files exist
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
echo -e "${RED}Error: $ENV_FILE doesn't exist${NC}"
|
|
echo -e "Creating a new $ENV_FILE from $ENV_EXAMPLE"
|
|
cp "$ENV_EXAMPLE" "$ENV_FILE"
|
|
echo -e "${GREEN}Done! Please review and fill in required values in $ENV_FILE${NC}"
|
|
return 0
|
|
fi
|
|
|
|
if ! check_file "$ENV_EXAMPLE"; then
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${BLUE}This will update your $ENV_FILE based on the structure in $ENV_EXAMPLE${NC}"
|
|
echo -e "${BLUE}Your existing values will be preserved where possible${NC}"
|
|
echo -e "${BLUE}Backup will be created as: $ENV_BACKUP${NC}"
|
|
echo -e "${YELLOW}Continue? [y/N]:${NC}"
|
|
read -r answer
|
|
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
|
|
echo -e "${RED}Environment update cancelled.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
# Create backup of current .env
|
|
create_backup "$ENV_FILE" "$ENV_BACKUP"
|
|
|
|
# Store current env values
|
|
echo -e "${BLUE}Reading current environment values...${NC}"
|
|
declare -A current_values
|
|
declare -A current_keys_present
|
|
while IFS='=' read -r key value; do
|
|
# Skip comments and empty lines
|
|
if [[ ! "$key" =~ ^#.*$ ]] && [[ ! -z "$key" ]]; then
|
|
# Clean up any comments after the value
|
|
value=$(echo "$value" | sed 's/[[:space:]]*#.*$//')
|
|
# Trim leading/trailing whitespace
|
|
key=$(echo "$key" | xargs)
|
|
value=$(echo "$value" | xargs)
|
|
# Store in associative array if key is not empty
|
|
if [[ ! -z "$key" ]]; then
|
|
current_values["$key"]="$value"
|
|
# Track that this key existed in original file, regardless of value
|
|
current_keys_present["$key"]=1
|
|
fi
|
|
fi
|
|
done < "$ENV_FILE"
|
|
|
|
# Create new env file from example
|
|
echo -e "${BLUE}Creating new $ENV_FILE from $ENV_EXAMPLE...${NC}"
|
|
cp "$ENV_EXAMPLE" "$ENV_FILE.new"
|
|
|
|
# Track which keys from the current env have been used
|
|
declare -A used_keys
|
|
# Track new keys that need attention
|
|
new_keys=()
|
|
# Track keys with special warnings
|
|
special_keys=()
|
|
|
|
# Process the template and fill in values from current env
|
|
while IFS= read -r line; do
|
|
if [[ "$line" =~ ^([A-Za-z0-9_]+)=(.*)$ ]]; then
|
|
key="${BASH_REMATCH[1]}"
|
|
default_value="${BASH_REMATCH[2]}"
|
|
|
|
# Mark the key as used if it exists in the original file
|
|
if [[ -n "${current_keys_present[$key]}" ]]; then
|
|
used_keys["$key"]=1
|
|
|
|
# Replace the line with the current value if one exists
|
|
if [[ -n "${current_values[$key]}" ]]; then
|
|
sed -i "s|^$key=.*$|$key=${current_values[$key]}|" "$ENV_FILE.new"
|
|
fi
|
|
# If key doesn't exist in original file and has empty/placeholder value
|
|
elif [[ -z "$default_value" ]] || [[ "$default_value" == '""' ]] || [[ "$default_value" == "''" ]]; then
|
|
new_keys+=("$key")
|
|
|
|
# Special attention for Authelia keys
|
|
if [[ "$key" == AUTHELIA_*_SECRET* ]] || [[ "$key" == AUTHELIA_*_KEY* ]]; then
|
|
special_keys+=("$key")
|
|
fi
|
|
fi
|
|
fi
|
|
done < "$ENV_FILE.new"
|
|
|
|
# Create section for unused/deprecated keys at the bottom of the file
|
|
echo -e "\n\n# --- DEPRECATED OR UNUSED KEYS (Kept for Reference) ---" >> "$ENV_FILE.new"
|
|
echo -e "# Keys below were in your original .env but aren't in the current .env.example" >> "$ENV_FILE.new"
|
|
echo -e "# They may be deprecated or renamed. Review and remove if no longer needed\n" >> "$ENV_FILE.new"
|
|
|
|
unused_keys_count=0
|
|
for key in "${!current_values[@]}"; do
|
|
if [[ -z "${used_keys[$key]}" ]]; then
|
|
echo "$key=${current_values[$key]} # DEPRECATED/UNUSED - Review" >> "$ENV_FILE.new"
|
|
unused_keys_count=$((unused_keys_count + 1))
|
|
fi
|
|
done
|
|
|
|
# Replace the old file with the new one
|
|
mv "$ENV_FILE.new" "$ENV_FILE"
|
|
|
|
# Generate summary
|
|
echo -e "\n${GREEN}${BOLD}Environment Update Complete!${NC}"
|
|
echo -e "${BLUE}Summary:${NC}"
|
|
echo -e " - ${CYAN}Original config backed up to: $ENV_BACKUP${NC}"
|
|
echo -e " - ${CYAN}Updated .env structure to match .env.example${NC}"
|
|
echo -e " - ${CYAN}Preserved ${#used_keys[@]} existing values${NC}"
|
|
|
|
if [[ $unused_keys_count -gt 0 ]]; then
|
|
echo -e " - ${YELLOW}Found $unused_keys_count deprecated/unused keys${NC}"
|
|
echo -e " ${YELLOW}These have been moved to the bottom of the file with warnings${NC}"
|
|
fi
|
|
|
|
if [[ ${#new_keys[@]} -gt 0 ]]; then
|
|
echo -e "\n${YELLOW}${BOLD}NEW KEYS NEEDING ATTENTION:${NC}"
|
|
echo -e "${YELLOW}The following keys are new and may need values set:${NC}"
|
|
for key in "${new_keys[@]}"; do
|
|
echo -e " - ${MAGENTA}$key${NC}"
|
|
done
|
|
fi
|
|
|
|
if [[ ${#special_keys[@]} -gt 0 ]]; then
|
|
echo -e "\n${RED}${BOLD}IMPORTANT SECURITY KEYS:${NC}"
|
|
echo -e "${RED}The following keys require secure values:${NC}"
|
|
for key in "${special_keys[@]}"; do
|
|
echo -e " - ${MAGENTA}$key${NC}"
|
|
|
|
# Specific advice for Authelia keys
|
|
if [[ "$key" == AUTHELIA_*_SECRET* ]] || [[ "$key" == AUTHELIA_*_KEY* ]]; then
|
|
echo -e " ${CYAN}Generate with: ${GREEN}openssl rand -hex 32${NC}"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo -e "\n${BLUE}Review your updated $ENV_FILE file and adjust any values as needed.${NC}"
|
|
}
|
|
|
|
##################################################
|
|
# PART 2: Update Authelia configuration
|
|
##################################################
|
|
|
|
update_authelia_config() {
|
|
print_header "Authelia Configuration Update Tool"
|
|
|
|
# Check if files exist
|
|
if ! check_file "$AUTHELIA_CONFIG_EXAMPLE"; then
|
|
echo -e "${RED}Error: Example configuration file doesn't exist${NC}"
|
|
return 1
|
|
fi
|
|
|
|
# If config file doesn't exist, create it from example
|
|
if [ ! -f "$AUTHELIA_CONFIG" ]; then
|
|
echo -e "${YELLOW}Authelia configuration file doesn't exist, creating from example...${NC}"
|
|
cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG"
|
|
echo -e "${GREEN}Created new Authelia configuration file.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${BLUE}This will update your Authelia configuration based on the example file${NC}"
|
|
echo -e "${BLUE}Backup will be created as: $AUTHELIA_CONFIG_BACKUP${NC}"
|
|
echo -e "${YELLOW}Continue? [y/N]:${NC}"
|
|
read -r answer
|
|
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
|
|
echo -e "${RED}Authelia config update cancelled.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
# Create backup of current config
|
|
create_backup "$AUTHELIA_CONFIG" "$AUTHELIA_CONFIG_BACKUP"
|
|
|
|
# Copy the example file over the current one
|
|
cp "$AUTHELIA_CONFIG_EXAMPLE" "$AUTHELIA_CONFIG"
|
|
|
|
# Get the tailnet domain from .env for proper configuration
|
|
if [ -f "$ENV_FILE" ]; then
|
|
TAILNET_DOMAIN=$(grep -o "TAILSCALE_TAILNET_DOMAIN=.*" "$ENV_FILE" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
|
|
TAILSCALE_HOSTNAME=$(grep -o "TAILSCALE_HOSTNAME=.*" "$ENV_FILE" | cut -d'=' -f2 | tr -d '"' | tr -d "'")
|
|
|
|
if [ -n "$TAILNET_DOMAIN" ] && [ -n "$TAILSCALE_HOSTNAME" ]; then
|
|
# Replace placeholders with actual values
|
|
sed -i "s/\*.ts.net/\*.$TAILNET_DOMAIN/g" "$AUTHELIA_CONFIG"
|
|
sed -i "s/tailscale-nas.ts.net/$TAILSCALE_HOSTNAME.$TAILNET_DOMAIN/g" "$AUTHELIA_CONFIG"
|
|
echo -e "${GREEN}Configured Authelia with your Tailscale domain: $TAILSCALE_HOSTNAME.$TAILNET_DOMAIN${NC}"
|
|
fi
|
|
fi
|
|
|
|
echo -e "${GREEN}${BOLD}Authelia Configuration Update Complete!${NC}"
|
|
echo -e "${BLUE}${BOLD}Note:${NC} Original config backed up to: $AUTHELIA_CONFIG_BACKUP"
|
|
}
|
|
|
|
##################################################
|
|
# PART 3: Update Service Configurations
|
|
##################################################
|
|
|
|
update_arr_config() {
|
|
local container=$1
|
|
local path=$2
|
|
|
|
echo -e "${BLUE}Updating ${container} configuration...${NC}"
|
|
until [ -f "${CONFIG_ROOT:-.}"/"$container"/config.xml ]; do sleep 1; done
|
|
sed -i.bak "s/<UrlBase><\/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}"
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${BLUE}Checking for running containers to update...${NC}"
|
|
for container in $(docker ps --format '{{.Names}}'); do
|
|
if [[ "$container" =~ ^(radarr|sonarr|lidarr|prowlarr)$ ]]; then
|
|
update_arr_config "$container" "$container"
|
|
elif [[ "$container" =~ ^(bazarr)$ ]]; then
|
|
update_bazarr_config "$container"
|
|
elif [[ "$container" =~ ^(qbittorrent)$ ]]; then
|
|
update_qbittorrent_config "$container"
|
|
fi
|
|
done
|
|
|
|
echo -e "\n${GREEN}${BOLD}Service Configuration Update Complete!${NC}"
|
|
}
|
|
|
|
##################################################
|
|
# Additional utility functions
|
|
##################################################
|
|
|
|
generate_passphrase() {
|
|
local words=(
|
|
"apple" "banana" "cherry" "dragon" "eagle" "forest" "guitar" "harbor"
|
|
"island" "jungle" "kitchen" "lemon" "mountain" "notebook" "ocean"
|
|
"planet" "quiet" "river" "summer" "tiger" "umbrella" "village"
|
|
"winter" "xylophone" "yellow" "zebra" "anchor" "beaver" "candle"
|
|
"dolphin" "elephant" "falcon" "giraffe" "hamster" "iguana" "jaguar"
|
|
)
|
|
|
|
local separators=( "-" "_" "." "+" "=" "*" "~" "^" "@" "#" "%" "&" "!" "?" )
|
|
|
|
local random_num=$((RANDOM % 900 + 100))
|
|
|
|
local selected_words=()
|
|
for i in {1..3}; do
|
|
local index=$((RANDOM % ${#words[@]}))
|
|
selected_words+=(${words[$index]})
|
|
done
|
|
|
|
local separator1=${separators[$((RANDOM % ${#separators[@]}))]}
|
|
local separator2=${separators[$((RANDOM % ${#separators[@]}))]}
|
|
|
|
echo "${selected_words[0]}${separator1}${selected_words[1]}${separator2}${selected_words[2]}${random_num}"
|
|
}
|
|
|
|
##################################################
|
|
# PART 4: Authentication Management
|
|
##################################################
|
|
|
|
# Get the current auth status for a service
|
|
get_auth_status() {
|
|
local service=$1
|
|
|
|
# Use yq if available for more reliable YAML parsing
|
|
if command -v yq &> /dev/null; then
|
|
local middlewares=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service.middlewares=\"))" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -n "$middlewares" ]; then
|
|
if echo "$middlewares" | grep -q "authelia-auth"; then
|
|
echo "enabled"
|
|
else
|
|
echo "disabled"
|
|
fi
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
else
|
|
# Fall back to grep if yq isn't available
|
|
if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth" "$COMPOSE_FILE"; then
|
|
echo "enabled"
|
|
elif grep -q "traefik.http.routers.$service.middlewares=" "$COMPOSE_FILE"; then
|
|
echo "disabled"
|
|
else
|
|
echo "unknown"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
enable_auth() {
|
|
local service=$1
|
|
echo -e "${BLUE}Enabling authentication for $service...${NC}"
|
|
|
|
# Check if service exists in the compose file
|
|
# Use yq if available for more reliable parsing
|
|
if command -v yq &> /dev/null; then
|
|
# Check if service exists
|
|
local service_exists=$(yq e ".services.$service" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -z "$service_exists" ] || [ "$service_exists" == "null" ]; then
|
|
echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
|
|
# Check if auth is already enabled
|
|
local middlewares=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service.middlewares=\"))" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -n "$middlewares" ] && echo "$middlewares" | grep -q "authelia-auth"; then
|
|
echo -e "${GREEN}Authentication already enabled for $service${NC}"
|
|
return 0
|
|
fi
|
|
|
|
# Create a temporary file for editing with yq
|
|
local temp_file="${COMPOSE_FILE}.tmp"
|
|
|
|
# Check if there's already a middlewares label
|
|
if [ -n "$middlewares" ]; then
|
|
# Modify the existing middlewares label to include authelia-auth
|
|
if echo "$middlewares" | grep -q "middlewares=$"; then
|
|
# Empty middlewares
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=authelia-auth@docker\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
elif echo "$middlewares" | grep -q "middlewares=.*@docker"; then
|
|
# Has other middlewares with @docker
|
|
local current_value=$(echo "$middlewares" | sed 's/.*middlewares=\(.*\)@docker.*/\1/')
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${current_value},authelia-auth@docker\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
else
|
|
# Has middlewares without @docker
|
|
local current_value=$(echo "$middlewares" | sed 's/.*middlewares=\(.*\).*/\1/')
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${current_value},authelia-auth@docker\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
fi
|
|
else
|
|
# Need to add a new middlewares label
|
|
# Check if the service has any labels
|
|
local has_labels=$(yq e ".services.$service.labels" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -z "$has_labels" ] || [ "$has_labels" == "null" ]; then
|
|
# Add a new labels array with the middlewares
|
|
yq e ".services.$service.labels = [\"traefik.http.routers.$service.middlewares=authelia-auth@docker\"]" "$COMPOSE_FILE" > "$temp_file"
|
|
else
|
|
# Add the middlewares to the existing labels
|
|
yq e ".services.$service.labels += [\"traefik.http.routers.$service.middlewares=authelia-auth@docker\"]" "$COMPOSE_FILE" > "$temp_file"
|
|
fi
|
|
fi
|
|
|
|
# Replace the original file if the temp file was created successfully
|
|
if [ $? -eq 0 ] && [ -f "$temp_file" ]; then
|
|
mv "$temp_file" "$COMPOSE_FILE"
|
|
echo -e "${GREEN}Authentication enabled for $service${NC}"
|
|
return 0
|
|
else
|
|
echo -e "${RED}Failed to update the compose file with yq. Falling back to sed.${NC}"
|
|
rm -f "$temp_file" # Clean up if the temp file exists
|
|
fi
|
|
fi
|
|
|
|
# Fall back to the existing implementation if yq is not available or failed
|
|
if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then
|
|
echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local service_start=$(grep -n "container_name: $service" "$COMPOSE_FILE" | cut -d':' -f1)
|
|
if [ -z "$service_start" ]; then
|
|
echo -e "${RED}Could not find service $service in $COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local next_service=$(tail -n +$((service_start+1)) "$COMPOSE_FILE" | grep -n "container_name:" | head -1 | cut -d':' -f1)
|
|
if [ -n "$next_service" ]; then
|
|
next_service=$((service_start + next_service))
|
|
else
|
|
next_service=$(wc -l < "$COMPOSE_FILE")
|
|
fi
|
|
|
|
local service_section=$(sed -n "${service_start},${next_service}p" "$COMPOSE_FILE")
|
|
|
|
if echo "$service_section" | grep -q "traefik.http.routers.$service.middlewares="; then
|
|
local middlewares_line=$(echo "$service_section" | grep "traefik.http.routers.$service.middlewares=")
|
|
|
|
if echo "$middlewares_line" | grep -q "authelia-auth@docker"; then
|
|
echo -e "${GREEN}Authentication already enabled for $service${NC}"
|
|
return 0
|
|
fi
|
|
|
|
if echo "$middlewares_line" | grep -q "middlewares=$"; then
|
|
sed -i "s|traefik.http.routers.$service.middlewares=|traefik.http.routers.$service.middlewares=authelia-auth@docker|" "$COMPOSE_FILE"
|
|
elif echo "$middlewares_line" | grep -q "middlewares=.*@docker"; then
|
|
sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)@docker|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE"
|
|
else
|
|
sed -i "s|traefik.http.routers.$service.middlewares=\(.*\)|traefik.http.routers.$service.middlewares=\1,authelia-auth@docker|" "$COMPOSE_FILE"
|
|
fi
|
|
else
|
|
local labels_line=$(echo "$service_section" | grep -n "labels:" | cut -d':' -f1)
|
|
if [ -z "$labels_line" ]; then
|
|
echo -e "${RED}Could not find labels section for service $service${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local rule_line=$(echo "$service_section" | grep -n "traefik.http.routers.$service.rule=" | cut -d':' -f1)
|
|
if [ -n "$rule_line" ]; then
|
|
rule_line=$((service_start + rule_line))
|
|
sed -i "${rule_line}a \ \ \ \ \ \ - traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE"
|
|
else
|
|
echo -e "${RED}Could not find rule line for service $service${NC}"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
echo -e "${GREEN}Authentication enabled for $service${NC}"
|
|
return 0
|
|
}
|
|
|
|
disable_auth() {
|
|
local service=$1
|
|
echo -e "${BLUE}Disabling authentication for $service...${NC}"
|
|
|
|
# Use yq if available for more reliable parsing
|
|
if command -v yq &> /dev/null; then
|
|
# Check if service exists
|
|
local service_exists=$(yq e ".services.$service" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -z "$service_exists" ] || [ "$service_exists" == "null" ]; then
|
|
echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
|
|
# Check if auth is already disabled
|
|
local middlewares=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service.middlewares=\"))" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -n "$middlewares" ] && ! echo "$middlewares" | grep -q "authelia-auth"; then
|
|
echo -e "${GREEN}Authentication already disabled for $service${NC}"
|
|
return 0
|
|
fi
|
|
|
|
# Create a temporary file for editing with yq
|
|
local temp_file="${COMPOSE_FILE}.tmp"
|
|
|
|
# Extract current middlewares
|
|
if [ -n "$middlewares" ]; then
|
|
if echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker$"; then
|
|
# Only authelia-auth@docker, replace with empty
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
elif echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker,"; then
|
|
# authelia-auth@docker at start with comma
|
|
local remaining=$(echo "$middlewares" | sed 's/.*middlewares=authelia-auth@docker,\(.*\)/\1/')
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${remaining}\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
elif echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker$"; then
|
|
# authelia-auth@docker at end with comma
|
|
local remaining=$(echo "$middlewares" | sed 's/\(.*\),authelia-auth@docker$/\1/')
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${remaining}\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
elif echo "$middlewares" | grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker,.*"; then
|
|
# authelia-auth@docker in the middle with commas
|
|
local remaining=$(echo "$middlewares" | sed 's/\(.*\),authelia-auth@docker,\(.*\)/\1,\2/')
|
|
yq e ".services.$service.labels |= map(. | select(contains(\"traefik.http.routers.$service.middlewares=\")) |= \"traefik.http.routers.$service.middlewares=${remaining}\" // .)" "$COMPOSE_FILE" > "$temp_file"
|
|
else
|
|
echo -e "${RED}Could not determine how to remove authelia-auth from middlewares for $service${NC}"
|
|
return 1
|
|
fi
|
|
|
|
# Replace the original file if the temp file was created successfully
|
|
if [ $? -eq 0 ] && [ -f "$temp_file" ]; then
|
|
mv "$temp_file" "$COMPOSE_FILE"
|
|
echo -e "${GREEN}Authentication disabled for $service${NC}"
|
|
return 0
|
|
else
|
|
echo -e "${RED}Failed to update the compose file with yq. Falling back to sed.${NC}"
|
|
rm -f "$temp_file" # Clean up if the temp file exists
|
|
fi
|
|
else
|
|
echo -e "${GREEN}No middlewares found for $service, authentication is already disabled${NC}"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Fall back to the existing implementation if yq is not available or failed
|
|
if ! grep -q "container_name: $service" "$COMPOSE_FILE"; then
|
|
echo -e "${RED}Service $service not found in $COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
|
|
if grep -q "traefik.http.routers.$service.middlewares=.*authelia-auth@docker" "$COMPOSE_FILE"; then
|
|
if grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker" "$COMPOSE_FILE"; then
|
|
sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE"
|
|
elif grep -q "traefik.http.routers.$service.middlewares=authelia-auth@docker," "$COMPOSE_FILE"; then
|
|
sed -i "s|traefik.http.routers.$service.middlewares=authelia-auth@docker,|traefik.http.routers.$service.middlewares=|" "$COMPOSE_FILE"
|
|
elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker" "$COMPOSE_FILE"; then
|
|
sed -i "s|,authelia-auth@docker||" "$COMPOSE_FILE"
|
|
elif grep -q "traefik.http.routers.$service.middlewares=.*,authelia-auth@docker,.*" "$COMPOSE_FILE"; then
|
|
sed -i "s|,authelia-auth@docker,|,|" "$COMPOSE_FILE"
|
|
else
|
|
echo -e "${RED}Could not determine how to remove authelia-auth from middlewares for $service${NC}"
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${GREEN}Authentication disabled for $service${NC}"
|
|
else
|
|
echo -e "${GREEN}Authentication already disabled for $service${NC}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
list_services() {
|
|
print_header "Services Authentication Status"
|
|
|
|
# Check if file exists
|
|
if ! check_file "$COMPOSE_FILE"; then
|
|
echo -e "${RED}Error: $COMPOSE_FILE doesn't exist${NC}"
|
|
return 1
|
|
fi
|
|
|
|
# Show a warning if we're not creating a backup for this operation
|
|
echo -e "${BLUE}Checking services in $COMPOSE_FILE...${NC}"
|
|
echo -e "${CYAN}SERVICE\t\tAUTH STATUS${NC}"
|
|
echo -e "${CYAN}-------\t\t-----------${NC}"
|
|
|
|
local service_count=0
|
|
|
|
# Use yq if available for more reliable parsing
|
|
if command -v yq &> /dev/null; then
|
|
# Get all services from the docker-compose.yml file
|
|
local services=$(yq e '.services | keys | .[]' "$COMPOSE_FILE" 2>/dev/null)
|
|
|
|
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
|
|
|
|
# Check if this service has Traefik router configured
|
|
local has_router=$(yq e ".services.$service.labels[] | select(contains(\"traefik.http.routers.$service\"))" "$COMPOSE_FILE" 2>/dev/null)
|
|
if [ -n "$has_router" ]; then
|
|
local status=$(get_auth_status "$service")
|
|
|
|
# Format the output with padding
|
|
printf "${BOLD}%-20s${NC}" "$service"
|
|
|
|
case "$status" in
|
|
"enabled")
|
|
echo -e "${GREEN}Enabled${NC}"
|
|
service_count=$((service_count + 1))
|
|
;;
|
|
"disabled")
|
|
echo -e "${YELLOW}Disabled${NC}"
|
|
service_count=$((service_count + 1))
|
|
;;
|
|
*)
|
|
echo -e "${RED}Unknown${NC}"
|
|
;;
|
|
esac
|
|
fi
|
|
done
|
|
else
|
|
# Fallback to using grep for parsing (less reliable)
|
|
# First identify 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
|
|
|
|
# Look specifically for router configuration for this service
|
|
if grep -q "traefik.http.routers.$service" "$COMPOSE_FILE"; then
|
|
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
|
|
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 that no services are configured with Traefik routers,${NC}"
|
|
echo -e "${YELLOW}or that the compose file has an unexpected structure.${NC}"
|
|
fi
|
|
}
|
|
|
|
cleanup_backups() {
|
|
print_header "Backup Files Cleanup"
|
|
|
|
echo -e "${BLUE}Searching for backup files...${NC}"
|
|
|
|
local env_backups=$(find . -maxdepth 1 -name ".env.*.bak" | sort)
|
|
local compose_backups=$(find . -maxdepth 1 -name "docker-compose.*.bak" | sort)
|
|
local authelia_backups=$(find ./authelia -maxdepth 1 -name "configuration.*.bak" 2>/dev/null | sort)
|
|
|
|
local env_count=$(echo "$env_backups" | grep -c "^")
|
|
local compose_count=$(echo "$compose_backups" | grep -c "^")
|
|
local authelia_count=$(echo "$authelia_backups" | grep -c "^")
|
|
|
|
echo -e "${CYAN}Found:${NC}"
|
|
echo -e " - ${CYAN}$env_count .env backup files${NC}"
|
|
echo -e " - ${CYAN}$compose_count docker-compose.yml backup files${NC}"
|
|
echo -e " - ${CYAN}$authelia_count Authelia configuration backup files${NC}"
|
|
|
|
local total_count=$((env_count + compose_count + authelia_count))
|
|
|
|
if [ $total_count -eq 0 ]; then
|
|
echo -e "${GREEN}No backup files found.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
echo -e "${YELLOW}Do you want to delete all backup files? [y/N]:${NC}"
|
|
read -r answer
|
|
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
|
|
echo -e "${YELLOW}Would you like to keep the most recent backup of each type? [Y/n]:${NC}"
|
|
read -r keep_recent
|
|
|
|
if [[ "$keep_recent" =~ ^[Nn]$ ]]; then
|
|
echo -e "${RED}Backup cleanup cancelled.${NC}"
|
|
return 0
|
|
fi
|
|
|
|
if [ $env_count -gt 1 ]; then
|
|
local keep_env=$(echo "$env_backups" | tail -1)
|
|
local del_env=$(echo "$env_backups" | grep -v "$keep_env")
|
|
echo -e "${BLUE}Keeping most recent .env backup: ${CYAN}$keep_env${NC}"
|
|
echo -e "${BLUE}Deleting ${CYAN}$((env_count - 1))${BLUE} older .env backups${NC}"
|
|
for file in $del_env; do
|
|
rm "$file"
|
|
done
|
|
fi
|
|
|
|
if [ $compose_count -gt 1 ]; then
|
|
local keep_compose=$(echo "$compose_backups" | tail -1)
|
|
local del_compose=$(echo "$compose_backups" | grep -v "$keep_compose")
|
|
echo -e "${BLUE}Keeping most recent docker-compose.yml backup: ${CYAN}$keep_compose${NC}"
|
|
echo -e "${BLUE}Deleting ${CYAN}$((compose_count - 1))${BLUE} older docker-compose.yml backups${NC}"
|
|
for file in $del_compose; do
|
|
rm "$file"
|
|
done
|
|
fi
|
|
|
|
if [ $authelia_count -gt 1 ]; then
|
|
local keep_authelia=$(echo "$authelia_backups" | tail -1)
|
|
local del_authelia=$(echo "$authelia_backups" | grep -v "$keep_authelia")
|
|
echo -e "${BLUE}Keeping most recent Authelia config backup: ${CYAN}$keep_authelia${NC}"
|
|
echo -e "${BLUE}Deleting ${CYAN}$((authelia_count - 1))${BLUE} older Authelia config backups${NC}"
|
|
for file in $del_authelia; do
|
|
rm "$file"
|
|
done
|
|
fi
|
|
|
|
echo -e "${GREEN}Cleanup completed with most recent backups retained.${NC}"
|
|
else
|
|
for file in $env_backups $compose_backups $authelia_backups; do
|
|
rm "$file"
|
|
done
|
|
echo -e "${GREEN}All $total_count backup files have been deleted.${NC}"
|
|
fi
|
|
}
|
|
|
|
manage_auth() {
|
|
print_header "Authentication Management"
|
|
|
|
echo -e "${BLUE}This tool lets you control which services require authentication${NC}"
|
|
echo -e "${BLUE}Choose an option:${NC}"
|
|
echo -e " ${CYAN}1. ${NC}List services and their authentication status"
|
|
echo -e " ${CYAN}2. ${NC}Enable authentication for a service"
|
|
echo -e " ${CYAN}3. ${NC}Disable authentication for a service"
|
|
echo -e " ${CYAN}4. ${NC}Enable authentication for all services"
|
|
echo -e " ${CYAN}5. ${NC}Disable authentication for all services"
|
|
echo -e " ${CYAN}6. ${NC}Return to main menu"
|
|
echo
|
|
|
|
local choice
|
|
while true; do
|
|
echo -e "${YELLOW}Enter your choice [1-6]: ${NC}"
|
|
read -r choice
|
|
|
|
case "$choice" in
|
|
1)
|
|
list_services
|
|
;;
|
|
2)
|
|
create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP"
|
|
|
|
echo -e "${BLUE}Enter the service name to enable authentication for:${NC}"
|
|
read -r service
|
|
if [ -z "$service" ]; then
|
|
echo -e "${RED}No service name provided.${NC}"
|
|
continue
|
|
fi
|
|
enable_auth "$service"
|
|
|
|
echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}"
|
|
echo -e " ${CYAN}docker compose down${NC}"
|
|
echo -e " ${CYAN}docker compose up -d${NC}"
|
|
;;
|
|
3)
|
|
create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP"
|
|
|
|
echo -e "${BLUE}Enter the service name to disable authentication for:${NC}"
|
|
read -r service
|
|
if [ -z "$service" ]; then
|
|
echo -e "${RED}No service name provided.${NC}"
|
|
continue
|
|
fi
|
|
disable_auth "$service"
|
|
|
|
echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}"
|
|
echo -e " ${CYAN}docker compose down${NC}"
|
|
echo -e " ${CYAN}docker compose up -d${NC}"
|
|
;;
|
|
4)
|
|
create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP"
|
|
|
|
echo -e "${BLUE}Enabling authentication for all services...${NC}"
|
|
local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}')
|
|
for service in $services; do
|
|
if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then
|
|
continue
|
|
fi
|
|
|
|
enable_auth "$service"
|
|
done
|
|
|
|
echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}"
|
|
echo -e " ${CYAN}docker compose down${NC}"
|
|
echo -e " ${CYAN}docker compose up -d${NC}"
|
|
;;
|
|
5)
|
|
create_backup "$COMPOSE_FILE" "$COMPOSE_BACKUP"
|
|
|
|
echo -e "${BLUE}Disabling authentication for all services...${NC}"
|
|
local services=$(grep "container_name:" "$COMPOSE_FILE" | awk '{print $3}')
|
|
for service in $services; do
|
|
if [[ "$service" == "redis" || "$service" == "authelia" || "$service" == "traefik" || "$service" == "tailscale" || "$service" == "watchtower" || "$service" == "autoheal" || "$service" == "middlewares" ]]; then
|
|
continue
|
|
fi
|
|
|
|
disable_auth "$service"
|
|
done
|
|
|
|
echo -e "${YELLOW}Remember to restart the stack for changes to take effect:${NC}"
|
|
echo -e " ${CYAN}docker compose down${NC}"
|
|
echo -e " ${CYAN}docker compose up -d${NC}"
|
|
;;
|
|
6)
|
|
return 0
|
|
;;
|
|
*)
|
|
echo -e "${RED}Invalid choice. Please try again.${NC}"
|
|
;;
|
|
esac
|
|
|
|
echo
|
|
done
|
|
}
|
|
|
|
##################################################
|
|
# PART 5: Authelia Account Management
|
|
##################################################
|
|
|
|
manage_authelia_accounts() {
|
|
print_header "Authelia Account Management"
|
|
|
|
local users_file="${CONFIG_ROOT:-.}/authelia/users_database.yml"
|
|
|
|
if [ ! -f "$users_file" ]; then
|
|
echo -e "${RED}Error: users_database.yml not found at $users_file${NC}"
|
|
echo -e "${YELLOW}Would you like to create a new users database file? [y/N]:${NC}"
|
|
read -r answer
|
|
if [[ "$answer" =~ ^[Yy]$ ]]; then
|
|
cat > "$users_file" <<EOL
|
|
# Authelia User Database
|
|
# Documentation: https://www.authelia.com/configuration/security/authentication/file/
|
|
|
|
users:
|
|
admin:
|
|
displayname: "Admin User"
|
|
password: "$argon2id$v=19$m=102400,t=1,p=8$PBf/L9l3s7LwN6jX/B3tVg$9+q3kL8VAbpWj9Gv9Z6uA5bA4zT1fB2fH3aD5c6b7e8"
|
|
email: admin@example.com
|
|
groups:
|
|
- admins
|
|
- users
|
|
EOL
|
|
echo -e "${GREEN}Created new users_database.yml file${NC}"
|
|
else
|
|
echo -e "${RED}Account management cancelled.${NC}"
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
local backup_file="${users_file}.${TIMESTAMP}.bak"
|
|
cp "$users_file" "$backup_file"
|
|
echo -e "${BLUE}Backed up users database to ${backup_file}${NC}"
|
|
|
|
while true; do
|
|
echo -e "\n${CYAN}${BOLD}Add Authelia User${NC}"
|
|
echo -e "${BLUE}Enter a username (or press Enter to finish):${NC}"
|
|
read -r username
|
|
|
|
if [ -z "$username" ]; then
|
|
break
|
|
fi
|
|
|
|
if grep -q "^[[:space:]]*${username}:" "$users_file"; then
|
|
echo -e "${YELLOW}Warning: User '${username}' already exists.${NC}"
|
|
echo -e "${YELLOW}Would you like to update this user? [y/N]:${NC}"
|
|
read -r answer
|
|
if [[ ! "$answer" =~ ^[Yy]$ ]]; then
|
|
echo -e "${RED}Skipping user '${username}'.${NC}"
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
echo -e "${BLUE}Enter display name for ${username}:${NC}"
|
|
read -r displayname
|
|
if [ -z "$displayname" ]; then
|
|
displayname="$username"
|
|
fi
|
|
|
|
echo -e "${BLUE}Enter email for ${username}:${NC}"
|
|
read -r email
|
|
if [ -z "$email" ]; then
|
|
email="${username}@example.com"
|
|
fi
|
|
|
|
echo -e "${BLUE}Select group(s) for ${username} (comma-separated, default: users):${NC}"
|
|
echo -e "${CYAN}Available groups: admins, users${NC}"
|
|
read -r groups
|
|
if [ -z "$groups" ]; then
|
|
groups="users"
|
|
fi
|
|
|
|
IFS=',' read -ra group_array <<< "$groups"
|
|
formatted_groups=""
|
|
for group in "${group_array[@]}"; do
|
|
formatted_groups+=" - $(echo $group | xargs)\n"
|
|
done
|
|
|
|
generated_passphrase=$(generate_passphrase)
|
|
echo -e "${GREEN}Generated passphrase: ${BOLD}${generated_passphrase}${NC}"
|
|
echo -e "${YELLOW}Do you want to use this passphrase? [Y/n]:${NC}"
|
|
read -r use_generated
|
|
|
|
if [[ "$use_generated" =~ ^[Nn]$ ]]; then
|
|
echo -e "${BLUE}Enter a custom password for ${username}:${NC}"
|
|
read -rs password
|
|
if [ -z "$password" ]; then
|
|
echo -e "${RED}Error: Password cannot be empty.${NC}"
|
|
continue
|
|
fi
|
|
else
|
|
password="$generated_passphrase"
|
|
fi
|
|
|
|
echo -e "${CYAN}Generating password hash...${NC}"
|
|
|
|
password_hash=$(docker compose run --rm authelia authelia crypto hash generate argon2 --password "$password" 2>/dev/null)
|
|
|
|
if [ -z "$password_hash" ]; then
|
|
echo -e "${RED}Error: Failed to generate password hash. Is the authelia container available?${NC}"
|
|
echo -e "${YELLOW}Trying direct docker run method...${NC}"
|
|
password_hash=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$password" 2>/dev/null)
|
|
|
|
if [ -z "$password_hash" ]; then
|
|
echo -e "${RED}Error: Both methods failed to generate a password hash. Skipping user.${NC}"
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
password_hash=$(echo "$password_hash" | sed 's/^Digest: //')
|
|
|
|
if [[ ! "$password_hash" =~ ^\$argon2id.*$ ]]; then
|
|
echo -e "${RED}Error: Generated hash does not have the expected format. Actual value:${NC}"
|
|
echo -e "${YELLOW}$password_hash${NC}"
|
|
echo -e "${RED}Skipping user creation.${NC}"
|
|
continue
|
|
fi
|
|
|
|
echo -e "${GREEN}Password hash generated successfully.${NC}"
|
|
|
|
if grep -q "^[[:space:]]*${username}:" "$users_file"; then
|
|
sed -i "/^[[:space:]]*${username}:/,/^[[:space:]]*[a-zA-Z0-9_-]\+:/ s/^/# UPDATED: /" "$users_file"
|
|
sed -i "0,/^# UPDATED: [[:space:]]*[a-zA-Z0-9_-]\+:/ s/^# UPDATED: //" "$users_file"
|
|
fi
|
|
|
|
cat >> "$users_file" <<EOL
|
|
${username}:
|
|
displayname: "${displayname}"
|
|
password: "${password_hash}"
|
|
email: ${email}
|
|
groups:
|
|
$(echo -e "$formatted_groups")
|
|
EOL
|
|
|
|
echo -e "${GREEN}User '${username}' added successfully!${NC}"
|
|
echo -e "${CYAN}Password: ${BOLD}${password}${NC}"
|
|
echo -e "${YELLOW}Please save this password securely.${NC}"
|
|
done
|
|
|
|
echo -e "\n${GREEN}${BOLD}Account Management Complete!${NC}"
|
|
echo -e "${BLUE}Users file updated: ${users_file}${NC}"
|
|
echo -e "${BLUE}Backup saved as: ${backup_file}${NC}"
|
|
|
|
echo -e "${YELLOW}Would you like to restart Authelia to apply changes? [y/N]:${NC}"
|
|
read -r restart
|
|
if [[ "$restart" =~ ^[Yy]$ ]]; then
|
|
if docker compose restart authelia; then
|
|
echo -e "${GREEN}Authelia restarted successfully!${NC}"
|
|
else
|
|
echo -e "${RED}Failed to restart Authelia. Please restart manually.${NC}"
|
|
fi
|
|
else
|
|
echo -e "${YELLOW}Remember to restart Authelia to apply these changes:${NC}"
|
|
echo -e "${CYAN} docker compose restart authelia${NC}"
|
|
fi
|
|
}
|
|
|
|
##################################################
|
|
# MAIN SCRIPT
|
|
##################################################
|
|
|
|
if [ $# -gt 0 ]; then
|
|
case "$1" in
|
|
"list-auth")
|
|
list_services
|
|
exit 0
|
|
;;
|
|
"enable-auth")
|
|
if [ -z "$2" ]; then
|
|
echo -e "${RED}Error: No service specified${NC}"
|
|
echo -e "Usage: $0 enable-auth <service>"
|
|
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 <service>"
|
|
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 <service>${NC} Enable authentication for a specific service"
|
|
echo -e " ${CYAN}disable-auth <service>${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}"
|
|
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}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-7 or q to quit]: ${NC}"
|
|
read -r CHOICE
|
|
|
|
case "$CHOICE" in
|
|
1) update_env_file ;;
|
|
2) update_authelia_config ;;
|
|
3) update_service_configs ;;
|
|
4) manage_authelia_accounts ;;
|
|
5) manage_auth ;;
|
|
6) cleanup_backups ;;
|
|
7)
|
|
update_env_file
|
|
update_authelia_config
|
|
update_service_configs
|
|
manage_authelia_accounts
|
|
CHOICE="q"
|
|
;;
|
|
q|Q)
|
|
echo -e "${GREEN}Exiting setup tool.${NC}"
|
|
exit 0
|
|
;;
|
|
*) echo -e "${RED}Invalid choice. Please try again.${NC}" ;;
|
|
esac
|
|
done
|
|
|
|
echo -e "\n${GREEN}${BOLD}All updates completed!${NC}"
|
|
echo -e "${BLUE}Be sure to restart any services that were updated:${NC}"
|
|
echo -e " ${CYAN}docker compose restart${NC}"
|
|
echo -e "\n${BLUE}If you updated the Authelia configuration, restart Authelia:${NC}"
|
|
echo -e " ${CYAN}docker compose restart authelia${NC}" |