From 71d9a1e152b041c18b9c12f6b20f8658ba11d54a Mon Sep 17 00:00:00 2001 From: EZ-Homelab Date: Thu, 22 Jan 2026 18:56:20 -0500 Subject: [PATCH] feat: Complete EZ-Homelab deployment system overhaul - Add unified ez-homelab.sh script with guided menu interface - Create dedicated Dockge stack in /opt/dockge for clean isolation - Move dockerproxy from core to infrastructure stack - Fix Authelia configuration with proper variable placeholders - Update all compose files to use variables - Enhance script with comprehensive variable replacement - Fix sed delimiter conflicts and middleware issues - Add proper step numbering and error handling - Prepare all stacks for Dockge management - Update README with new deployment instructions --- README.md | 17 +- config-templates/authelia/configuration.yml | 23 +- .../core/authelia/configuration.yml | 24 +- docker-compose/core/docker-compose.yml | 19 - docker-compose/dashboards/docker-compose.yml | 3 - docker-compose/dockge/docker-compose.yml | 51 ++ .../infrastructure/docker-compose.yml | 70 +- scripts/deploy-homelab.sh | 367 ++++++---- scripts/ez-homelab.sh | 647 ++++++++++++++++++ 9 files changed, 961 insertions(+), 260 deletions(-) create mode 100644 docker-compose/dockge/docker-compose.yml create mode 100755 scripts/ez-homelab.sh diff --git a/README.md b/README.md index 3385cc3..2aaf4d3 100644 --- a/README.md +++ b/README.md @@ -26,17 +26,16 @@ Plus Dockge for visual management of containers, and Homepage dashboard to easil git clone https://github.com/kelinfoxy/EZ-Homelab.git cd EZ-Homelab -# Configure environment -cp .env.example .env -nano .env # Add your domain and tokens - -# Run setup script (installs Docker, generates secrets) -sudo ./scripts/setup-homelab.sh - -# Deploy all services -sudo ./scripts/deploy-homelab.sh +# Run the unified setup script (guided installation) +./scripts/ez-homelab.sh ``` +**What the script does:** +- Installs Docker and required system packages +- Guides you through configuration (domain, admin credentials, etc.) +- Deploys selected services based on your needs +- Sets up all stacks for Dockge management + **Access your homelab:** - **Dockge**: `https://dockge.yourdomain.duckdns.org` (primary management interface) - **Homepage**: `https://homepage.yourdomain.duckdns.org` (service dashboard) diff --git a/config-templates/authelia/configuration.yml b/config-templates/authelia/configuration.yml index 9b7912b..500d4aa 100644 --- a/config-templates/authelia/configuration.yml +++ b/config-templates/authelia/configuration.yml @@ -13,10 +13,10 @@ theme: dark jwt_secret: ${AUTHELIA_JWT_SECRET} -default_redirection_url: https://auth.your-domain.duckdns.org +default_redirection_url: https://auth.${DOMAIN} totp: - issuer: your-domain.duckdns.org + issuer: ${DOMAIN} period: 30 skew: 1 @@ -36,25 +36,25 @@ access_control: rules: # Bypass Authelia for Jellyfin (allow app access) - - domain: jellyfin.your-domain.duckdns.org + - domain: jellyfin.${DOMAIN} policy: bypass # Bypass for Plex (allow app access) - - domain: plex.your-domain.duckdns.org + - domain: plex.${DOMAIN} policy: bypass # Bypass for Home Assistant (has its own auth) - - domain: ha.your-domain.duckdns.org + - domain: ha.${DOMAIN} policy: bypass # Protected: All other services require authentication - - domain: "*.your-domain.duckdns.org" + - domain: "*.${DOMAIN}" policy: one_factor # Two-factor for admin services (optional) # - domain: - # - "admin.your-domain.duckdns.org" - # - "portainer.your-domain.duckdns.org" + # - "admin.${DOMAIN}" + # - "portainer.${DOMAIN}" # policy: two_factor session: @@ -63,7 +63,12 @@ session: expiration: 24h # Session expires after 24 hours inactivity: 24h # Session expires after 24 hours of inactivity remember_me_duration: 1M - domain: your-domain.duckdns.org + domain: ${DOMAIN} + cookies: + - name: authelia_session + domain: ${DOMAIN} + secure: true + same_site: lax regulation: max_retries: 3 diff --git a/docker-compose/core/authelia/configuration.yml b/docker-compose/core/authelia/configuration.yml index 1e788e0..5402935 100644 --- a/docker-compose/core/authelia/configuration.yml +++ b/docker-compose/core/authelia/configuration.yml @@ -1,6 +1,6 @@ # Authelia Configuration # Copy to /opt/stacks/authelia/configuration.yml -# IMPORTANT: Replace 'kelin-casa.duckdns.org' with your actual DuckDNS domain +# IMPORTANT: Replace '${DOMAIN}' with your actual DuckDNS domain server: host: 0.0.0.0 @@ -13,10 +13,10 @@ theme: dark jwt_secret: ${AUTHELIA_JWT_SECRET} -default_redirection_url: https://auth.kelin-casa.duckdns.org +default_redirection_url: https://auth.${DOMAIN} totp: - issuer: kelin-casa.duckdns.org + issuer: ${DOMAIN} period: 30 skew: 1 @@ -36,31 +36,31 @@ access_control: rules: # Bypass Authelia for Jellyfin (allow app access) - - domain: jellyfin.kelin-casa.duckdns.org + - domain: jellyfin.${DOMAIN} policy: bypass # Bypass for Plex (allow app access) - - domain: plex.kelin-casa.duckdns.org + - domain: plex.${DOMAIN} policy: bypass # Bypass for Home Assistant (has its own auth) - - domain: ha.kelin-casa.duckdns.org + - domain: ha.${DOMAIN} policy: bypass # Bypass for development services (they have their own auth or setup) - - domain: pgadmin.kelin-casa.duckdns.org + - domain: pgadmin.${DOMAIN} policy: bypass - - domain: gitlab.kelin-casa.duckdns.org + - domain: gitlab.${DOMAIN} policy: bypass # Protected: All other services require authentication - - domain: "*.kelin-casa.duckdns.org" + - domain: "*.${DOMAIN}" policy: one_factor # Two-factor for admin services (optional) # - domain: - # - "admin.kelin-casa.duckdns.org" - # - "portainer.kelin-casa.duckdns.org" + # - "admin.${DOMAIN}" + # - "portainer.${DOMAIN}" # policy: two_factor session: @@ -69,7 +69,7 @@ session: expiration: 24h # Session expires after 24 hours inactivity: 24h # Session expires after 24 hours of inactivity remember_me_duration: 1M - domain: kelin-casa.duckdns.org + domain: ${DOMAIN} regulation: max_retries: 3 diff --git a/docker-compose/core/docker-compose.yml b/docker-compose/core/docker-compose.yml index 1fcb480..be717e3 100644 --- a/docker-compose/core/docker-compose.yml +++ b/docker-compose/core/docker-compose.yml @@ -73,25 +73,6 @@ services: - traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true - x-dockge.url=https://auth.${DOMAIN} - dockerproxy: - image: tecnativa/docker-socket-proxy:latest - container_name: dockerproxy - privileged: true - restart: unless-stopped - ports: - - 2375:2375 - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - environment: - - CONTAINERS=1 - - SERVICES=1 - - TASKS=1 - - NETWORKS=1 - - NODES=1 - labels: - - homelab.category=infrastructure - - homelab.description=Docker socket proxy for security - # Sablier - Lazy loading service for Docker containers sablier-service: image: sablierapp/sablier:latest diff --git a/docker-compose/dashboards/docker-compose.yml b/docker-compose/dashboards/docker-compose.yml index 80fed42..1ea3798 100644 --- a/docker-compose/dashboards/docker-compose.yml +++ b/docker-compose/dashboards/docker-compose.yml @@ -25,7 +25,6 @@ services: networks: - homelab-network - traefik-network - - dockerproxy-network volumes: - ./homepage:/app/config - /var/run/docker.sock:/var/run/docker.sock # For Docker integration do not mount RO @@ -86,5 +85,3 @@ networks: external: true traefik-network: external: true - dockerproxy-network: - external: true diff --git a/docker-compose/dockge/docker-compose.yml b/docker-compose/dockge/docker-compose.yml new file mode 100644 index 0000000..1a1baf9 --- /dev/null +++ b/docker-compose/dockge/docker-compose.yml @@ -0,0 +1,51 @@ +# Dockge Stack +# Docker Compose Stack Manager +# Place in /opt/dockge/docker-compose.yml + +# Service Access URLs: +# - Dockge: https://dockge.${DOMAIN} + +services: + # Dockge - Docker Compose Stack Manager (PRIMARY - preferred over Portainer) + # Access at: https://dockge.${DOMAIN} + dockge: + image: louislam/dockge:1 + deploy: + resources: + limits: + cpus: '0.50' + memory: 256M + pids: 512 + reservations: + cpus: '0.25' + memory: 128M + container_name: dockge + restart: unless-stopped + networks: + - homelab-network + - traefik-network + ports: + - "5001:5001" # Optional: direct access + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - /opt/stacks:/opt/stacks # Dockge manages stacks in this directory + - ./data:/app/data + environment: + - DOCKGE_STACKS_DIR=/opt/stacks + - DOCKGE_ENABLE_CONSOLE=true + labels: + - "homelab.category=infrastructure" + - "homelab.description=Docker Compose stack manager (PRIMARY)" + - "traefik.enable=true" + - "traefik.http.routers.dockge.rule=Host(`dockge.${DOMAIN}`)" + - "traefik.http.routers.dockge.entrypoints=websecure" + - "traefik.http.routers.dockge.tls=true" + - "traefik.http.routers.dockge.middlewares=authelia@docker" + - "traefik.http.services.dockge.loadbalancer.server.port=5001" + - "x-dockge.url=https://dockge.${DOMAIN}" + +networks: + homelab-network: + external: true + traefik-network: + external: true \ No newline at end of file diff --git a/docker-compose/infrastructure/docker-compose.yml b/docker-compose/infrastructure/docker-compose.yml index d77fd7e..5f44ca0 100644 --- a/docker-compose/infrastructure/docker-compose.yml +++ b/docker-compose/infrastructure/docker-compose.yml @@ -5,7 +5,6 @@ # See /opt/stacks/traefik/, /opt/stacks/authelia/, etc. # Service Access URLs: -# - Dockge: https://dockge.${DOMAIN} # - Portainer: https://portainer.${DOMAIN} # - Pi-hole: https://pihole.${DOMAIN} # - Dozzle: https://dozzle.${DOMAIN} @@ -13,43 +12,24 @@ # - Netdata: https://netdata.${DOMAIN} services: - # Dockge - Docker Compose Stack Manager (PRIMARY - preferred over Portainer) - # Access at: https://dockge.${DOMAIN} - dockge: - image: louislam/dockge:1 - deploy: - resources: - limits: - cpus: '0.50' - memory: 256M - pids: 512 - reservations: - cpus: '0.25' - memory: 128M - container_name: dockge + dockerproxy: + image: tecnativa/docker-socket-proxy:latest + container_name: dockerproxy + privileged: true restart: unless-stopped - networks: - - homelab-network - - traefik-network ports: - - "5001:5001" # Optional: direct access + - 2375:2375 volumes: - - /var/run/docker.sock:/var/run/docker.sock - - /opt/stacks:/opt/stacks # Dockge manages stacks in this directory - - /opt/dockge/data:/app/data + - /var/run/docker.sock:/var/run/docker.sock:ro environment: - - DOCKGE_STACKS_DIR=/opt/stacks - - DOCKGE_ENABLE_CONSOLE=true + - CONTAINERS=1 + - SERVICES=1 + - TASKS=1 + - NETWORKS=1 + - NODES=1 labels: - - "homelab.category=infrastructure" - - "homelab.description=Docker Compose stack manager (PRIMARY)" - - "traefik.enable=true" - - "traefik.http.routers.dockge.rule=Host(`dockge.${DOMAIN}`)" - - "traefik.http.routers.dockge.entrypoints=websecure" - - "traefik.http.routers.dockge.tls=true" - - "traefik.http.routers.dockge.middlewares=authelia@docker" - - "traefik.http.services.dockge.loadbalancer.server.port=5001" - - "x-dockge.url=https://dockge.${DOMAIN}" + - homelab.category=infrastructure + - homelab.description=Docker socket proxy for security # Pi-hole - Network-wide ad blocker and DNS server # Access at: https://pihole.${DOMAIN} @@ -160,28 +140,6 @@ services: - "traefik.http.routers.dozzle.middlewares=authelia@docker" - "traefik.http.services.dozzle.loadbalancer.server.port=8080" - # Docker Proxy - Socket proxy for security - # Used by services that need Docker socket access - dockerproxy: - image: tecnativa/docker-socket-proxy:latest - container_name: dockerproxy - restart: unless-stopped - networks: - - dockerproxy-network - ports: - - "127.0.0.1:2375:2375" - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - environment: - - CONTAINERS=1 - - SERVICES=1 - - TASKS=1 - - NETWORKS=1 - - NODES=1 - labels: - - "homelab.category=infrastructure" - - "homelab.description=Docker socket proxy for security" - # Glances - System monitoring # Access at: https://glances.${DOMAIN} glances: @@ -259,5 +217,3 @@ networks: external: true traefik-network: external: true - dockerproxy-network: - external: true diff --git a/scripts/deploy-homelab.sh b/scripts/deploy-homelab.sh index bcfdc4e..3e6bf28 100755 --- a/scripts/deploy-homelab.sh +++ b/scripts/deploy-homelab.sh @@ -1,6 +1,6 @@ #!/bin/bash -# AI-Homelab Deployment Script -# This script deploys the core infrastructure and Dockge +# 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 @@ -37,11 +37,11 @@ if [ "$EUID" -eq 0 ]; then exit 1 fi -# Get script directory (AI-Homelab/scripts) +# Get script directory (EZ-Homelab/scripts) SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" -log_info "AI-Homelab Deployment Script" +log_info "EZ-Homelab Deployment Script" echo "" # Check if .env file exists @@ -82,8 +82,105 @@ 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 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 + + 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/5: Creating required directories..." +log_info "Step 1: Creating required directories..." mkdir -p /opt/stacks/core mkdir -p /opt/stacks/infrastructure mkdir -p /opt/dockge/data @@ -91,130 +188,145 @@ log_success "Directories created" echo "" # Step 2: Create Docker networks (if they don't exist) -log_info "Step 2/5: Creating Docker networks..." +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) -log_info "Step 3/5: 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 "" +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 + # 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/ + 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/ -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/ + 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 -sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml + # Replace domain placeholders in Authelia config + 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 + 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 -# Replace secret placeholders in Authelia config -source /opt/stacks/core/.env -sed -i "s|\${AUTHELIA_JWT_SECRET}|${AUTHELIA_JWT_SECRET}|g" /opt/stacks/core/authelia/configuration.yml -sed -i "s|\${AUTHELIA_SESSION_SECRET}|${AUTHELIA_SESSION_SECRET}|g" /opt/stacks/core/authelia/configuration.yml -sed -i "s|\${AUTHELIA_STORAGE_ENCRYPTION_KEY}|${AUTHELIA_STORAGE_ENCRYPTION_KEY}|g" /opt/stacks/core/authelia/configuration.yml + # Replace secret placeholders in Authelia config + source /opt/stacks/core/.env + sed -i "s|\${AUTHELIA_JWT_SECRET}|${AUTHELIA_JWT_SECRET}|g" /opt/stacks/core/authelia/configuration.yml + sed -i "s|\${AUTHELIA_SESSION_SECRET}|${AUTHELIA_SESSION_SECRET}|g" /opt/stacks/core/authelia/configuration.yml + sed -i "s|\${AUTHELIA_STORAGE_ENCRYPTION_KEY}|${AUTHELIA_STORAGE_ENCRYPTION_KEY}|g" /opt/stacks/core/authelia/configuration.yml -# Replace placeholders in Authelia users database -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 + # Replace placeholders in Authelia users database + 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 + # Deploy core stack + cd /opt/stacks/core + docker compose up -d -log_success "Core infrastructure deployed" -echo "" + log_success "Core infrastructure deployed" + echo "" -# Wait for Traefik to be ready -log_info "Waiting for Traefik to initialize..." -sleep 10 + # 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" + # 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_warning "Traefik container check inconclusive, continuing..." + log_info "Skipping core infrastructure deployment" + echo "" fi -echo "" # Step 4: Deploy infrastructure stack (Dockge and monitoring tools) -log_info "Step 4/6: 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 "" +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 + # 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 + # Deploy infrastructure stack + cd /opt/stacks/infrastructure + docker compose up -d -log_success "Infrastructure stack deployed" -echo "" - -# Step 5: Deploy dashboard stack -log_info "Step 5/7: 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/ + log_success "Infrastructure stack deployed" + echo "" +else + log_info "Skipping infrastructure stack deployment" + echo "" fi -# Deploy dashboards stack -cd /opt/stacks/dashboards -docker compose up -d +# 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 "" -log_success "Dashboard stack deployed" -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/7: Deploying Dokuwiki wiki platform..." +log_info "Step 6: Deploying Dokuwiki wiki platform..." log_info " - DokuWiki (File-based wiki with pre-configured content)" echo "" @@ -253,59 +365,12 @@ docker compose up -d log_success "Dokuwiki deployed with pre-configured content" echo "" -# Step 6: Wait for Dockge to be ready and open browser -log_info "Step 6/6: Waiting for Dockge web UI to be ready..." - -DOCKGE_URL="https://dockge.${DOMAIN}" -MAX_WAIT=60 # Maximum wait time in seconds -WAITED=0 - -# Function to check if Dockge is accessible -check_dockge() { - # Try to connect to Dockge (ignore SSL cert warnings for self-signed during startup) - curl -k -s -o /dev/null -w "%{http_code}" "$DOCKGE_URL" 2>/dev/null -} - -# Wait for Dockge to respond -while [ $WAITED -lt $MAX_WAIT ]; do - HTTP_CODE=$(check_dockge) - if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "401" ]; then - log_success "Dockge web UI is ready!" - break - fi - echo -n "." - sleep 2 - WAITED=$((WAITED + 2)) -done - -echo "" -echo "" - -if [ $WAITED -ge $MAX_WAIT ]; then - log_warning "Dockge did not respond within ${MAX_WAIT} seconds" - log_info "It may still be starting up. Check manually at: $DOCKGE_URL" -else - # Try to open browser - log_info "Opening Dockge in your browser..." - - # Detect and use available browser - if command -v xdg-open &> /dev/null; then - xdg-open "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - elif command -v gnome-open &> /dev/null; then - gnome-open "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - elif command -v firefox &> /dev/null; then - firefox "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - elif command -v google-chrome &> /dev/null; then - google-chrome "$DOCKGE_URL" &> /dev/null & - log_success "Browser opened" - else - log_warning "No browser detected. Please manually open: $DOCKGE_URL" - fi +# 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!" diff --git a/scripts/ez-homelab.sh b/scripts/ez-homelab.sh new file mode 100755 index 0000000..765ade7 --- /dev/null +++ b/scripts/ez-homelab.sh @@ -0,0 +1,647 @@ +#!/bin/bash +# EZ-Homelab Unified Setup & Deployment Script +# This script provides a guided setup and deployment experience +# Run as: ./ez-homelab.sh (will use sudo when needed) + +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" +} + +# Get script directory and repo directory +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" + +# Default values +DOMAIN="" +SERVER_IP="" +ADMIN_USER="" +ADMIN_EMAIL="" +ADMIN_PASSWORD="" +DEPLOY_CORE=false +DEPLOY_INFRASTRUCTURE=false +DEPLOY_DASHBOARDS=false +SETUP_STACKS=false + +# Load existing .env file if it exists +load_env_file() { + if [ -f "$REPO_DIR/.env" ]; then + log_info "Found existing .env file, loading current configuration..." + source "$REPO_DIR/.env" + + # Show current values + echo "" + echo "Current configuration:" + echo " Domain: ${DOMAIN:-Not set}" + echo " Server IP: ${SERVER_IP:-Not set}" + echo " Admin User: ${AUTHELIA_ADMIN_USER:-Not set}" + echo " Admin Email: ${AUTHELIA_ADMIN_EMAIL:-Not set}" + echo " Timezone: ${TZ:-Not set}" + echo "" + + return 0 + else + log_info "No existing .env file found. We'll create one during setup." + return 1 + fi +} + +# Save configuration to .env file +save_env_file() { + log_info "Saving configuration to .env file..." + + # Create .env file if it doesn't exist + if [ ! -f "$REPO_DIR/.env" ]; then + cp "$REPO_DIR/.env.example" "$REPO_DIR/.env" + fi + + # Update values + sed -i "s%DOMAIN=.*%DOMAIN=$DOMAIN%" "$REPO_DIR/.env" + sed -i "s%SERVER_IP=.*%SERVER_IP=$SERVER_IP%" "$REPO_DIR/.env" + sed -i "s%TZ=.*%TZ=$TZ%" "$REPO_DIR/.env" + + # Authelia settings (only if deploying core) + if [ "$DEPLOY_CORE" = true ]; then + # Ensure we have admin credentials + if [ -z "$ADMIN_USER" ]; then + ADMIN_USER="admin" + fi + if [ -z "$ADMIN_EMAIL" ]; then + ADMIN_EMAIL="${ADMIN_USER}@${DOMAIN}" + fi + if [ -z "$ADMIN_PASSWORD" ]; then + log_info "Using default admin password (changeme123) - please change this after setup!" + ADMIN_PASSWORD="changeme123" + fi + + if [ -z "$AUTHELIA_JWT_SECRET" ]; then + AUTHELIA_JWT_SECRET=$(openssl rand -hex 64) + fi + if [ -z "$AUTHELIA_SESSION_SECRET" ]; then + AUTHELIA_SESSION_SECRET=$(openssl rand -hex 64) + fi + if [ -z "$AUTHELIA_STORAGE_ENCRYPTION_KEY" ]; then + AUTHELIA_STORAGE_ENCRYPTION_KEY=$(openssl rand -hex 64) + fi + + # Save Authelia settings to .env + sed -i "s%AUTHELIA_JWT_SECRET=.*%AUTHELIA_JWT_SECRET=$AUTHELIA_JWT_SECRET%" "$REPO_DIR/.env" + sed -i "s%AUTHELIA_SESSION_SECRET=.*%AUTHELIA_SESSION_SECRET=$AUTHELIA_SESSION_SECRET%" "$REPO_DIR/.env" + sed -i "s%AUTHELIA_STORAGE_ENCRYPTION_KEY=.*%AUTHELIA_STORAGE_ENCRYPTION_KEY=$AUTHELIA_STORAGE_ENCRYPTION_KEY%" "$REPO_DIR/.env" + sed -i "s%# AUTHELIA_ADMIN_USER=.*%AUTHELIA_ADMIN_USER=$ADMIN_USER%" "$REPO_DIR/.env" + sed -i "s%# AUTHELIA_ADMIN_EMAIL=.*%AUTHELIA_ADMIN_EMAIL=$ADMIN_EMAIL%" "$REPO_DIR/.env" + + # Generate password hash if needed + if [ -z "$AUTHELIA_ADMIN_PASSWORD" ]; then + log_info "Generating Authelia password hash..." + # Pull Authelia image if needed + if ! docker images | grep -q authelia/authelia; then + docker pull authelia/authelia:latest > /dev/null 2>&1 + fi + AUTHELIA_ADMIN_PASSWORD=$(docker run --rm authelia/authelia:latest authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | grep -o '\$argon2id.*') + if [ -z "$AUTHELIA_ADMIN_PASSWORD" ]; then + log_error "Failed to generate Authelia password hash. Please check that ADMIN_PASSWORD is set." + exit 1 + fi + fi + + # Save password hash + sed -i "s%# AUTHELIA_ADMIN_PASSWORD=.*%AUTHELIA_ADMIN_PASSWORD=$AUTHELIA_ADMIN_PASSWORD%" "$REPO_DIR/.env" + sed -i "s%AUTHELIA_ADMIN_PASSWORD=.*%AUTHELIA_ADMIN_PASSWORD=$AUTHELIA_ADMIN_PASSWORD%" "$REPO_DIR/.env" + fi + + log_success "Configuration saved to .env file" +} + +# Prompt for required values +prompt_for_values() { + echo "" + log_info "Please provide the following information:" + echo " (Press Enter without typing to keep the current/default value shown in brackets)" + echo "" + + # Domain + if [ -z "$DOMAIN" ]; then + read -p "Enter your domain (e.g., example.duckdns.org): " DOMAIN + while [ -z "$DOMAIN" ]; do + log_warning "Domain is required" + read -p "Enter your domain (e.g., example.duckdns.org): " DOMAIN + done + else + read -p "Domain [$DOMAIN] (press Enter to keep current): " input + [ -n "$input" ] && DOMAIN="$input" + fi + + # Server IP + if [ -z "$SERVER_IP" ]; then + read -p "Enter your server IP address: " SERVER_IP + while [ -z "$SERVER_IP" ]; do + log_warning "Server IP is required" + read -p "Enter your server IP address: " SERVER_IP + done + else + read -p "Server IP [$SERVER_IP] (press Enter to keep current): " input + [ -n "$input" ] && SERVER_IP="$input" + fi + + # Timezone + if [ -z "$TZ" ]; then + TZ="America/New_York" + fi + read -p "Timezone [$TZ] (press Enter to keep current): " input + [ -n "$input" ] && TZ="$input" + + # Admin credentials (only if deploying core) + if [ "$DEPLOY_CORE" = true ]; then + echo "" + log_info "Authelia Admin Credentials:" + + if [ -z "$ADMIN_USER" ]; then + ADMIN_USER="admin" + fi + read -p "Admin username [$ADMIN_USER] (press Enter to keep current): " input + [ -n "$input" ] && ADMIN_USER="$input" + + if [ -z "$ADMIN_EMAIL" ]; then + ADMIN_EMAIL="${ADMIN_USER}@${DOMAIN}" + fi + read -p "Admin email [$ADMIN_EMAIL] (press Enter to keep current): " input + [ -n "$input" ] && ADMIN_EMAIL="$input" + + if [ -z "$ADMIN_PASSWORD" ]; then + while [ -z "$ADMIN_PASSWORD" ]; do + read -s -p "Admin password (will be hashed): " ADMIN_PASSWORD + echo "" + if [ ${#ADMIN_PASSWORD} -lt 8 ]; then + log_warning "Password must be at least 8 characters" + ADMIN_PASSWORD="" + fi + done + else + log_info "Admin password already configured" + fi + fi + + echo "" +} + +# System setup function (Docker, directories, etc.) +system_setup() { + log_info "Performing system setup..." + + # Check if running as root for system setup + if [ "$EUID" -ne 0 ]; then + log_warning "System setup requires root privileges. Running with sudo..." + exec sudo "$0" "$@" + fi + + # Get the actual user who invoked sudo + ACTUAL_USER=${SUDO_USER:-$USER} + + # Step 1: System Update + log_info "Step 1/10: Updating system packages..." + apt-get update && apt-get upgrade -y + log_success "System updated successfully" + + # Step 2: Install required packages + log_info "Step 2/10: Installing required packages..." + apt-get install -y curl wget git htop nano vim ufw fail2ban unattended-upgrades apt-listchanges + + # Step 3: Install Docker + log_info "Step 3/10: Installing Docker..." + curl -fsSL https://get.docker.com | sh + usermod -aG docker "$ACTUAL_USER" + + # Step 4: Install Docker Compose + log_info "Step 4/10: Installing Docker Compose..." + curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + chmod +x /usr/local/bin/docker-compose + + # Step 5: Configure UFW firewall + log_info "Step 5/10: Configuring firewall..." + ufw --force enable + ufw allow ssh + ufw allow 80 + ufw allow 443 + + # Step 6: Configure automatic updates + log_info "Step 6/10: Configuring automatic updates..." + dpkg-reconfigure -f noninteractive unattended-upgrades + + # Step 7: Create required directories + log_info "Step 7/10: Creating required directories..." + mkdir -p /opt/stacks/core + mkdir -p /opt/stacks/infrastructure + mkdir -p /opt/stacks/dashboards + mkdir -p /opt/dockge + + # Step 8: Set proper ownership + log_info "Step 8/10: Setting directory ownership..." + chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks + chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge + + # Step 9: Create Docker networks + log_info "Step 9/10: 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" + + # Step 10: Generate SSH keys for Git (optional) + log_info "Step 10/10: SSH key setup (optional)..." + if [ ! -f "/home/$ACTUAL_USER/.ssh/id_rsa" ]; then + log_info "Generating SSH key for $ACTUAL_USER..." + sudo -u "$ACTUAL_USER" ssh-keygen -t rsa -b 4096 -f "/home/$ACTUAL_USER/.ssh/id_rsa" -N "" + log_info "SSH public key:" + cat "/home/$ACTUAL_USER/.ssh/id_rsa.pub" + echo "" + log_info "Add this key to your Git provider (GitHub, GitLab, etc.)" + fi + + log_success "System setup completed!" + echo "" + log_info "Please log out and back in for Docker group changes to take effect." + echo "" +} + +# Deployment function +perform_deployment() { + log_info "Starting deployment..." + + # Switch back to regular user if we were running as root + if [ "$EUID" -eq 0 ]; then + ACTUAL_USER=${SUDO_USER:-$USER} + log_info "Switching to user $ACTUAL_USER for deployment..." + exec sudo -u "$ACTUAL_USER" "$0" "$@" + fi + + # Source the .env file + source "$REPO_DIR/.env" + + # 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/stacks/dashboards + mkdir -p /opt/dockge + log_success "Directories created" + + # 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 Dockge (always deployed) + log_info "Step 3: Deploying Dockge stack manager..." + log_info " - Dockge (Docker Compose Manager)" + echo "" + + # Copy Dockge stack files + cp "$REPO_DIR/docker-compose/dockge/docker-compose.yml" /opt/dockge/docker-compose.yml + cp "$REPO_DIR/.env" /opt/dockge/.env + + # Deploy Dockge stack + cd /opt/dockge + docker compose up -d + log_success "Dockge deployed" + echo "" + + # Deploy core infrastructure + if [ "$DEPLOY_CORE" = true ]; then + log_info "Step 4: Deploying core infrastructure stack..." + log_info " - DuckDNS (Dynamic DNS)" + log_info " - Traefik (Reverse Proxy with SSL)" + log_info " - Authelia (Single Sign-On)" + echo "" + + # Copy core stack files + cp "$REPO_DIR/docker-compose/core/docker-compose.yml" /opt/stacks/core/docker-compose.yml + cp "$REPO_DIR/.env" /opt/stacks/core/.env + + # Copy configs + if [ -d "/opt/stacks/core/traefik" ]; then + 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/ + + # Replace ACME email placeholder + sed -i "s/ACME_EMAIL_PLACEHOLDER/${AUTHELIA_ADMIN_EMAIL}/g" /opt/stacks/core/traefik/traefik.yml + + # Replace domain placeholders in traefik dynamic configs + find /opt/stacks/core/traefik/dynamic -name "*.yml" -exec sed -i "s/\${DOMAIN}/${DOMAIN}/g" {} \; + + if [ -d "/opt/stacks/core/authelia" ]; then + 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 + sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml + sed -i "s/\${DOMAIN}/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml + + # Replace secret placeholders + sed -i "s|\${AUTHELIA_JWT_SECRET}|${AUTHELIA_JWT_SECRET}|g" /opt/stacks/core/authelia/configuration.yml + sed -i "s|\${AUTHELIA_SESSION_SECRET}|${AUTHELIA_SESSION_SECRET}|g" /opt/stacks/core/authelia/configuration.yml + sed -i "s|\${AUTHELIA_STORAGE_ENCRYPTION_KEY}|${AUTHELIA_STORAGE_ENCRYPTION_KEY}|g" /opt/stacks/core/authelia/configuration.yml + 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 "" + fi + + # Deploy infrastructure stack + if [ "$DEPLOY_INFRASTRUCTURE" = true ]; then + step_num=$([ "$DEPLOY_CORE" = true ] && echo "5" || echo "4") + log_info "Step $step_num: Deploying infrastructure stack..." + 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 + + # If core is not deployed, remove Authelia middleware references + if [ "$DEPLOY_CORE" = false ]; then + log_info "Core infrastructure not deployed - removing Authelia middleware references..." + sed -i '/middlewares=authelia@docker/d' /opt/stacks/infrastructure/docker-compose.yml + fi + + # Deploy infrastructure stack + cd /opt/stacks/infrastructure + docker compose up -d + log_success "Infrastructure stack deployed" + echo "" + fi + + # Deploy dashboard stack + if [ "$DEPLOY_DASHBOARDS" = true ]; then + if [ "$DEPLOY_CORE" = true ] && [ "$DEPLOY_INFRASTRUCTURE" = true ]; then + step_num=6 + elif [ "$DEPLOY_CORE" = true ] || [ "$DEPLOY_INFRASTRUCTURE" = true ]; then + step_num=5 + else + step_num=4 + fi + log_info "Step $step_num: 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 "" + fi + + # Setup stacks for Dockge + if [ "$SETUP_STACKS" = true ]; then + setup_stacks_for_dockge + fi + + # Deploy Dokuwiki (always deployed as it's part of the base setup) + deployed_count=3 # Dockge is always deployed + [ "$DEPLOY_CORE" = true ] && deployed_count=$((deployed_count + 1)) + [ "$DEPLOY_INFRASTRUCTURE" = true ] && deployed_count=$((deployed_count + 1)) + [ "$DEPLOY_DASHBOARDS" = true ] && deployed_count=$((deployed_count + 1)) + step_num=$((deployed_count + 1)) + log_info "Step $step_num: 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 + + # Replace domain placeholders in Dokuwiki + sed -i "s/\${DOMAIN}/${DOMAIN}/g" /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/ + fi + + if [ -d "$REPO_DIR/config-templates/dokuwiki/data" ]; then + cp -r "$REPO_DIR/config-templates/dokuwiki/data" /opt/stacks/dokuwiki/config/ + fi + + if [ -d "$REPO_DIR/config-templates/dokuwiki/keys" ]; then + cp -r "$REPO_DIR/config-templates/dokuwiki/keys" /opt/stacks/dokuwiki/config/ + 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 "" +} + +# Setup stacks for Dockge function +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 + mkdir -p "$STACK_DIR" + if [ -f "$REPO_STACK_DIR/docker-compose.yml" ]; then + cp "$REPO_STACK_DIR/docker-compose.yml" "$STACK_DIR/docker-compose.yml" + cp "$REPO_DIR/.env" "$STACK_DIR/.env" + log_success "Prepared $stack stack 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 "" +} + +# Main menu +show_main_menu() { + echo "==========================================" + echo " EZ-HOMELAB SETUP & DEPLOYMENT" + echo "==========================================" + echo "" + echo "What would you like to do?" + echo "" + echo "1) 🚀 Default Setup (Recommended)" + echo " - Deploy Dockge, core infrastructure, dashboards & monitoring" + echo " - All additional stacks prepared for Dockge" + echo "" + echo "2) 🏗️ Core Only" + echo " - Deploy Dockge and core infrastructure only" + echo " - All stacks prepared for Dockge" + echo "" + echo "3) 🔧 Infrastructure Only" + echo " - Deploy Dockge and monitoring tools" + echo " - Requires existing Traefik (from previous setup)" + echo " - Services accessible without authentication" + echo " - All stacks prepared for Dockge" + echo "" + echo "4) ❌ Exit" + echo "" +} + +# Main logic +main() { + log_info "EZ-Homelab Unified Setup & Deployment Script" + echo "" + + # Load existing configuration + ENV_EXISTS=false + if load_env_file; then + ENV_EXISTS=true + fi + + # Show main menu + show_main_menu + read -p "Choose an option (1-4): " MAIN_CHOICE + + case $MAIN_CHOICE in + 1) + log_info "Selected: Default Setup" + DEPLOY_CORE=true + DEPLOY_INFRASTRUCTURE=true + DEPLOY_DASHBOARDS=true + SETUP_STACKS=true + ;; + 2) + log_info "Selected: Core Only" + DEPLOY_CORE=true + DEPLOY_INFRASTRUCTURE=false + DEPLOY_DASHBOARDS=false + SETUP_STACKS=true + ;; + 3) + log_info "Selected: Infrastructure Only" + DEPLOY_CORE=false + DEPLOY_INFRASTRUCTURE=true + DEPLOY_DASHBOARDS=false + SETUP_STACKS=true + ;; + 4) + log_info "Exiting..." + exit 0 + ;; + *) + log_error "Invalid choice. Please run the script again." + exit 1 + ;; + esac + + echo "" + + # Check if system setup is needed + if [ "$EUID" -eq 0 ] || ! command -v docker &> /dev/null; then + log_info "Docker not found or running as root. Performing system setup first..." + system_setup "$@" + echo "" + log_info "System setup complete. Please log out and back in, then run this script again." + exit 0 + fi + + # Prompt for configuration values + prompt_for_values + + # Save configuration + save_env_file + + # Perform deployment + perform_deployment + + # Show completion message + echo "" + echo "==========================================" + log_success "Setup and deployment completed successfully!" + echo "==========================================" + echo "" + + if [ "$DEPLOY_INFRASTRUCTURE" = true ]; then + log_info "Access your services:" + echo "" + echo " 🚀 Dockge: https://dockge.${DOMAIN}" + [ "$DEPLOY_CORE" = true ] && echo " 🔒 Authelia: https://auth.${DOMAIN}" + [ "$DEPLOY_CORE" = true ] && echo " 🔀 Traefik: https://traefik.${DOMAIN}" + echo " 📊 Homepage: https://home.${DOMAIN}" + echo " 🎯 Homarr: https://homarr.${DOMAIN}" + echo " 📖 Wiki: https://wiki.${DOMAIN}" + echo "" + fi + + log_info "Next steps:" + echo "" + echo " 1. Access Dockge at https://dockge.${DOMAIN}" + if [ "$DEPLOY_CORE" = true ]; then + echo " (Use your Authelia credentials: ${AUTHELIA_ADMIN_USER})" + fi + echo "" + echo " 2. Start additional stacks from Dockge's web UI" + echo "" + echo " 3. 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 "" +} + +# Run main function +main "$@" \ No newline at end of file