feat: Add Docker deployment manager and stack assignment helper scripts

- Introduced `docker-deployment-manager.sh` for managing Docker stack deployments across multiple servers, including initialization, deployment, and status checks.
- Added `stack-assignment-helper.sh` to analyze Docker stacks and suggest server assignments based on predefined patterns.
- Removed outdated `SETUP_COMPLETE.md` file as it is no longer relevant to the current setup process.
- Ref - Documentation review #11
This commit is contained in:
Peter Wood
2025-05-29 18:19:37 -04:00
parent a05f5c6d9d
commit 3ce2b687ac
7 changed files with 469 additions and 116 deletions

View File

@@ -0,0 +1,467 @@
#!/bin/bash
# stack-assignment-helper.sh - Helper script to analyze and assign Docker stacks to servers
# Author: Shell Repository
# Description: Analyze Docker stacks and suggest server assignments based on patterns
set -e
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Configuration
DOCKER_DIR="$HOME/docker"
DEPLOYMENT_CONFIG_DIR="$HOME/.docker-deployment"
# Stack classification patterns
declare -A MEDIA_PATTERNS=(
["plex"]="media-server"
["jellyfin"]="media-server"
["emby"]="media-server"
["kodi"]="media-server"
["photoprism"]="media-server"
["immich"]="media-server"
["nextcloud"]="media-server"
)
# Multi-server patterns - stacks that should run on ALL servers
declare -A MULTI_SERVER_PATTERNS=(
["dozzle"]="monitoring"
["dockge"]="management"
["diun"]="monitoring"
["watchtower"]="monitoring"
["portainer"]="management"
)
declare -A DOWNLOAD_PATTERNS=(
["radarr"]="download-server"
["sonarr"]="download-server"
["lidarr"]="download-server"
["readarr"]="download-server"
["prowlarr"]="download-server"
["sabnzbd"]="download-server"
["nzbget"]="download-server"
["qbittorrent"]="download-server"
["transmission"]="download-server"
["deluge"]="download-server"
["overseerr"]="download-server"
["ombi"]="download-server"
["requestrr"]="download-server"
["jackett"]="download-server"
["metube"]="download-server"
["pinchflat"]="download-server"
["pdf"]="download-server"
)
declare -A UTILITY_PATTERNS=(
["traefik"]="reverse-proxy"
["nginx"]="reverse-proxy"
["caddy"]="reverse-proxy"
["nginxproxymanager"]="reverse-proxy"
["grafana"]="monitoring"
["prometheus"]="monitoring"
["uptime-kuma"]="monitoring"
["gatus"]="monitoring"
["ntfy"]="monitoring"
["adguard"]="security"
["portainer"]="management"
["watchtower"]="management"
["vaultwarden"]="security"
["authelia"]="security"
["authentik"]="security"
["duplicati"]="backup"
["restic"]="backup"
["database"]="infrastructure"
["cloudflare"]="infrastructure"
["tclip"]="utility"
["opengist"]="utility"
["newt"]="utility"
["pangolin"]="utility"
["omni-tools"]="utility"
["golinks"]="utility"
["hoarder"]="productivity"
["paperless-ng"]="productivity"
["docmost"]="productivity"
["wiki"]="productivity"
["filebrowser"]="productivity"
["n8n"]="automation"
)
# Analyze Docker directory structure
analyze_stacks() {
echo -e "${BLUE}=== Analyzing Docker Stacks ===${NC}"
echo ""
if [ ! -d "$DOCKER_DIR" ]; then
echo -e "${RED}Docker directory not found: $DOCKER_DIR${NC}"
exit 1
fi
local total_stacks=0
local classified_stacks=0
# Arrays to store classifications
local media_stacks=()
local download_stacks=()
local utility_stacks=()
local unclassified_stacks=()
# Analyze each directory in docker folder
for stack_dir in "$DOCKER_DIR"/*; do
if [ -d "$stack_dir" ]; then
local stack_name=$(basename "$stack_dir")
total_stacks=$((total_stacks + 1))
local classification=""
local suggested_server=""
# Check against patterns - prioritize multi-server patterns first
if [[ -n "${MULTI_SERVER_PATTERNS[$stack_name]}" ]]; then
classification="Multi-Server Tool"
suggested_server="all-servers"
utility_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
elif [[ -n "${MEDIA_PATTERNS[$stack_name]}" ]]; then
classification="Media Server"
suggested_server="europa"
media_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
elif [[ -n "${DOWNLOAD_PATTERNS[$stack_name]}" ]]; then
classification="Download/Acquisition"
suggested_server="io"
download_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
elif [[ -n "${UTILITY_PATTERNS[$stack_name]}" ]]; then
classification="Utility/Monitoring"
case "${UTILITY_PATTERNS[$stack_name]}" in
"reverse-proxy"|"productivity")
suggested_server="europa"
;;
"monitoring"|"backup"|"security"|"infrastructure")
suggested_server="racknerd"
;;
"automation"|"utility")
suggested_server="io"
;;
*)
suggested_server="racknerd"
;;
esac
utility_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
else
# Try to classify based on docker-compose.yml content
local compose_file="$stack_dir/docker-compose.yml"
if [ -f "$compose_file" ]; then
if grep -qi "plex\|jellyfin\|emby\|photoprism\|immich" "$compose_file"; then
classification="Media Server (detected)"
suggested_server="europa"
media_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
elif grep -qi "radarr\|sonarr\|sabnzbd\|qbittorrent\|transmission" "$compose_file"; then
classification="Download/Acquisition (detected)"
suggested_server="io"
download_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
elif grep -qi "grafana\|prometheus\|monitoring\|uptime" "$compose_file"; then
classification="Monitoring (detected)"
suggested_server="racknerd"
utility_stacks+=("$stack_name")
classified_stacks=$((classified_stacks + 1))
else
classification="Unknown"
suggested_server="manual-review"
unclassified_stacks+=("$stack_name")
fi
else
classification="No compose file"
suggested_server="manual-review"
unclassified_stacks+=("$stack_name")
fi
fi
# Display analysis
local status_icon=""
local server_color=""
case "$suggested_server" in
"europa")
status_icon="🎬"
server_color="$GREEN"
;;
"io")
status_icon="📥"
server_color="$BLUE"
;;
"racknerd")
status_icon="🔧"
server_color="$YELLOW"
;;
"all")
status_icon="🌐"
server_color="$GREEN"
;;
*)
status_icon="❓"
server_color="$RED"
;;
esac
printf "%-20s %-25s ${server_color}%-10s${NC} %s\n" \
"$status_icon $stack_name" \
"$classification" \
"$suggested_server" \
"$(check_stack_health "$stack_dir")"
fi
done
echo ""
echo -e "${BLUE}=== Classification Summary ===${NC}"
echo "Total stacks found: $total_stacks"
echo "Classified stacks: $classified_stacks"
echo "Unclassified stacks: $((total_stacks - classified_stacks))"
echo ""
# Show server assignments
if [ ${#media_stacks[@]} -gt 0 ]; then
echo -e "${GREEN}🎬 Europa (Media Server):${NC}"
printf '%s\n' "${media_stacks[@]}" | sed 's/^/ - /'
echo ""
fi
if [ ${#download_stacks[@]} -gt 0 ]; then
echo -e "${BLUE}📥 IO (Download Server):${NC}"
printf '%s\n' "${download_stacks[@]}" | sed 's/^/ - /'
echo ""
fi
if [ ${#utility_stacks[@]} -gt 0 ]; then
echo -e "${YELLOW}🔧 Racknerd (Utility/Backup):${NC}"
printf '%s\n' "${utility_stacks[@]}" | sed 's/^/ - /'
echo ""
fi
if [ ${#unclassified_stacks[@]} -gt 0 ]; then
echo -e "${RED}❓ Manual Review Required:${NC}"
printf '%s\n' "${unclassified_stacks[@]}" | sed 's/^/ - /'
echo ""
fi
}
# Check stack health indicators
check_stack_health() {
local stack_dir="$1"
local health_indicators=()
# Check for docker-compose.yml
if [ -f "$stack_dir/docker-compose.yml" ]; then
health_indicators+=("compose")
fi
# Check for .env files
if find "$stack_dir" -name "*.env" -o -name ".env*" | grep -q .; then
health_indicators+=("env")
fi
# Check for data/config directories
if [ -d "$stack_dir/data" ] || [ -d "$stack_dir/config" ]; then
health_indicators+=("data")
fi
# Format output
if [ ${#health_indicators[@]} -eq 0 ]; then
echo "⚠️ Incomplete"
else
echo "$(IFS=,; echo "${health_indicators[*]}")"
fi
}
# Generate deployment configuration
generate_deployment_config() {
echo -e "${YELLOW}Generating deployment configuration...${NC}"
# Ensure deployment config directory exists
mkdir -p "$DEPLOYMENT_CONFIG_DIR/stacks"
# Create individual stack configurations
for stack_dir in "$DOCKER_DIR"/*; do
if [ -d "$stack_dir" ]; then
local stack_name=$(basename "$stack_dir")
local config_file="$DEPLOYMENT_CONFIG_DIR/stacks/${stack_name}.yml"
# Determine server assignment
local server=""
if [[ -n "${MEDIA_PATTERNS[$stack_name]}" ]]; then
server="europa"
elif [[ -n "${DOWNLOAD_PATTERNS[$stack_name]}" ]]; then
server="io"
elif [[ -n "${UTILITY_PATTERNS[$stack_name]}" ]]; then
case "${UTILITY_PATTERNS[$stack_name]}" in
"reverse-proxy"|"management")
server="europa"
;;
*)
server="racknerd"
;;
esac
elif [[ -n "${MULTI_SERVER_PATTERNS[$stack_name]}" ]]; then
server="all"
else
server="manual-assignment-required"
fi
# Generate stack configuration
cat > "$config_file" << EOF
# Auto-generated stack configuration for $stack_name
name: "$stack_name"
description: "Docker stack for $stack_name"
deployment:
servers: ["$server"]
priority: "medium"
dependencies: []
restart_policy: "unless-stopped"
health_check:
enabled: false
timeout: 30
retries: 3
backup:
enabled: true
schedule: "0 2 * * *"
retention_days: 7
# Review and customize this configuration as needed
EOF
echo -e "${GREEN}✓ Generated: $stack_name.yml${NC}"
fi
done
echo ""
echo -e "${BLUE}Configuration files generated in: $DEPLOYMENT_CONFIG_DIR/stacks/${NC}"
echo -e "${YELLOW}Please review and customize the generated configurations before deployment.${NC}"
}
# Show resource usage estimates
show_resource_estimates() {
echo -e "${BLUE}=== Resource Usage Estimates ===${NC}"
echo ""
# Define resource estimates for common stacks
declare -A CPU_ESTIMATES=(
["plex"]="2-4"
["jellyfin"]="1-3"
["radarr"]="0.5-1"
["sonarr"]="0.5-1"
["sabnzbd"]="1-2"
["qbittorrent"]="0.5-2"
["grafana"]="0.5-1"
["prometheus"]="1-2"
["traefik"]="0.5-1"
["nextcloud"]="1-2"
)
declare -A MEMORY_ESTIMATES=(
["plex"]="2-4GB"
["jellyfin"]="1-2GB"
["radarr"]="256-512MB"
["sonarr"]="256-512MB"
["sabnzbd"]="512MB-1GB"
["qbittorrent"]="256-512MB"
["grafana"]="256-512MB"
["prometheus"]="512MB-1GB"
["traefik"]="128-256MB"
["nextcloud"]="512MB-1GB"
)
# Aggregate by server
local europa_cpu=0
local io_cpu=0
local racknerd_cpu=0
for stack_dir in "$DOCKER_DIR"/*; do
if [ -d "$stack_dir" ]; then
local stack_name=$(basename "$stack_dir")
local cpu_est="${CPU_ESTIMATES[$stack_name]:-0.5}"
local mem_est="${MEMORY_ESTIMATES[$stack_name]:-256MB}"
# Extract numeric part for aggregation (use lower bound)
local cpu_num=$(echo "$cpu_est" | cut -d'-' -f1)
printf "%-20s CPU: %-8s Memory: %-10s\n" "$stack_name" "$cpu_est cores" "$mem_est"
fi
done
echo ""
echo -e "${YELLOW}Note: These are rough estimates. Actual usage depends on workload and configuration.${NC}"
}
# Suggest optimization opportunities
suggest_optimizations() {
echo -e "${BLUE}=== Optimization Suggestions ===${NC}"
echo ""
echo -e "${GREEN}💡 Recommendations:${NC}"
echo "1. Co-locate Traefik with media services (Europa) for efficient reverse proxy"
echo "2. Keep download services (IO) on fast storage for temporary files"
echo "3. Place monitoring/backup services (Racknerd) on cost-effective hardware"
echo "4. Consider resource limits in docker-compose.yml files"
echo "5. Use shared volumes for media access across containers"
echo ""
echo -e "${YELLOW}🔧 Integration Points:${NC}"
echo "1. Integrate with existing backup-env-files.sh for environment management"
echo "2. Use existing crontab system for deployment automation"
echo "3. Leverage ntfy notifications for deployment status"
echo "4. Consider using existing logging infrastructure"
echo ""
}
# Main function
main() {
case "${1:-analyze}" in
analyze|--analyze|-a)
analyze_stacks
;;
generate|--generate|-g)
generate_deployment_config
;;
resources|--resources|-r)
show_resource_estimates
;;
optimize|--optimize|-o)
suggest_optimizations
;;
all|--all)
analyze_stacks
echo ""
show_resource_estimates
echo ""
suggest_optimizations
;;
help|--help|-h)
echo "Usage: $0 [COMMAND]"
echo ""
echo "Commands:"
echo " analyze Analyze and classify Docker stacks (default)"
echo " generate Generate deployment configuration files"
echo " resources Show resource usage estimates"
echo " optimize Show optimization suggestions"
echo " all Run all analysis commands"
echo " help Show this help message"
;;
*)
echo -e "${RED}Unknown command: $1${NC}"
echo "Use '$0 help' for usage information"
exit 1
;;
esac
}
# Run main function
main "$@"