diff --git a/update-setup.sh b/update-setup.sh index f6fd93b..1ecfc80 100755 --- a/update-setup.sh +++ b/update-setup.sh @@ -31,6 +31,16 @@ 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}" @@ -368,14 +378,31 @@ generate_passphrase() { # PART 4: Authentication Management ################################################## +# Get the current auth status for a service 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" + + # 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 - echo "unknown" + # 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 } @@ -383,6 +410,66 @@ 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 @@ -443,6 +530,63 @@ 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 @@ -473,39 +617,91 @@ disable_auth() { 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 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 + # 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) - local status=$(get_auth_status "$service") + 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}') - 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 + 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 }