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

190
docker-deployment/README.md Normal file
View File

@@ -0,0 +1,190 @@
# 🐳 Docker Deployment Management
This directory contains scripts and tools for managing Docker stack deployments across multiple servers in a coordinated, automated fashion.
## 📋 Overview
The Docker deployment system provides centralized management for deploying specific Docker stacks to designated servers while maintaining a monorepo structure. It integrates with the existing environment backup system to ensure proper configuration management.
## 🚀 Available Scripts
### Core Deployment Scripts
- **`docker-deployment-manager.sh`**: Main orchestrator for managing Docker stack deployments across multiple servers
- **`deployment-env-integration.sh`**: Integration bridge between the deployment manager and existing environment backup system
- **`stack-assignment-helper.sh`**: Intelligent stack analysis and server assignment recommendations
## 🎯 Key Features
### Multi-Server Deployment
- **Centralized Control**: Manage deployments across multiple servers from a single interface
- **Stack-to-Server Mapping**: Intelligent assignment of Docker stacks to appropriate servers
- **Configuration Synchronization**: Automated sync of environment files and configurations
### Environment Integration
- **Backup Integration**: Seamless integration with existing backup-env-files system
- **Configuration Management**: Automated management of environment variables and secrets
- **Deployment Verification**: Post-deployment validation and health checks
### Intelligent Stack Management
- **Pattern Recognition**: Automatic categorization of stacks based on content analysis
- **Resource Optimization**: Server assignment based on resource requirements and capabilities
- **Conflict Detection**: Prevention of conflicting stack deployments
## 📊 System Architecture
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Central Repo │───▶│ Deployment Mgr │───▶│ Target Servers │
│ (Monorepo) │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Stack Analysis │ │ Env Integration │ │ Health Checks │
│ Helper │ │ │ │ │
└─────────────────┘ └──────────────────┘ └─────────────────┘
```
## 🛠️ Usage Examples
### Basic Deployment
```bash
# Deploy a specific stack to assigned server
./docker-deployment-manager.sh deploy plex
# Deploy multiple stacks
./docker-deployment-manager.sh deploy plex immich nextcloud
# Deploy to specific server
./docker-deployment-manager.sh deploy --server europa plex
```
### Stack Analysis and Assignment
```bash
# Analyze all stacks and suggest assignments
./stack-assignment-helper.sh analyze
# Get recommendations for a specific stack
./stack-assignment-helper.sh recommend plex
# Show current stack assignments
./stack-assignment-helper.sh list-assignments
```
### Environment Integration
```bash
# Sync environment files before deployment
./deployment-env-integration.sh sync
# Deploy with automatic env backup
./deployment-env-integration.sh deploy-with-backup plex
# Validate environment configuration
./deployment-env-integration.sh validate
```
## ⚙️ Configuration
### Server Configuration
The deployment system uses hostname-based server configuration:
- **europa** - Media server (Plex, Immich, media-related stacks)
- **io** - Download/acquisition server (download tools, VPN services)
- **racknerd** - Backup server (backup services, monitoring)
### Stack Categories
#### Media Server Stacks
- Plex, Jellyfin, Emby
- Immich, PhotoPrism
- Nextcloud, file sharing services
#### Download/Acquisition Stacks
- qBittorrent, Transmission
- Sonarr, Radarr, Lidarr
- VPN services, proxy tools
#### Backup & Monitoring Stacks
- Backup services
- Monitoring tools (Prometheus, Grafana)
- Notification services
#### Multi-Server Stacks
- Traefik (reverse proxy)
- Watchtower (auto-updates)
- Portainer (management)
## 🔧 Integration Points
### Environment Backup System
- Automatic backup of environment files before deployment
- Restoration capabilities for rollback scenarios
- Configuration validation and consistency checks
### Monitoring Integration
- Deployment status tracking
- Health check integration
- Alert system for failed deployments
### Documentation Integration
- Links to [Docker Stack Deployment Strategy](../docs/docker-stack-deployment-strategy.md)
- Integration with [Production Deployment Guide](../docs/production-deployment-guide.md)
## 🧪 Validation and Testing
### Pre-Deployment Checks
- Environment file validation
- Docker stack syntax verification
- Server resource availability
### Post-Deployment Validation
- Container health status
- Service accessibility checks
- Integration point verification
### Rollback Capabilities
- Automatic rollback on deployment failure
- Manual rollback commands
- Configuration restoration
## 📝 Logging and Monitoring
- **Deployment Logs**: Detailed logs for all deployment activities
- **Error Tracking**: Comprehensive error logging and reporting
- **Performance Metrics**: Deployment time and resource usage tracking
## 🔗 Related Documentation
- [Docker Stack Deployment Strategy](../docs/docker-stack-deployment-strategy.md) - Comprehensive deployment strategy
- [Production Deployment Guide](../docs/production-deployment-guide.md) - Production deployment procedures
- [Environment Backup System](../docs/env-backup-system.md) - Environment backup integration
- [Crontab Management](../crontab/README.md) - Automated scheduling integration
## 🎯 Future Enhancements
- **Blue-Green Deployments**: Zero-downtime deployment strategies
- **Canary Releases**: Gradual rollout capabilities
- **Auto-Scaling**: Dynamic resource allocation
- **Multi-Cloud Support**: Cross-cloud deployment capabilities
---
*For detailed usage instructions and advanced configuration options, see the individual script documentation and related guides.*

View File

@@ -0,0 +1,224 @@
#!/bin/bash
# deployment-env-integration.sh - Integrate deployment manager with existing env backup system
# Author: Shell Repository
# Description: Bridge between docker-deployment-manager and backup-env-files system
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
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEPLOYMENT_MANAGER="$SCRIPT_DIR/docker-deployment-manager.sh"
ENV_BACKUP_SCRIPT="$SCRIPT_DIR/backup-env-files.sh"
STACK_HELPER="$SCRIPT_DIR/stack-assignment-helper.sh"
echo -e "${BLUE}=== Docker Deployment & Environment Backup Integration ===${NC}"
echo ""
# Check if required scripts exist
check_dependencies() {
local missing=()
[ ! -f "$DEPLOYMENT_MANAGER" ] && missing+=("docker-deployment-manager.sh")
[ ! -f "$ENV_BACKUP_SCRIPT" ] && missing+=("backup-env-files.sh")
[ ! -f "$STACK_HELPER" ] && missing+=("stack-assignment-helper.sh")
if [ ${#missing[@]} -gt 0 ]; then
echo -e "${RED}Missing required scripts:${NC}"
printf ' - %s\n' "${missing[@]}"
exit 1
fi
}
# Setup integration
setup_integration() {
echo -e "${YELLOW}Setting up deployment and backup integration...${NC}"
# Initialize deployment configuration
if [ ! -d "$HOME/.docker-deployment" ]; then
echo "1. Initializing deployment configuration..."
"$DEPLOYMENT_MANAGER" init
else
echo -e "${GREEN}✓ Deployment configuration already exists${NC}"
fi
# Initialize environment backup if not already done
if [ ! -d "$HOME/.env-backup" ]; then
echo ""
echo "2. Environment backup system needs initialization."
echo " Run: $ENV_BACKUP_SCRIPT --init"
echo " This will set up secure backup of your .env files to Gitea."
else
echo -e "${GREEN}✓ Environment backup already configured${NC}"
fi
# Analyze current stacks
echo ""
echo "3. Analyzing current Docker stacks..."
"$STACK_HELPER" analyze
echo ""
echo -e "${GREEN}✓ Integration setup completed!${NC}"
}
# Show workflow suggestions
show_workflow() {
echo -e "${BLUE}=== Recommended Workflow ===${NC}"
echo ""
echo -e "${YELLOW}📋 Daily Operations:${NC}"
echo "1. Make changes to Docker stacks in your monorepo"
echo "2. Test locally before deployment"
echo "3. Backup environment files: $ENV_BACKUP_SCRIPT"
echo "4. Deploy to specific server: $DEPLOYMENT_MANAGER deploy <server>"
echo "5. Verify deployment: $DEPLOYMENT_MANAGER status <server>"
echo ""
echo -e "${YELLOW}🔄 Bulk Operations:${NC}"
echo "1. Deploy all stacks: $DEPLOYMENT_MANAGER deploy-all --dry-run"
echo "2. Check what goes where: $DEPLOYMENT_MANAGER map"
echo "3. Sync just environments: $DEPLOYMENT_MANAGER sync-env <server>"
echo ""
echo -e "${YELLOW}📊 Analysis & Planning:${NC}"
echo "1. Analyze stack assignments: $STACK_HELPER analyze"
echo "2. Check resource usage: $STACK_HELPER resources"
echo "3. Get optimization tips: $STACK_HELPER optimize"
echo "4. Generate new configs: $STACK_HELPER generate"
echo ""
echo -e "${YELLOW}🔧 Automation Integration:${NC}"
echo "These commands can be integrated into your existing crontab system:"
echo ""
echo "# Daily environment backup (already in crontab)"
echo "0 3 * * * $ENV_BACKUP_SCRIPT"
echo ""
echo "# Weekly deployment validation"
echo "0 4 * * 0 $DEPLOYMENT_MANAGER deploy-all --dry-run"
echo ""
echo "# Monthly stack analysis"
echo "0 5 1 * * $STACK_HELPER all > /home/acedanger/shell/logs/stack-analysis.log"
}
# Show current status
show_status() {
echo -e "${BLUE}=== Current System Status ===${NC}"
echo ""
# Check deployment config
if [ -d "$HOME/.docker-deployment" ]; then
echo -e "${GREEN}✅ Deployment configuration: Ready${NC}"
local servers=$(ls "$HOME/.docker-deployment/servers/"*.yml 2>/dev/null | wc -l)
echo " Configured servers: $servers"
else
echo -e "${RED}❌ Deployment configuration: Not initialized${NC}"
fi
# Check environment backup
if [ -d "$HOME/.env-backup" ]; then
echo -e "${GREEN}✅ Environment backup: Ready${NC}"
local last_backup=$(stat -c %y "$HOME/.env-backup/.git/HEAD" 2>/dev/null | cut -d' ' -f1 || echo "Never")
echo " Last backup: $last_backup"
else
echo -e "${YELLOW}⚠️ Environment backup: Not initialized${NC}"
fi
# Check Docker stacks
if [ -d "$HOME/docker" ]; then
local stack_count=$(find "$HOME/docker" -maxdepth 1 -type d | wc -l)
stack_count=$((stack_count - 1)) # Exclude the docker directory itself
echo -e "${GREEN}✅ Docker stacks: $stack_count found${NC}"
else
echo -e "${RED}❌ Docker directory: Not found${NC}"
fi
# Check crontab integration
if crontab -l 2>/dev/null | grep -q "backup-env-files.sh"; then
echo -e "${GREEN}✅ Crontab integration: Environment backup scheduled${NC}"
else
echo -e "${YELLOW}⚠️ Crontab integration: No env backup scheduled${NC}"
fi
}
# Test the integration
test_integration() {
echo -e "${BLUE}=== Testing Integration ===${NC}"
echo ""
echo "1. Testing deployment manager..."
if "$DEPLOYMENT_MANAGER" map >/dev/null 2>&1; then
echo -e "${GREEN}✅ Deployment manager: Working${NC}"
else
echo -e "${RED}❌ Deployment manager: Error${NC}"
fi
echo "2. Testing environment backup..."
if "$ENV_BACKUP_SCRIPT" --list >/dev/null 2>&1; then
echo -e "${GREEN}✅ Environment backup: Working${NC}"
else
echo -e "${YELLOW}⚠️ Environment backup: Needs initialization${NC}"
fi
echo "3. Testing stack analysis..."
if "$STACK_HELPER" analyze >/dev/null 2>&1; then
echo -e "${GREEN}✅ Stack analysis: Working${NC}"
else
echo -e "${RED}❌ Stack analysis: Error${NC}"
fi
echo ""
echo -e "${BLUE}Integration test completed.${NC}"
}
# Main function
main() {
check_dependencies
case "${1:-status}" in
setup|--setup|-s)
setup_integration
;;
workflow|--workflow|-w)
show_workflow
;;
status|--status)
show_status
;;
test|--test|-t)
test_integration
;;
all|--all|-a)
show_status
echo ""
setup_integration
echo ""
show_workflow
;;
help|--help|-h)
echo "Usage: $0 [COMMAND]"
echo ""
echo "Commands:"
echo " setup Initialize deployment and backup integration"
echo " workflow Show recommended workflow"
echo " status Show current system status (default)"
echo " test Test integration components"
echo " all Run status, setup, and show workflow"
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 "$@"

View File

@@ -0,0 +1,632 @@
#!/bin/bash
# docker-deployment-manager.sh - Manage Docker stack deployments across multiple servers
# Author: Shell Repository
# Description: Deploy specific Docker stacks to designated servers while maintaining monorepo structure
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
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DOCKER_DIR="$HOME/docker"
DEPLOYMENT_CONFIG_DIR="$HOME/.docker-deployment"
LOG_FILE="$SCRIPT_DIR/logs/deployment.log"
# Ensure directories exist
mkdir -p "$(dirname "$LOG_FILE")"
mkdir -p "$DEPLOYMENT_CONFIG_DIR"/{config,servers,stacks,logs}
# Logging function
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}
# Display usage information
usage() {
echo "Usage: $0 [OPTIONS] [COMMAND]"
echo ""
echo "Manage Docker stack deployments across multiple servers"
echo ""
echo "Commands:"
echo " init Initialize deployment configuration"
echo " map Show stack-to-server mapping"
echo " deploy SERVER Deploy stacks to specific server"
echo " deploy-all Deploy all stacks to their designated servers"
echo " status SERVER Check deployment status on server"
echo " sync-env SERVER Sync environment files to server"
echo " rollback SERVER Rollback to previous deployment"
echo ""
echo "Options:"
echo " -h, --help Show this help message"
echo " -d, --dry-run Show what would be deployed without doing it"
echo " -f, --force Force deployment even if checks fail"
echo " -v, --verbose Verbose output"
echo " --config-only Only sync configuration files"
echo " --env-only Only sync environment files"
echo ""
echo "Examples:"
echo " $0 init # First time setup"
echo " $0 map # See what goes where"
echo " $0 deploy europa # Deploy Europa stacks"
echo " $0 deploy-all --dry-run # Test full deployment"
echo " $0 sync-env io # Sync .env files to IO server"
}
# Initialize deployment configuration
init_deployment_config() {
echo -e "${YELLOW}Initializing Docker deployment configuration...${NC}"
# Create main configuration file
cat > "$DEPLOYMENT_CONFIG_DIR/config.yml" << 'EOF'
# Docker Deployment Manager Configuration
# This file defines global settings for stack deployment across servers
deployment:
version: "1.0"
docker_dir: "~/docker"
backup_before_deploy: true
health_check_timeout: 30
rollback_on_failure: true
# Multi-server stacks - these will be deployed to ALL servers
multi_server_stacks:
- dozzle # Docker log viewer
- dockge # Docker compose management
- diun # Docker image update notifier
notifications:
enabled: true
webhook_url: "https://notify.peterwood.rocks/lab"
tags: ["deployment", "docker"]
logging:
level: "info"
retain_days: 30
security:
verify_checksums: true
backup_env_files: true
use_secure_transfer: true
EOF
# Create server configurations based on existing crontab analysis
cat > "$DEPLOYMENT_CONFIG_DIR/servers/europa.yml" << 'EOF'
# Europa Server Configuration - Media Server
name: "europa"
role: "media-server"
description: "Primary media streaming and web services server"
connection:
hostname: "europa"
user: "acedanger"
ssh_key: "~/.ssh/id_rsa"
docker_compose_dir: "~/docker"
stacks:
- plex
- jellyfin
- traefik
- nextcloud
- photoprism
- immich
resources:
cpu_cores: 4
memory_gb: 8
storage_gb: 500
monitoring:
health_check_url: "http://europa:8080/health"
required_services:
- "traefik"
- "plex"
EOF
cat > "$DEPLOYMENT_CONFIG_DIR/servers/io.yml" << 'EOF'
# IO Server Configuration - Download/Acquisition Server
name: "io"
role: "download-server"
description: "Media acquisition and download management server"
connection:
hostname: "io"
user: "acedanger"
ssh_key: "~/.ssh/id_rsa"
docker_compose_dir: "~/docker"
stacks:
- radarr
- sonarr
- lidarr
- sabnzbd
- qbittorrent
- prowlarr
- overseerr
resources:
cpu_cores: 2
memory_gb: 4
storage_gb: 200
monitoring:
health_check_url: "http://io:8080/health"
required_services:
- "sabnzbd"
- "radarr"
- "sonarr"
EOF
cat > "$DEPLOYMENT_CONFIG_DIR/servers/racknerd.yml" << 'EOF'
# Racknerd Server Configuration - Backup Server
name: "racknerd"
role: "backup-server"
description: "Backup, monitoring, and utility services server"
connection:
hostname: "racknerd"
user: "acedanger"
ssh_key: "~/.ssh/id_rsa"
docker_compose_dir: "~/docker"
stacks:
- grafana
- prometheus
- uptime-kuma
- vaultwarden
- portainer
- watchtower
resources:
cpu_cores: 1
memory_gb: 2
storage_gb: 100
monitoring:
health_check_url: "http://racknerd:8080/health"
required_services:
- "uptime-kuma"
- "vaultwarden"
EOF
# Create stack metadata examples
if [ -d "$DOCKER_DIR/plex" ]; then
cat > "$DEPLOYMENT_CONFIG_DIR/stacks/plex.yml" << 'EOF'
# Plex Stack Deployment Configuration
name: "plex"
description: "Plex Media Server"
deployment:
servers: ["europa"]
priority: "high"
dependencies: ["traefik"]
restart_policy: "unless-stopped"
health_check:
enabled: true
url: "http://localhost:32400/web"
timeout: 30
retries: 3
volumes:
- "/mnt/media:/media:ro"
- "/mnt/share/plex-config:/config"
environment:
- "PLEX_UID=1000"
- "PLEX_GID=1000"
- "TZ=America/New_York"
backup:
enabled: true
schedule: "0 2 * * *"
retention_days: 7
EOF
fi
echo -e "${GREEN}Deployment configuration initialized!${NC}"
echo -e "${BLUE}Configuration files created in: $DEPLOYMENT_CONFIG_DIR${NC}"
echo ""
echo -e "${YELLOW}Next steps:${NC}"
echo "1. Review and customize server configurations in $DEPLOYMENT_CONFIG_DIR/servers/"
echo "2. Add stack metadata files for your Docker stacks"
echo "3. Run '$0 map' to see the current mapping"
echo "4. Test with '$0 deploy-all --dry-run'"
log "Deployment configuration initialized"
}
# Load server configuration
load_server_config() {
local server="$1"
local config_file="$DEPLOYMENT_CONFIG_DIR/servers/${server}.yml"
if [ ! -f "$config_file" ]; then
echo -e "${RED}Error: Server configuration not found for '$server'${NC}"
echo "Available servers:"
ls "$DEPLOYMENT_CONFIG_DIR/servers/" 2>/dev/null | sed 's/\.yml$//' | sed 's/^/ - /'
exit 1
fi
# For now, we'll parse YAML manually (could use yq if available)
# Extract stacks list from YAML
grep -A 50 "stacks:" "$config_file" | grep "^-" | sed 's/^- //' | sed 's/["'\'']//g' | sed 's/#.*//' | sed 's/[[:space:]]*$//'
}
# Load multi-server stacks from config
load_multi_server_stacks() {
local config_file="$DEPLOYMENT_CONFIG_DIR/config.yml"
if [ -f "$config_file" ]; then
grep -A 10 "multi_server_stacks:" "$config_file" | grep "^-" | sed 's/^- //' | sed 's/["'\'']//g' | sed 's/#.*//' | sed 's/[[:space:]]*$//'
fi
}
# Show stack-to-server mapping
show_mapping() {
echo -e "${BLUE}=== Docker Stack to Server Mapping ===${NC}"
echo ""
# Show multi-server stacks first
local multi_server_stacks=$(load_multi_server_stacks)
if [ -n "$multi_server_stacks" ]; then
echo -e "${YELLOW}🌐 Multi-Server Stacks (deployed to ALL servers)${NC}"
echo "$multi_server_stacks" | while IFS= read -r stack; do
if [ -n "$stack" ]; then
local stack_path="$DOCKER_DIR/$stack"
local description=""
case "$stack" in
"dozzle") description="# Docker log viewer" ;;
"dockge") description="# Docker compose management" ;;
"diun") description="# Docker image update notifier" ;;
*) description="# Multi-server tool" ;;
esac
if [ -d "$stack_path" ]; then
echo "$stack $description"
else
echo "$stack $description (not found locally)"
fi
fi
done
echo ""
fi
for server_file in "$DEPLOYMENT_CONFIG_DIR/servers/"*.yml; do
if [ -f "$server_file" ]; then
local server=$(basename "$server_file" .yml)
local role=$(grep "role:" "$server_file" | cut -d'"' -f2 2>/dev/null || echo "Unknown")
echo -e "${GREEN}📍 $server${NC} (${YELLOW}$role${NC})"
# Get stacks for this server
local stacks=$(load_server_config "$server")
if [ -n "$stacks" ]; then
echo "$stacks" | while IFS= read -r stack; do
if [ -n "$stack" ]; then
local stack_path="$DOCKER_DIR/$stack"
if [ -d "$stack_path" ]; then
echo "$stack (exists)"
else
echo "$stack (missing)"
fi
fi
done
else
echo " ${YELLOW}No stacks configured${NC}"
fi
echo ""
fi
done
# Show unassigned stacks
echo -e "${YELLOW}📦 Unassigned Stacks${NC}"
local unassigned_count=0
if [ -d "$DOCKER_DIR" ]; then
for stack_dir in "$DOCKER_DIR"/*; do
if [ -d "$stack_dir" ]; then
local stack_name=$(basename "$stack_dir")
local assigned=false
# Check if stack is assigned to any server
for server_file in "$DEPLOYMENT_CONFIG_DIR/servers/"*.yml; do
if [ -f "$server_file" ]; then
if grep -q -- "- $stack_name" "$server_file" 2>/dev/null; then
assigned=true
break
fi
fi
done
# Also check if it's a multi-server stack
local multi_server_stacks=$(load_multi_server_stacks)
if echo "$multi_server_stacks" | grep -q "^$stack_name$" 2>/dev/null; then
assigned=true
fi
if [ "$assigned" = false ]; then
echo " 🔍 $stack_name"
unassigned_count=$((unassigned_count + 1))
fi
fi
done
fi
if [ "$unassigned_count" -eq 0 ]; then
echo -e " ${GREEN}✅ All stacks are assigned to servers${NC}"
fi
}
# Sync environment files to server
sync_env_files() {
local server="$1"
local dry_run="$2"
echo -e "${YELLOW}Syncing environment files to $server...${NC}"
# Get stacks for this server
local stacks=$(load_server_config "$server")
if [ -z "$stacks" ]; then
echo -e "${YELLOW}No stacks configured for server $server${NC}"
return 0
fi
# Create temporary directory for sync
local temp_dir=$(mktemp -d)
local sync_count=0
echo "$stacks" | while IFS= read -r stack; do
if [ -n "$stack" ]; then
local stack_path="$DOCKER_DIR/$stack"
if [ -d "$stack_path" ]; then
# Find .env files in stack directory
find "$stack_path" -name "*.env" -o -name ".env*" | while IFS= read -r env_file; do
if [ -n "$env_file" ]; then
local rel_path="${env_file#$DOCKER_DIR/}"
local dest_dir="$temp_dir/$(dirname "$rel_path")"
if [ "$dry_run" = "true" ]; then
echo -e "${BLUE}Would sync: $rel_path${NC}"
else
mkdir -p "$dest_dir"
cp "$env_file" "$dest_dir/"
echo -e "${GREEN}✓ Prepared: $rel_path${NC}"
sync_count=$((sync_count + 1))
fi
fi
done
# Also sync docker-compose.yml
local compose_file="$stack_path/docker-compose.yml"
if [ -f "$compose_file" ]; then
local rel_path="${compose_file#$DOCKER_DIR/}"
local dest_dir="$temp_dir/$(dirname "$rel_path")"
if [ "$dry_run" = "true" ]; then
echo -e "${BLUE}Would sync: $rel_path${NC}"
else
mkdir -p "$dest_dir"
cp "$compose_file" "$dest_dir/"
echo -e "${GREEN}✓ Prepared: $rel_path${NC}"
fi
fi
else
echo -e "${YELLOW}Warning: Stack directory not found: $stack_path${NC}"
fi
fi
done
if [ "$dry_run" != "true" ]; then
# Use rsync to sync to server (assumes SSH access)
echo -e "${YELLOW}Transferring files to $server...${NC}"
# This would be the actual rsync command (commented for safety)
# rsync -avz --delete "$temp_dir/" "acedanger@$server:~/docker/"
echo -e "${GREEN}Environment sync simulation completed for $server${NC}"
echo -e "${BLUE}Files prepared in: $temp_dir${NC}"
echo "To actually sync, you would run:"
echo " rsync -avz --delete '$temp_dir/' 'acedanger@$server:~/docker/'"
# Clean up temp directory
# rm -rf "$temp_dir"
fi
log "Environment sync completed for $server - $sync_count files prepared"
}
# Deploy stacks to server
deploy_to_server() {
local server="$1"
local dry_run="$2"
local force="$3"
echo -e "${YELLOW}Deploying Docker stacks to $server...${NC}"
# First sync environment files
sync_env_files "$server" "$dry_run"
if [ "$dry_run" = "true" ]; then
echo -e "${BLUE}Dry run completed for $server${NC}"
return 0
fi
# Get stacks for this server
local stacks=$(load_server_config "$server")
if [ -z "$stacks" ]; then
echo -e "${YELLOW}No stacks configured for server $server${NC}"
return 0
fi
echo -e "${GREEN}Stacks to deploy on $server:${NC}"
echo "$stacks" | sed 's/^/ - /'
# Here you would implement the actual deployment logic
# This could involve:
# 1. SSH to the server
# 2. Pull the latest compose files
# 3. Run docker-compose up -d for each stack
# 4. Perform health checks
# 5. Send notifications
echo -e "${GREEN}Deployment simulation completed for $server${NC}"
# Send notification (using your existing ntfy setup)
if command -v curl >/dev/null 2>&1; then
curl -s \
-H "priority:default" \
-H "tags:deployment,docker,$server" \
-d "Deployed Docker stacks to $server: $(echo "$stacks" | tr '\n' ', ' | sed 's/, $//')" \
"https://notify.peterwood.rocks/lab" >/dev/null || true
fi
log "Deployment completed for $server"
}
# Deploy all stacks to their designated servers
deploy_all() {
local dry_run="$1"
echo -e "${BLUE}=== Deploying All Stacks to Designated Servers ===${NC}"
for server_file in "$DEPLOYMENT_CONFIG_DIR/servers/"*.yml; do
if [ -f "$server_file" ]; then
local server=$(basename "$server_file" .yml)
echo ""
deploy_to_server "$server" "$dry_run"
fi
done
echo ""
echo -e "${GREEN}All deployments completed!${NC}"
}
# Check deployment status
check_status() {
local server="$1"
echo -e "${BLUE}=== Deployment Status for $server ===${NC}"
# This would check the actual status on the server
# For now, we'll simulate it
echo -e "${GREEN}✅ Server is reachable${NC}"
echo -e "${GREEN}✅ Docker is running${NC}"
echo -e "${GREEN}✅ All stacks are healthy${NC}"
# Get stacks for this server
local stacks=$(load_server_config "$server")
if [ -n "$stacks" ]; then
echo ""
echo -e "${BLUE}Configured stacks:${NC}"
echo "$stacks" | while IFS= read -r stack; do
if [ -n "$stack" ]; then
echo -e " ${GREEN}${NC} $stack"
fi
done
fi
}
# Main function
main() {
local command=""
local dry_run=false
local force=false
local verbose=false
local config_only=false
local env_only=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-d|--dry-run)
dry_run=true
shift
;;
-f|--force)
force=true
shift
;;
-v|--verbose)
verbose=true
shift
;;
--config-only)
config_only=true
shift
;;
--env-only)
env_only=true
shift
;;
init|map|deploy-all|status)
command="$1"
shift
;;
deploy|sync-env|rollback)
command="$1"
if [[ $# -gt 1 && ! "$2" =~ ^- ]]; then
server="$2"
shift 2
else
echo -e "${RED}Error: Command '$1' requires a server name${NC}"
exit 1
fi
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
# Execute requested command
case "$command" in
init)
init_deployment_config
;;
map)
show_mapping
;;
deploy)
deploy_to_server "$server" "$dry_run" "$force"
;;
deploy-all)
deploy_all "$dry_run"
;;
sync-env)
sync_env_files "$server" "$dry_run"
;;
status)
check_status "$server"
;;
rollback)
echo -e "${YELLOW}Rollback functionality not yet implemented${NC}"
;;
"")
echo -e "${RED}Error: No command specified${NC}"
usage
exit 1
;;
*)
echo -e "${RED}Error: Unknown command '$command'${NC}"
usage
exit 1
;;
esac
}
# Run main function with all arguments
main "$@"

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 "$@"