- Add replace_env_vars() function to automatically scan and replace patterns - Function checks all config files (.yml, .yaml, .conf, .json) for variables - Replaces variables found in .env file with their values - Warns about variables referenced in templates but missing from .env - Applied to both core deployment and stack setup for Dockge - Maintains backward compatibility with existing hardcoded replacements - Makes deployment script maintenance-free for new services
466 lines
16 KiB
Bash
Executable File
466 lines
16 KiB
Bash
Executable File
#!/bin/bash
|
|
# EZ-Homelab Deployment Script
|
|
# This script deploys homelab services with flexible options
|
|
# Run after: 1) setup-homelab.sh and 2) editing .env file
|
|
# Run as: ./deploy-homelab.sh
|
|
|
|
set -e # Exit on error
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Log functions
|
|
log_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
log_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
log_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Check if running as root
|
|
if [ "$EUID" -eq 0 ]; then
|
|
log_error "Please do NOT run this script as root or with sudo"
|
|
log_info "Run as: ./deploy-homelab.sh"
|
|
exit 1
|
|
fi
|
|
|
|
# Get script directory (EZ-Homelab/scripts)
|
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )"
|
|
|
|
log_info "EZ-Homelab Deployment Script"
|
|
echo ""
|
|
|
|
# Check if .env file exists
|
|
if [ ! -f "$REPO_DIR/.env" ]; then
|
|
log_error ".env file not found!"
|
|
log_info "Please create and configure your .env file first:"
|
|
echo " cd $REPO_DIR"
|
|
echo " cp .env.example .env"
|
|
echo " nano .env"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if Docker is installed and running
|
|
if ! command -v docker &> /dev/null; then
|
|
log_error "Docker is not installed. Please run setup-homelab.sh first."
|
|
exit 1
|
|
fi
|
|
|
|
if ! docker info &> /dev/null; then
|
|
log_error "Docker daemon is not running or you don't have permission."
|
|
log_info "Try: sudo systemctl start docker"
|
|
log_info "Or log out and log back in for group changes to take effect"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Docker is available and running"
|
|
echo ""
|
|
|
|
# Load environment variables for domain check
|
|
source "$REPO_DIR/.env"
|
|
|
|
if [ -z "$DOMAIN" ]; then
|
|
log_error "DOMAIN is not set in .env file"
|
|
log_info "Please edit .env and set your DuckDNS domain"
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Using domain: $DOMAIN"
|
|
echo ""
|
|
|
|
# Deployment options menu
|
|
echo "=========================================="
|
|
echo " EZ-HOMELAB DEPLOYMENT OPTIONS"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo "Choose your deployment scenario:"
|
|
echo ""
|
|
echo "1) Full deployment (recommended for new servers)"
|
|
echo " - Deploy core infrastructure (Traefik, Authelia, etc.)"
|
|
echo " - Deploy infrastructure stack (Dockge, Pi-hole, etc.)"
|
|
echo " - Deploy dashboards (Homepage, Homarr)"
|
|
echo " - Setup all remaining stacks for Dockge"
|
|
echo ""
|
|
echo "2) Skip core stack (for existing homelab servers)"
|
|
echo " - Skip core infrastructure deployment"
|
|
echo " - Deploy infrastructure stack (Dockge, Pi-hole, etc.)"
|
|
echo " - Deploy dashboards (Homepage, Homarr)"
|
|
echo " - Setup all remaining stacks for Dockge"
|
|
echo ""
|
|
echo "3) Setup stacks only (no deployment)"
|
|
echo " - Setup all stacks in Dockge without deploying any"
|
|
echo " - Useful for preparing stacks for manual deployment"
|
|
echo ""
|
|
read -p "Enter your choice (1-3): " DEPLOY_CHOICE
|
|
|
|
case $DEPLOY_CHOICE in
|
|
1)
|
|
DEPLOY_CORE=true
|
|
DEPLOY_INFRASTRUCTURE=true
|
|
DEPLOY_DASHBOARDS=true
|
|
SETUP_STACKS=true
|
|
log_info "Selected: Full deployment"
|
|
;;
|
|
2)
|
|
DEPLOY_CORE=false
|
|
DEPLOY_INFRASTRUCTURE=true
|
|
DEPLOY_DASHBOARDS=true
|
|
SETUP_STACKS=true
|
|
log_info "Selected: Skip core stack"
|
|
;;
|
|
3)
|
|
DEPLOY_CORE=false
|
|
DEPLOY_INFRASTRUCTURE=false
|
|
DEPLOY_DASHBOARDS=false
|
|
SETUP_STACKS=true
|
|
log_info "Selected: Setup stacks only"
|
|
;;
|
|
*)
|
|
log_error "Invalid choice. Please run the script again."
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
|
|
# Function to replace environment variables in config files
|
|
replace_env_vars() {
|
|
local config_dir="$1"
|
|
local env_file="$2"
|
|
|
|
log_info "Processing environment variables in $config_dir..."
|
|
|
|
# Find all files in the config directory
|
|
find "$config_dir" -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.conf" -o -name "*.json" \) | while read -r file; do
|
|
# Extract all ${VAR_NAME} patterns from the file
|
|
vars_found=$(grep -o '\${[^}]*}' "$file" | sed 's/\${//' | sed 's/}//' | sort | uniq)
|
|
|
|
if [ -n "$vars_found" ]; then
|
|
log_info "Processing file: $(basename "$file")"
|
|
|
|
for var in $vars_found; do
|
|
# Check if variable exists in .env file
|
|
if grep -q "^${var}=" "$env_file" 2>/dev/null; then
|
|
# Get the value from .env file
|
|
value=$(grep "^${var}=" "$env_file" | cut -d'=' -f2- | sed 's/^"//' | sed 's/"$//')
|
|
# Escape special characters for sed
|
|
escaped_value=$(printf '%s\n' "$value" | sed 's/[[\.*^$()+?{|]/\\&/g')
|
|
# Replace in file
|
|
sed -i "s|\${${var}}|${escaped_value}|g" "$file"
|
|
log_info " ✓ Replaced \${$var}"
|
|
else
|
|
log_warning " ⚠ Variable \${$var} not found in .env file - leaving as-is"
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Function to setup stacks without deploying them
|
|
setup_stacks_for_dockge() {
|
|
log_info "Setting up all stacks for Dockge..."
|
|
|
|
# List of stacks to setup
|
|
STACKS=("vpn" "media" "media-management" "monitoring" "productivity" "utilities" "alternatives" "homeassistant" "nextcloud")
|
|
|
|
for stack in "${STACKS[@]}"; do
|
|
STACK_DIR="/opt/stacks/$stack"
|
|
REPO_STACK_DIR="$REPO_DIR/docker-compose/$stack"
|
|
|
|
if [ -d "$REPO_STACK_DIR" ]; then
|
|
log_info "Setting up $stack stack..."
|
|
|
|
# Create stack directory
|
|
mkdir -p "$STACK_DIR"
|
|
|
|
# Copy docker-compose.yml
|
|
if [ -f "$REPO_STACK_DIR/docker-compose.yml" ]; then
|
|
cp "$REPO_STACK_DIR/docker-compose.yml" "$STACK_DIR/"
|
|
cp "$REPO_DIR/.env" "$STACK_DIR/.env"
|
|
|
|
# Copy any additional config directories
|
|
for config_dir in "$REPO_STACK_DIR"/*/; do
|
|
if [ -d "$config_dir" ] && [ "$(basename "$config_dir")" != "." ]; then
|
|
cp -r "$config_dir" "$STACK_DIR/"
|
|
fi
|
|
done
|
|
|
|
# Replace environment variables in the stack configuration
|
|
replace_env_vars "$STACK_DIR" "$STACK_DIR/.env"
|
|
|
|
log_success "$stack stack prepared for Dockge"
|
|
else
|
|
log_warning "$stack stack docker-compose.yml not found, skipping..."
|
|
fi
|
|
else
|
|
log_warning "$stack stack directory not found in repo, skipping..."
|
|
fi
|
|
done
|
|
|
|
log_success "All stacks prepared for Dockge deployment"
|
|
echo ""
|
|
}
|
|
|
|
# Step 1: Create required directories
|
|
log_info "Step 1: Creating required directories..."
|
|
mkdir -p /opt/stacks/core
|
|
mkdir -p /opt/stacks/infrastructure
|
|
mkdir -p /opt/dockge/data
|
|
log_success "Directories created"
|
|
echo ""
|
|
|
|
# Step 2: Create Docker networks (if they don't exist)
|
|
log_info "Step 2: Creating Docker networks..."
|
|
docker network create homelab-network 2>/dev/null && log_success "Created homelab-network" || log_info "homelab-network already exists"
|
|
docker network create traefik-network 2>/dev/null && log_success "Created traefik-network" || log_info "traefik-network already exists"
|
|
docker network create media-network 2>/dev/null && log_success "Created media-network" || log_info "media-network already exists"
|
|
echo ""
|
|
|
|
# Step 3: Deploy core infrastructure (DuckDNS, Traefik, Authelia, Gluetun)
|
|
if [ "$DEPLOY_CORE" = true ]; then
|
|
log_info "Step 3: Deploying core infrastructure stack..."
|
|
log_info " - DuckDNS (Dynamic DNS)"
|
|
log_info " - Traefik (Reverse Proxy with SSL)"
|
|
log_info " - Authelia (Single Sign-On)"
|
|
log_info " - Gluetun (VPN Client)"
|
|
echo ""
|
|
|
|
# Copy core stack files with overwrite checks
|
|
if [ -f "/opt/stacks/core/docker-compose.yml" ]; then
|
|
log_warning "docker-compose.yml already exists in /opt/stacks/core/"
|
|
log_info "Creating backup: docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S)"
|
|
cp /opt/stacks/core/docker-compose.yml /opt/stacks/core/docker-compose.yml.backup.$(date +%Y%m%d_%H%M%S)
|
|
fi
|
|
cp "$REPO_DIR/docker-compose/core/docker-compose.yml" /opt/stacks/core/docker-compose.yml
|
|
|
|
if [ -d "/opt/stacks/core/traefik" ]; then
|
|
log_warning "Traefik configuration already exists in /opt/stacks/core/"
|
|
log_info "Creating backup: traefik.backup.$(date +%Y%m%d_%H%M%S)"
|
|
mv /opt/stacks/core/traefik /opt/stacks/core/traefik.backup.$(date +%Y%m%d_%H%M%S)
|
|
fi
|
|
cp -r "$REPO_DIR/config-templates/traefik" /opt/stacks/core/
|
|
|
|
# Detect server hostname and update configuration
|
|
log_info "Detecting server hostname..."
|
|
DETECTED_HOSTNAME=$(hostname)
|
|
if [ -n "$DETECTED_HOSTNAME" ] && [ "$DETECTED_HOSTNAME" != "debian" ]; then
|
|
log_info "Detected hostname: $DETECTED_HOSTNAME"
|
|
# Update SERVER_HOSTNAME in the copied .env file
|
|
sed -i "s/SERVER_HOSTNAME=.*/SERVER_HOSTNAME=$DETECTED_HOSTNAME/" /opt/stacks/core/.env
|
|
# Update SERVER_HOSTNAME in the source .env file for future deployments
|
|
sed -i "s/SERVER_HOSTNAME=.*/SERVER_HOSTNAME=$DETECTED_HOSTNAME/" "$REPO_DIR/.env"
|
|
# Update sablier.yml with detected hostname
|
|
sed -i "s/debian-/$DETECTED_HOSTNAME-/g" /opt/stacks/core/traefik/dynamic/sablier.yml
|
|
log_success "Updated configuration with detected hostname: $DETECTED_HOSTNAME"
|
|
else
|
|
log_info "Using default hostname 'debian' (hostname detection failed or returned default)"
|
|
fi
|
|
echo ""
|
|
|
|
if [ -d "/opt/stacks/core/authelia" ]; then
|
|
log_warning "Authelia configuration already exists in /opt/stacks/core/"
|
|
log_info "Creating backup: authelia.backup.$(date +%Y%m%d_%H%M%S)"
|
|
mv /opt/stacks/core/authelia /opt/stacks/core/authelia.backup.$(date +%Y%m%d_%H%M%S)
|
|
fi
|
|
cp -r "$REPO_DIR/config-templates/authelia" /opt/stacks/core/
|
|
|
|
# Replace domain placeholders in Authelia config (legacy hardcoded replacement)
|
|
sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml
|
|
|
|
if [ -f "/opt/stacks/core/.env" ]; then
|
|
log_warning ".env already exists in /opt/stacks/core/"
|
|
log_info "Creating backup: .env.backup.$(date +%Y%m%d_%H%M%S)"
|
|
cp /opt/stacks/core/.env /opt/stacks/core/.env.backup.$(date +%Y%m%d_%H%M%S)
|
|
fi
|
|
cp "$REPO_DIR/.env" /opt/stacks/core/.env
|
|
|
|
# Automatically replace all environment variables in config files
|
|
replace_env_vars "/opt/stacks/core" "/opt/stacks/core/.env"
|
|
|
|
# Legacy hardcoded replacements (keeping for backward compatibility)
|
|
# These will be overridden by the automatic replacement above if variables exist
|
|
sed -i "s/admin/${AUTHELIA_ADMIN_USER}/g" /opt/stacks/core/authelia/users_database.yml
|
|
sed -i "s/admin@example.com/${AUTHELIA_ADMIN_EMAIL}/g" /opt/stacks/core/authelia/users_database.yml
|
|
sed -i "s|\$argon2id\$v=19\$m=65536,t=3,p=4\$CHANGEME|${AUTHELIA_ADMIN_PASSWORD}|g" /opt/stacks/core/authelia/users_database.yml
|
|
|
|
# Deploy core stack
|
|
cd /opt/stacks/core
|
|
docker compose up -d
|
|
|
|
log_success "Core infrastructure deployed"
|
|
echo ""
|
|
|
|
# Wait for Traefik to be ready
|
|
log_info "Waiting for Traefik to initialize..."
|
|
sleep 10
|
|
|
|
# Check if Traefik is healthy
|
|
if docker ps | grep -q "traefik.*Up"; then
|
|
log_success "Traefik is running"
|
|
else
|
|
log_warning "Traefik container check inconclusive, continuing..."
|
|
fi
|
|
echo ""
|
|
else
|
|
log_info "Skipping core infrastructure deployment"
|
|
echo ""
|
|
fi
|
|
|
|
# Step 4: Deploy infrastructure stack (Dockge and monitoring tools)
|
|
if [ "$DEPLOY_INFRASTRUCTURE" = true ]; then
|
|
log_info "Step 4: Deploying infrastructure stack..."
|
|
log_info " - Dockge (Docker Compose Manager)"
|
|
log_info " - Pi-hole (DNS Ad Blocker)"
|
|
log_info " - Watchtower (Container Updates)"
|
|
log_info " - Dozzle (Log Viewer)"
|
|
log_info " - Glances (System Monitor)"
|
|
log_info " - Docker Proxy (Security)"
|
|
echo ""
|
|
|
|
# Copy infrastructure stack
|
|
cp "$REPO_DIR/docker-compose/infrastructure/docker-compose.yml" /opt/stacks/infrastructure/docker-compose.yml
|
|
cp "$REPO_DIR/.env" /opt/stacks/infrastructure/.env
|
|
|
|
# Deploy infrastructure stack
|
|
cd /opt/stacks/infrastructure
|
|
docker compose up -d
|
|
|
|
log_success "Infrastructure stack deployed"
|
|
echo ""
|
|
else
|
|
log_info "Skipping infrastructure stack deployment"
|
|
echo ""
|
|
fi
|
|
|
|
# Step 5: Deploy dashboard stack
|
|
if [ "$DEPLOY_DASHBOARDS" = true ]; then
|
|
log_info "Step 5: Deploying dashboard stack..."
|
|
log_info " - Homepage (Application Dashboard)"
|
|
log_info " - Homarr (Modern Dashboard)"
|
|
echo ""
|
|
|
|
# Create dashboards directory
|
|
mkdir -p /opt/stacks/dashboards
|
|
|
|
# Copy dashboards compose file
|
|
cp "$REPO_DIR/docker-compose/dashboards/docker-compose.yml" /opt/stacks/dashboards/docker-compose.yml
|
|
cp "$REPO_DIR/.env" /opt/stacks/dashboards/.env
|
|
|
|
# Copy homepage config
|
|
if [ -d "$REPO_DIR/docker-compose/dashboards/homepage" ]; then
|
|
cp -r "$REPO_DIR/docker-compose/dashboards/homepage" /opt/stacks/dashboards/
|
|
fi
|
|
|
|
# Deploy dashboards stack
|
|
cd /opt/stacks/dashboards
|
|
docker compose up -d
|
|
|
|
log_success "Dashboard stack deployed"
|
|
echo ""
|
|
else
|
|
log_info "Skipping dashboard stack deployment"
|
|
echo ""
|
|
fi
|
|
|
|
# Step 6: Deploy Dokuwiki
|
|
log_info "Step 6: Deploying Dokuwiki wiki platform..."
|
|
log_info " - DokuWiki (File-based wiki with pre-configured content)"
|
|
echo ""
|
|
|
|
# Create Dokuwiki directory
|
|
mkdir -p /opt/stacks/dokuwiki/config
|
|
|
|
# Copy Dokuwiki compose file
|
|
cp "$REPO_DIR/config-templates/dokuwiki/docker-compose.yml" /opt/stacks/dokuwiki/docker-compose.yml
|
|
|
|
# Copy pre-configured Dokuwiki config, content, and keys
|
|
if [ -d "$REPO_DIR/config-templates/dokuwiki/conf" ]; then
|
|
cp -r "$REPO_DIR/config-templates/dokuwiki/conf" /opt/stacks/dokuwiki/config/
|
|
else
|
|
log_warning "Dokuwiki conf directory not found, skipping..."
|
|
fi
|
|
|
|
if [ -d "$REPO_DIR/config-templates/dokuwiki/data" ]; then
|
|
cp -r "$REPO_DIR/config-templates/dokuwiki/data" /opt/stacks/dokuwiki/config/
|
|
else
|
|
log_warning "Dokuwiki data directory not found, skipping..."
|
|
fi
|
|
|
|
if [ -d "$REPO_DIR/config-templates/dokuwiki/keys" ]; then
|
|
cp -r "$REPO_DIR/config-templates/dokuwiki/keys" /opt/stacks/dokuwiki/config/
|
|
else
|
|
log_warning "Dokuwiki keys directory not found, skipping..."
|
|
fi
|
|
|
|
# Set proper ownership for Dokuwiki config
|
|
sudo chown -R 1000:1000 /opt/stacks/dokuwiki/config
|
|
|
|
# Deploy Dokuwiki
|
|
cd /opt/stacks/dokuwiki
|
|
docker compose up -d
|
|
|
|
log_success "Dokuwiki deployed with pre-configured content"
|
|
echo ""
|
|
|
|
# Step 7: Setup stacks for Dockge (if requested)
|
|
if [ "$SETUP_STACKS" = true ]; then
|
|
setup_stacks_for_dockge
|
|
fi
|
|
|
|
# Deployment completed
|
|
echo ""
|
|
echo "=========================================="
|
|
log_success "Deployment completed successfully!"
|
|
echo "=========================================="
|
|
echo ""
|
|
log_info "Access your services:"
|
|
echo ""
|
|
echo " 🚀 Dockge: $DOCKGE_URL"
|
|
echo " 📊 Homepage: https://home.${DOMAIN}"
|
|
echo " 🎯 Homarr: https://homarr.${DOMAIN}"
|
|
echo " 📖 Wiki: https://wiki.${DOMAIN}"
|
|
echo " 🔒 Authelia: https://auth.${DOMAIN}"
|
|
echo " 🔀 Traefik: https://traefik.${DOMAIN}"
|
|
echo ""
|
|
log_info "Next steps:"
|
|
echo ""
|
|
echo " 1. Log in to Dockge using your Authelia credentials"
|
|
echo " (configured in /opt/stacks/core/authelia/users_database.yml)"
|
|
echo ""
|
|
echo " 2. Access your dashboards:"
|
|
echo " - Homepage: https://home.${DOMAIN} (AI-configurable dashboard)"
|
|
echo " - Homarr: https://homarr.${DOMAIN} (Modern dashboard)"
|
|
echo ""
|
|
echo " 3. Access your pre-deployed Dokuwiki at https://wiki.${DOMAIN}"
|
|
echo " (admin/admin credentials)"
|
|
echo ""
|
|
echo " 4. Deploy additional stacks through Dockge's web UI:"
|
|
echo " - media.yml (Plex, Jellyfin, Sonarr, Radarr, etc.)"
|
|
echo " - media-extended.yml (Readarr, Lidarr, etc.)"
|
|
echo " - homeassistant.yml (Home Assistant and accessories)"
|
|
echo " - productivity.yml (Nextcloud, Gitea, additional wikis)"
|
|
echo " - monitoring.yml (Grafana, Prometheus, etc.)"
|
|
echo " - utilities.yml (Backups, code editors, etc.)"
|
|
echo ""
|
|
echo " 5. Configure services via the AI assistant in VS Code"
|
|
echo ""
|
|
echo "=========================================="
|
|
echo ""
|
|
log_info "For documentation, see: $REPO_DIR/docs/"
|
|
log_info "For troubleshooting, see: $REPO_DIR/docs/quick-reference.md"
|
|
echo ""
|