Refactor scripts for improved maintainability
- setup-homelab.sh: Fixed syntax errors, placeholder detection, and hardcoded paths - deploy-homelab.sh: Refactored from inline code to function-based structure - Both scripts now use consistent function organization for better readability - Enhanced credential handling and error checking - All scripts validated for syntax correctness
This commit is contained in:
@@ -30,27 +30,11 @@ log_error() {
|
|||||||
echo -e "${RED}[ERROR]${NC} $1"
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if running as root
|
#==========================================
|
||||||
if [ "$EUID" -ne 0 ]; then
|
# VALIDATION FUNCTIONS
|
||||||
log_error "Please run as root (use: sudo ./deploy-homelab.sh)"
|
#==========================================
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get the actual user who invoked sudo
|
|
||||||
ACTUAL_USER="${SUDO_USER:-$USER}"
|
|
||||||
if [ "$ACTUAL_USER" = "root" ]; then
|
|
||||||
log_error "Please run this script with sudo, not as root user"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Get script directory (AI-Homelab/scripts)
|
|
||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
||||||
REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )"
|
|
||||||
|
|
||||||
log_info "AI-Homelab Deployment Script"
|
|
||||||
log_info "Running as user: $ACTUAL_USER"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
|
validate_prerequisites() {
|
||||||
# Check if .env file exists
|
# Check if .env file exists
|
||||||
if [ ! -f "$REPO_DIR/.env" ]; then
|
if [ ! -f "$REPO_DIR/.env" ]; then
|
||||||
log_error ".env file not found!"
|
log_error ".env file not found!"
|
||||||
@@ -67,8 +51,8 @@ log_info "Validating Docker installation..."
|
|||||||
if ! command -v docker &> /dev/null; then
|
if ! command -v docker &> /dev/null; then
|
||||||
log_error "Docker is not installed"
|
log_error "Docker is not installed"
|
||||||
log_info "Please run the setup script first:"
|
log_info "Please run the setup script first:"
|
||||||
log_info " cd ~/AI-Homelab/scripts"
|
log_info " cd $REPO_DIR"
|
||||||
log_info " sudo ./setup-homelab.sh"
|
log_info " sudo ./scripts/setup-homelab.sh"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -102,82 +86,31 @@ fi
|
|||||||
|
|
||||||
log_info "Using domain: $DOMAIN"
|
log_info "Using domain: $DOMAIN"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 1: Create required directories
|
#==========================================
|
||||||
log_info "Step 1/5: Creating required directories..."
|
# DEPLOYMENT STEP FUNCTIONS
|
||||||
|
#==========================================
|
||||||
|
|
||||||
|
step_1_create_directories() {
|
||||||
|
log_info "Step 1/7: Creating required directories..."
|
||||||
mkdir -p /opt/stacks/core
|
mkdir -p /opt/stacks/core
|
||||||
mkdir -p /opt/stacks/infrastructure
|
mkdir -p /opt/stacks/infrastructure
|
||||||
mkdir -p /opt/dockge/data
|
mkdir -p /opt/dockge/data
|
||||||
log_success "Directories created"
|
log_success "Directories created"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 2: Create Docker networks (if they don't exist)
|
step_2_create_networks() {
|
||||||
log_info "Step 2/5: Creating Docker networks..."
|
log_info "Step 2/7: 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 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 traefik-network 2>/dev/null && log_success "Created traefik-network" || log_info "traefik-network already exists"
|
||||||
docker network create dockerproxy-network 2>/dev/null && log_success "Created dockerproxy-network" || log_info "dockerproxy-network already exists"
|
docker network create dockerproxy-network 2>/dev/null && log_success "Created dockerproxy-network" || log_info "dockerproxy-network already exists"
|
||||||
docker network create media-network 2>/dev/null && log_success "Created media-network" || log_info "media-network already exists"
|
docker network create media-network 2>/dev/null && log_success "Created media-network" || log_info "media-network already exists"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 3: Deploy core infrastructure (DuckDNS, Traefik, Authelia, Gluetun)
|
configure_authelia() {
|
||||||
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 ""
|
|
||||||
|
|
||||||
# Copy core stack files
|
|
||||||
log_info "Preparing core stack configuration files..."
|
|
||||||
|
|
||||||
# Safety: Stop existing core stack if running (prevents file conflicts)
|
|
||||||
if [ -f "/opt/stacks/core/docker-compose.yml" ]; then
|
|
||||||
log_info "Stopping existing core stack for safe reconfiguration..."
|
|
||||||
cd /opt/stacks/core && docker compose down 2>/dev/null || true
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up any incorrect directory structure from previous runs
|
|
||||||
if [ -d "/opt/stacks/core/traefik/acme.json" ]; then
|
|
||||||
log_warning "Removing incorrectly created acme.json directory"
|
|
||||||
rm -rf /opt/stacks/core/traefik/acme.json
|
|
||||||
fi
|
|
||||||
if [ -d "/opt/stacks/core/traefik/traefik.yml" ]; then
|
|
||||||
log_warning "Removing incorrectly created traefik.yml directory"
|
|
||||||
rm -rf /opt/stacks/core/traefik/traefik.yml
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Copy compose file
|
|
||||||
cp "$REPO_DIR/docker-compose/core.yml" /opt/stacks/core/docker-compose.yml
|
|
||||||
|
|
||||||
# Safely remove and replace config directories
|
|
||||||
if [ -d "/opt/stacks/core/traefik" ]; then
|
|
||||||
rm -rf /opt/stacks/core/traefik
|
|
||||||
fi
|
|
||||||
if [ -d "/opt/stacks/core/authelia" ]; then
|
|
||||||
rm -rf /opt/stacks/core/authelia
|
|
||||||
fi
|
|
||||||
|
|
||||||
cp -r "$REPO_DIR/config-templates/traefik" /opt/stacks/core/
|
|
||||||
cp -r "$REPO_DIR/config-templates/authelia" /opt/stacks/core/
|
|
||||||
|
|
||||||
cp "$REPO_DIR/.env" /opt/stacks/core/.env
|
|
||||||
|
|
||||||
# Create acme.json as a file (not directory) with correct permissions
|
|
||||||
log_info "Creating acme.json for SSL certificates..."
|
|
||||||
touch /opt/stacks/core/traefik/acme.json
|
|
||||||
chmod 600 /opt/stacks/core/traefik/acme.json
|
|
||||||
log_success "acme.json created with correct permissions"
|
|
||||||
|
|
||||||
# Replace email placeholder in traefik.yml
|
|
||||||
log_info "Configuring Traefik with email: $ACME_EMAIL..."
|
|
||||||
sed -i "s/ACME_EMAIL_PLACEHOLDER/${ACME_EMAIL}/g" /opt/stacks/core/traefik/traefik.yml
|
|
||||||
log_success "Traefik email configured"
|
|
||||||
|
|
||||||
# Replace domain placeholder in authelia configuration
|
|
||||||
log_info "Configuring Authelia for domain: $DOMAIN..."
|
|
||||||
sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml
|
|
||||||
|
|
||||||
# Configure Authelia admin user from setup script
|
# Configure Authelia admin user from setup script
|
||||||
if [ -f /opt/stacks/.setup-temp/authelia_admin_credentials.tmp ] && [ -f /opt/stacks/.setup-temp/authelia_password_hash.tmp ]; then
|
if [ -f /opt/stacks/.setup-temp/authelia_admin_credentials.tmp ] && [ -f /opt/stacks/.setup-temp/authelia_password_hash.tmp ]; then
|
||||||
log_info "Loading Authelia admin credentials from setup temp files..."
|
log_info "Loading Authelia admin credentials from setup temp files..."
|
||||||
@@ -283,6 +216,15 @@ PYTHON_EOF
|
|||||||
chmod 600 /opt/stacks/core/authelia/ADMIN_PASSWORD.txt
|
chmod 600 /opt/stacks/core/authelia/ADMIN_PASSWORD.txt
|
||||||
chown $ACTUAL_USER:$ACTUAL_USER /opt/stacks/core/authelia/ADMIN_PASSWORD.txt
|
chown $ACTUAL_USER:$ACTUAL_USER /opt/stacks/core/authelia/ADMIN_PASSWORD.txt
|
||||||
log_info "Password also saved to: /opt/stacks/core/authelia/ADMIN_PASSWORD.txt"
|
log_info "Password also saved to: /opt/stacks/core/authelia/ADMIN_PASSWORD.txt"
|
||||||
|
|
||||||
|
# Save full credentials for later reference
|
||||||
|
{
|
||||||
|
echo "Username: $ADMIN_USER"
|
||||||
|
echo "Password: $ADMIN_PASSWORD"
|
||||||
|
echo "Email: $ADMIN_EMAIL"
|
||||||
|
} > /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt
|
||||||
|
chmod 600 /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt
|
||||||
|
chown $ACTUAL_USER:$ACTUAL_USER /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Clean up credentials files from setup script
|
# Clean up credentials files from setup script
|
||||||
@@ -299,6 +241,68 @@ else
|
|||||||
log_info "Using template users_database.yml from config-templates"
|
log_info "Using template users_database.yml from config-templates"
|
||||||
log_info "Please run setup-homelab.sh first or configure manually"
|
log_info "Please run setup-homelab.sh first or configure manually"
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
step_3_deploy_core() {
|
||||||
|
log_info "Step 3/7: 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
|
||||||
|
log_info "Preparing core stack configuration files..."
|
||||||
|
|
||||||
|
# Safety: Stop existing core stack if running (prevents file conflicts)
|
||||||
|
if [ -f "/opt/stacks/core/docker-compose.yml" ]; then
|
||||||
|
log_info "Stopping existing core stack for safe reconfiguration..."
|
||||||
|
cd /opt/stacks/core && docker compose down 2>/dev/null || true
|
||||||
|
sleep 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up any incorrect directory structure from previous runs
|
||||||
|
if [ -d "/opt/stacks/core/traefik/acme.json" ]; then
|
||||||
|
log_warning "Removing incorrectly created acme.json directory"
|
||||||
|
rm -rf /opt/stacks/core/traefik/acme.json
|
||||||
|
fi
|
||||||
|
if [ -d "/opt/stacks/core/traefik/traefik.yml" ]; then
|
||||||
|
log_warning "Removing incorrectly created traefik.yml directory"
|
||||||
|
rm -rf /opt/stacks/core/traefik/traefik.yml
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy compose file
|
||||||
|
cp "$REPO_DIR/docker-compose/core.yml" /opt/stacks/core/docker-compose.yml
|
||||||
|
|
||||||
|
# Safely remove and replace config directories
|
||||||
|
if [ -d "/opt/stacks/core/traefik" ]; then
|
||||||
|
rm -rf /opt/stacks/core/traefik
|
||||||
|
fi
|
||||||
|
if [ -d "/opt/stacks/core/authelia" ]; then
|
||||||
|
rm -rf /opt/stacks/core/authelia
|
||||||
|
fi
|
||||||
|
|
||||||
|
cp -r "$REPO_DIR/config-templates/traefik" /opt/stacks/core/
|
||||||
|
cp -r "$REPO_DIR/config-templates/authelia" /opt/stacks/core/
|
||||||
|
cp "$REPO_DIR/.env" /opt/stacks/core/.env
|
||||||
|
|
||||||
|
# Create acme.json as a file (not directory) with correct permissions
|
||||||
|
log_info "Creating acme.json for SSL certificates..."
|
||||||
|
touch /opt/stacks/core/traefik/acme.json
|
||||||
|
chmod 600 /opt/stacks/core/traefik/acme.json
|
||||||
|
log_success "acme.json created with correct permissions"
|
||||||
|
|
||||||
|
# Replace email placeholder in traefik.yml
|
||||||
|
log_info "Configuring Traefik with email: $ACME_EMAIL..."
|
||||||
|
sed -i "s/ACME_EMAIL_PLACEHOLDER/${ACME_EMAIL}/g" /opt/stacks/core/traefik/traefik.yml
|
||||||
|
log_success "Traefik email configured"
|
||||||
|
|
||||||
|
# Replace domain placeholder in authelia configuration
|
||||||
|
log_info "Configuring Authelia for domain: $DOMAIN..."
|
||||||
|
sed -i "s/your-domain.duckdns.org/${DOMAIN}/g" /opt/stacks/core/authelia/configuration.yml
|
||||||
|
|
||||||
|
# Configure Authelia admin user
|
||||||
|
configure_authelia
|
||||||
|
|
||||||
# Clean up old Authelia database if encryption key changed
|
# Clean up old Authelia database if encryption key changed
|
||||||
# This prevents "encryption key does not appear to be valid" errors
|
# This prevents "encryption key does not appear to be valid" errors
|
||||||
@@ -329,9 +333,10 @@ else
|
|||||||
log_warning "Traefik container check inconclusive, continuing..."
|
log_warning "Traefik container check inconclusive, continuing..."
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 4: Deploy infrastructure stack (Dockge and monitoring tools)
|
step_4_deploy_infrastructure() {
|
||||||
log_info "Step 4/6: Deploying infrastructure stack..."
|
log_info "Step 4/7: Deploying infrastructure stack..."
|
||||||
log_info " - Dockge (Docker Compose Manager)"
|
log_info " - Dockge (Docker Compose Manager)"
|
||||||
log_info " - Pi-hole (DNS Ad Blocker)"
|
log_info " - Pi-hole (DNS Ad Blocker)"
|
||||||
log_info " - Dozzle (Log Viewer)"
|
log_info " - Dozzle (Log Viewer)"
|
||||||
@@ -350,9 +355,10 @@ docker compose up -d
|
|||||||
|
|
||||||
log_success "Infrastructure stack deployed"
|
log_success "Infrastructure stack deployed"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 5: Deploy dashboards stack (Homepage and Homarr)
|
step_5_deploy_dashboards() {
|
||||||
log_info "Step 5/6: Deploying dashboards stack..."
|
log_info "Step 5/7: Deploying dashboards stack..."
|
||||||
log_info " - Homepage (AI-configurable Dashboard)"
|
log_info " - Homepage (AI-configurable Dashboard)"
|
||||||
log_info " - Homarr (Modern Dashboard)"
|
log_info " - Homarr (Modern Dashboard)"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -379,8 +385,9 @@ docker compose up -d
|
|||||||
|
|
||||||
log_success "Dashboards stack deployed"
|
log_success "Dashboards stack deployed"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 6: Deploy additional stacks to Dockge (not started)
|
step_6_prepare_additional_stacks() {
|
||||||
log_info "Step 6/7: Preparing additional stacks for Dockge..."
|
log_info "Step 6/7: Preparing additional stacks for Dockge..."
|
||||||
echo ""
|
echo ""
|
||||||
log_info "The following stacks can be deployed through Dockge's web UI:"
|
log_info "The following stacks can be deployed through Dockge's web UI:"
|
||||||
@@ -416,8 +423,9 @@ done
|
|||||||
log_success "Additional stacks prepared in Dockge"
|
log_success "Additional stacks prepared in Dockge"
|
||||||
log_info "These stacks are NOT started - deploy them via Dockge web UI as needed"
|
log_info "These stacks are NOT started - deploy them via Dockge web UI as needed"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 7: Wait for Dockge to be ready and open browser
|
step_7_wait_for_dockge() {
|
||||||
log_info "Step 7/7: Waiting for Dockge web UI to be ready..."
|
log_info "Step 7/7: Waiting for Dockge web UI to be ready..."
|
||||||
|
|
||||||
DOCKGE_URL="https://dockge.${DOMAIN}"
|
DOCKGE_URL="https://dockge.${DOMAIN}"
|
||||||
@@ -469,15 +477,17 @@ else
|
|||||||
log_warning "No browser detected. Please manually open: $DOCKGE_URL"
|
log_warning "No browser detected. Please manually open: $DOCKGE_URL"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
show_final_summary() {
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
log_success "Deployment completed successfully!"
|
log_success "Deployment completed successfully!"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Access your services:"
|
log_info "Access your services:"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 🚀 Dockge: $DOCKGE_URL"
|
echo " 🚀 Dockge: https://dockge.${DOMAIN}"
|
||||||
echo " 🔒 Authelia: https://auth.${DOMAIN}"
|
echo " 🔒 Authelia: https://auth.${DOMAIN}"
|
||||||
echo " 🔀 Traefik: https://traefik.${DOMAIN}"
|
echo " 🔀 Traefik: https://traefik.${DOMAIN}"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -485,13 +495,12 @@ log_info "SSL Certificates:"
|
|||||||
echo " 📝 Let's Encrypt certificates will be acquired automatically within 2-5 minutes"
|
echo " 📝 Let's Encrypt certificates will be acquired automatically within 2-5 minutes"
|
||||||
echo " ⚠️ Initial access uses self-signed certs (browser warning is normal)"
|
echo " ⚠️ Initial access uses self-signed certs (browser warning is normal)"
|
||||||
echo " 🔓 Ensure ports 80/443 are accessible from internet for Let's Encrypt"
|
echo " 🔓 Ensure ports 80/443 are accessible from internet for Let's Encrypt"
|
||||||
echo " 💾 Admin password saved to: /opt/stacks/core/authelia/ADMIN_PASSWORD.txt"
|
echo " 💾 Admin credentials saved to: /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt"
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Next steps:"
|
log_info "Next steps:"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 1. Log in to Dockge using your Authelia credentials"
|
echo " 1. Log in to Dockge using your Authelia credentials"
|
||||||
echo " Username: admin"
|
echo " (saved in /opt/stacks/core/authelia/ADMIN_CREDENTIALS.txt)"
|
||||||
echo " Password: (saved in /opt/stacks/core/authelia/ADMIN_PASSWORD.txt)"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo " 2. Deploy additional stacks through Dockge's web UI:"
|
echo " 2. Deploy additional stacks through Dockge's web UI:"
|
||||||
echo " - media.yml (Jellyfin, Calibre-web, qBittorrent)"
|
echo " - media.yml (Jellyfin, Calibre-web, qBittorrent)"
|
||||||
@@ -503,8 +512,8 @@ echo " - utilities.yml (Backups, code editors, etc.)"
|
|||||||
echo " - alternatives.yml (Portainer, Authentik - optional)"
|
echo " - alternatives.yml (Portainer, Authentik - optional)"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 3. Access your dashboards:"
|
echo " 3. Access your dashboards:"
|
||||||
echo " \ud83c\udfe0 Homepage: https://home.${DOMAIN}"
|
echo " 🏠 Homepage: https://home.${DOMAIN}"
|
||||||
echo " \ud83d\udcca Homarr: https://homarr.${DOMAIN}"
|
echo " 📊 Homarr: https://homarr.${DOMAIN}"
|
||||||
echo ""
|
echo ""
|
||||||
echo " 4. Configure services via the AI assistant in VS Code"
|
echo " 4. Configure services via the AI assistant in VS Code"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -513,3 +522,40 @@ echo ""
|
|||||||
log_info "For documentation, see: $REPO_DIR/docs/"
|
log_info "For documentation, see: $REPO_DIR/docs/"
|
||||||
log_info "For troubleshooting, see: $REPO_DIR/docs/quick-reference.md"
|
log_info "For troubleshooting, see: $REPO_DIR/docs/quick-reference.md"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
#==========================================
|
||||||
|
# MAIN EXECUTION
|
||||||
|
#==========================================
|
||||||
|
|
||||||
|
# Check if running as root
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
log_error "Please run as root (use: sudo ./deploy-homelab.sh)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the actual user who invoked sudo
|
||||||
|
ACTUAL_USER="${SUDO_USER:-$USER}"
|
||||||
|
if [ "$ACTUAL_USER" = "root" ]; then
|
||||||
|
log_error "Please run this script with sudo, not as root user"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get script directory (AI-Homelab/scripts)
|
||||||
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )"
|
||||||
|
|
||||||
|
log_info "AI-Homelab Deployment Script"
|
||||||
|
log_info "Running as user: $ACTUAL_USER"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Execute deployment steps
|
||||||
|
validate_prerequisites
|
||||||
|
step_1_create_directories
|
||||||
|
step_2_create_networks
|
||||||
|
step_3_deploy_core
|
||||||
|
step_4_deploy_infrastructure
|
||||||
|
step_5_deploy_dashboards
|
||||||
|
step_6_prepare_additional_stacks
|
||||||
|
step_7_wait_for_dockge
|
||||||
|
show_final_summary
|
||||||
|
|||||||
@@ -1,15 +1,44 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# AI-Homelab First-Run Setup Script
|
# AI-Homelab First-Run Setup Script
|
||||||
# This script prepares a fresh Debian installation for homelab deployment
|
# This script prepares a fresh Debian installation for homelab deployment
|
||||||
# Run as: sudo ./setup-homelab.sh
|
# Run as: sudo ./setup-homelab.sh [--yes]
|
||||||
|
# Options:
|
||||||
|
# --yes Skip all confirmation prompts (for automated deployments)
|
||||||
|
|
||||||
set -e # Exit on error
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
AUTO_YES=false
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
--yes|-y)
|
||||||
|
AUTO_YES=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
echo "Usage: sudo ./setup-homelab.sh [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Options:"
|
||||||
|
echo " --yes, -y Skip all confirmation prompts"
|
||||||
|
echo " --help, -h Show this help message"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $arg"
|
||||||
|
echo "Run with --help for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
# Colors for output
|
# Colors for output
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
BLUE='\033[0;34m'
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
MAGENTA='\033[0;35m'
|
||||||
|
BOLD='\033[1m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
# Log functions
|
# Log functions
|
||||||
@@ -29,41 +58,121 @@ log_error() {
|
|||||||
echo -e "${RED}[ERROR]${NC} $1"
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if running as root
|
log_progress() {
|
||||||
|
echo -e "${CYAN}[PROGRESS]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Colorized prompt function
|
||||||
|
prompt_user() {
|
||||||
|
local prompt_text="$1"
|
||||||
|
local default="${2:-}"
|
||||||
|
echo -e "${MAGENTA}${BOLD}[PROMPT]${NC} ${prompt_text}"
|
||||||
|
if [ -n "$default" ]; then
|
||||||
|
echo -e "${CYAN}(default: $default)${NC}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Confirmation function that respects --yes flag
|
||||||
|
confirm() {
|
||||||
|
local prompt="$1"
|
||||||
|
if [ "$AUTO_YES" = true ]; then
|
||||||
|
log_info "Auto-confirmed: $prompt"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
prompt_user "$prompt"
|
||||||
|
read -p "Continue? (y/N): " -n 1 -r
|
||||||
|
echo ""
|
||||||
|
[[ $REPLY =~ ^[Yy]$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if running with elevated privileges
|
||||||
if [ "$EUID" -ne 0 ]; then
|
if [ "$EUID" -ne 0 ]; then
|
||||||
log_error "Please run as root (use: sudo ./setup-homelab.sh)"
|
log_error "Please run with sudo: sudo ./setup-homelab.sh"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Get the actual user who invoked sudo
|
# Get the actual user who invoked sudo
|
||||||
ACTUAL_USER="${SUDO_USER:-$USER}"
|
ACTUAL_USER="${SUDO_USER:-$USER}"
|
||||||
if [ "$ACTUAL_USER" = "root" ]; then
|
|
||||||
log_error "Please run this script with sudo, not as root user"
|
#==========================================
|
||||||
|
# STEP FUNCTIONS
|
||||||
|
#==========================================
|
||||||
|
|
||||||
|
# Handle root user scenario - grant sudo and exit
|
||||||
|
handle_root_user() {
|
||||||
|
log_info "Running as root - checking for non-root users..."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Find non-root users with home directories and UID >= 1000
|
||||||
|
AVAILABLE_USERS=$(awk -F: '$3 >= 1000 && $3 < 65534 && $1 != "nobody" {print $1}' /etc/passwd)
|
||||||
|
|
||||||
|
if [ -z "$AVAILABLE_USERS" ]; then
|
||||||
|
log_error "No non-root user found on this system"
|
||||||
|
log_info "Please create a user first:"
|
||||||
|
log_info " adduser <username>"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Add trap for cleanup on error
|
# If only one user, use that one
|
||||||
cleanup_on_error() {
|
USER_COUNT=$(echo "$AVAILABLE_USERS" | wc -l)
|
||||||
local exit_code=$?
|
if [ "$USER_COUNT" -eq 1 ]; then
|
||||||
if [ $exit_code -ne 0 ]; then
|
TARGET_USER="$AVAILABLE_USERS"
|
||||||
log_error "Script failed with exit code: $exit_code"
|
log_info "Found user: $TARGET_USER"
|
||||||
|
else
|
||||||
|
# Multiple users found, let root choose
|
||||||
|
log_info "Multiple users found:"
|
||||||
|
echo "$AVAILABLE_USERS" | nl
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Partial setup may have occurred. To resume:"
|
read -p "Enter username to grant sudo access: " TARGET_USER
|
||||||
log_info " 1. Review error messages above"
|
|
||||||
log_info " 2. Fix the issue if possible"
|
# Validate the entered username
|
||||||
log_info " 3. Re-run: sudo ./setup-homelab.sh"
|
if ! echo "$AVAILABLE_USERS" | grep -q "^${TARGET_USER}$"; then
|
||||||
echo ""
|
log_error "Invalid username: $TARGET_USER"
|
||||||
log_info "The script is designed to be idempotent (safe to re-run)"
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if user already has sudo
|
||||||
|
if groups "$TARGET_USER" | grep -q '\bsudo\b'; then
|
||||||
|
log_success "User $TARGET_USER already has sudo access"
|
||||||
|
else
|
||||||
|
# Confirm before granting sudo
|
||||||
|
echo ""
|
||||||
|
read -p "Grant sudo access to $TARGET_USER? (y/N): " CONFIRM
|
||||||
|
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
|
||||||
|
usermod -aG sudo "$TARGET_USER"
|
||||||
|
log_success "Sudo access granted to $TARGET_USER"
|
||||||
|
else
|
||||||
|
log_error "Cannot proceed without sudo access"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exit with instructions
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
log_success "Setup preparation complete!"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
log_info "Next steps:"
|
||||||
|
echo ""
|
||||||
|
log_info " 1. Log in as $TARGET_USER:"
|
||||||
|
echo " su - $TARGET_USER"
|
||||||
|
echo ""
|
||||||
|
log_info " 2. Navigate to the AI-Homelab directory and run setup again:"
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
echo " cd $REPO_DIR"
|
||||||
|
echo " sudo ./scripts/setup-homelab.sh"
|
||||||
|
echo ""
|
||||||
|
log_warning "Note: You may need to log out and back in for sudo access to take effect"
|
||||||
|
echo ""
|
||||||
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
trap cleanup_on_error EXIT
|
step_0_preflight_checks() {
|
||||||
|
log_info "Step 0/$STEPS_TOTAL: Running pre-flight checks..."
|
||||||
log_info "Setting up AI-Homelab for user: $ACTUAL_USER"
|
log_progress "Starting setup process"
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Step 0: Pre-flight validation
|
|
||||||
log_info "Step 0/10: Running pre-flight checks..."
|
|
||||||
|
|
||||||
# Check internet connectivity
|
# Check internet connectivity
|
||||||
if ! ping -c 1 -W 2 8.8.8.8 &> /dev/null && ! ping -c 1 -W 2 1.1.1.1 &> /dev/null; then
|
if ! ping -c 1 -W 2 8.8.8.8 &> /dev/null && ! ping -c 1 -W 2 1.1.1.1 &> /dev/null; then
|
||||||
@@ -88,16 +197,22 @@ fi
|
|||||||
log_success "Pre-flight checks passed"
|
log_success "Pre-flight checks passed"
|
||||||
log_info "Internet: Connected"
|
log_info "Internet: Connected"
|
||||||
log_info "Disk space: $(($AVAILABLE_SPACE / 1024 / 1024))GB available"
|
log_info "Disk space: $(($AVAILABLE_SPACE / 1024 / 1024))GB available"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 1: System Update
|
step_1_update_system() {
|
||||||
log_info "Step 1/10: Updating system packages..."
|
log_info "Step 1/$STEPS_TOTAL: Updating system packages..."
|
||||||
apt-get update && apt-get upgrade -y
|
apt-get update && apt-get upgrade -y
|
||||||
log_success "System updated successfully"
|
log_success "System updated successfully"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 2: Install Required Packages
|
step_2_install_packages() {
|
||||||
log_info "Step 2/10: Installing required packages..."
|
log_info "Step 2/$STEPS_TOTAL: Installing required packages..."
|
||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
apt-transport-https \
|
apt-transport-https \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
@@ -113,10 +228,13 @@ apt-get install -y \
|
|||||||
ufw
|
ufw
|
||||||
|
|
||||||
log_success "Required packages installed"
|
log_success "Required packages installed"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 3: Install Docker
|
step_3_install_docker() {
|
||||||
log_info "Step 3/10: Installing Docker..."
|
log_info "Step 3/$STEPS_TOTAL: Installing Docker..."
|
||||||
if command -v docker &> /dev/null && docker --version &> /dev/null; then
|
if command -v docker &> /dev/null && docker --version &> /dev/null; then
|
||||||
log_warning "Docker is already installed ($(docker --version))"
|
log_warning "Docker is already installed ($(docker --version))"
|
||||||
else
|
else
|
||||||
@@ -137,10 +255,13 @@ else
|
|||||||
|
|
||||||
log_success "Docker installed successfully ($(docker --version))"
|
log_success "Docker installed successfully ($(docker --version))"
|
||||||
fi
|
fi
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 4: Configure User Groups
|
step_4_configure_user_groups() {
|
||||||
log_info "Step 4/10: Configuring user groups..."
|
log_info "Step 4/$STEPS_TOTAL: Configuring user groups..."
|
||||||
|
|
||||||
# Add user to sudo group if not already
|
# Add user to sudo group if not already
|
||||||
if groups "$ACTUAL_USER" | grep -q '\bsudo\b'; then
|
if groups "$ACTUAL_USER" | grep -q '\bsudo\b'; then
|
||||||
@@ -157,10 +278,13 @@ else
|
|||||||
usermod -aG docker "$ACTUAL_USER"
|
usermod -aG docker "$ACTUAL_USER"
|
||||||
log_success "User $ACTUAL_USER added to docker group"
|
log_success "User $ACTUAL_USER added to docker group"
|
||||||
fi
|
fi
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 5: Configure Firewall
|
step_5_configure_firewall() {
|
||||||
log_info "Step 5/10: Configuring firewall..."
|
log_info "Step 5/$STEPS_TOTAL: Configuring firewall..."
|
||||||
# Enable UFW if not already enabled
|
# Enable UFW if not already enabled
|
||||||
if ufw status | grep -q "Status: active"; then
|
if ufw status | grep -q "Status: active"; then
|
||||||
log_warning "Firewall is already active"
|
log_warning "Firewall is already active"
|
||||||
@@ -181,10 +305,13 @@ fi
|
|||||||
ufw allow 80/tcp
|
ufw allow 80/tcp
|
||||||
ufw allow 443/tcp
|
ufw allow 443/tcp
|
||||||
log_success "HTTP/HTTPS ports allowed in firewall"
|
log_success "HTTP/HTTPS ports allowed in firewall"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 6: Configure SSH
|
step_6_configure_ssh() {
|
||||||
log_info "Step 6/10: Configuring SSH server..."
|
log_info "Step 6/$STEPS_TOTAL: Configuring SSH server..."
|
||||||
systemctl enable ssh
|
systemctl enable ssh
|
||||||
systemctl start ssh
|
systemctl start ssh
|
||||||
|
|
||||||
@@ -196,9 +323,13 @@ if systemctl is-active --quiet ssh; then
|
|||||||
else
|
else
|
||||||
log_warning "SSH server failed to start, check configuration"
|
log_warning "SSH server failed to start, check configuration"
|
||||||
fi
|
fi
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
# Step 7: Generate Authelia Secrets
|
}
|
||||||
log_info "Step 7/10: Generating Authelia authentication secrets..."
|
|
||||||
|
step_7_generate_authelia_secrets() {
|
||||||
|
log_info "Step 7/$STEPS_TOTAL: Generating Authelia authentication secrets..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Validate Docker is available for password hash generation
|
# Validate Docker is available for password hash generation
|
||||||
@@ -217,14 +348,8 @@ fi
|
|||||||
log_success "Docker is available for password operations"
|
log_success "Docker is available for password operations"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Function to generate a secure random secret
|
|
||||||
generate_secret() {
|
|
||||||
openssl rand -hex 64
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if .env file exists in the repo
|
# Check if .env file exists in the repo
|
||||||
ACTUAL_USER_HOME=$(eval echo ~$ACTUAL_USER)
|
REPO_ENV_FILE="$REPO_DIR/.env"
|
||||||
REPO_ENV_FILE="$ACTUAL_USER_HOME/AI-Homelab/.env"
|
|
||||||
if [ ! -f "$REPO_ENV_FILE" ]; then
|
if [ ! -f "$REPO_ENV_FILE" ]; then
|
||||||
log_error ".env file not found at $REPO_ENV_FILE"
|
log_error ".env file not found at $REPO_ENV_FILE"
|
||||||
log_info "Please create .env file from .env.example first"
|
log_info "Please create .env file from .env.example first"
|
||||||
@@ -233,50 +358,25 @@ fi
|
|||||||
|
|
||||||
# Check if secrets are already set (not placeholder values)
|
# Check if secrets are already set (not placeholder values)
|
||||||
CURRENT_JWT=$(grep "^AUTHELIA_JWT_SECRET=" "$REPO_ENV_FILE" | cut -d'=' -f2)
|
CURRENT_JWT=$(grep "^AUTHELIA_JWT_SECRET=" "$REPO_ENV_FILE" | cut -d'=' -f2)
|
||||||
if [ -n "$CURRENT_JWT" ] && [ "$CURRENT_JWT" != "your-jwt-secret-here" ] && [ ${#CURRENT_JWT} -ge 64 ]; then
|
if [ -n "$CURRENT_JWT" ] && [ "$CURRENT_JWT" != "your-jwt-secret-here" ] && [ "$CURRENT_JWT" != "generate-with-openssl-rand-hex-64" ] && [ ${#CURRENT_JWT} -ge 64 ]; then
|
||||||
log_warning "Authelia secrets appear to already be set in .env"
|
log_warning "Authelia secrets appear to already be set in .env"
|
||||||
read -p "Do you want to regenerate them? (y/N): " -n 1 -r
|
if [ "$AUTO_YES" = true ]; then
|
||||||
echo ""
|
log_info "Auto-confirmed: Keeping existing secrets (--yes mode)"
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
elif confirm "Regenerate Authelia secrets?"; then
|
||||||
log_info "Keeping existing secrets"
|
generate_new_secrets
|
||||||
else
|
else
|
||||||
# Generate new secrets
|
log_info "Keeping existing secrets"
|
||||||
log_info "Generating new JWT secret..."
|
|
||||||
JWT_SECRET=$(generate_secret)
|
|
||||||
log_info "Generating new session secret..."
|
|
||||||
SESSION_SECRET=$(generate_secret)
|
|
||||||
log_info "Generating new storage encryption key..."
|
|
||||||
ENCRYPTION_KEY=$(generate_secret)
|
|
||||||
|
|
||||||
# Update .env file
|
|
||||||
sed -i "s|^AUTHELIA_JWT_SECRET=.*|AUTHELIA_JWT_SECRET=${JWT_SECRET}|" "$REPO_ENV_FILE"
|
|
||||||
sed -i "s|^AUTHELIA_SESSION_SECRET=.*|AUTHELIA_SESSION_SECRET=${SESSION_SECRET}|" "$REPO_ENV_FILE"
|
|
||||||
sed -i "s|^AUTHELIA_STORAGE_ENCRYPTION_KEY=.*|AUTHELIA_STORAGE_ENCRYPTION_KEY=${ENCRYPTION_KEY}|" "$REPO_ENV_FILE"
|
|
||||||
|
|
||||||
log_success "New secrets generated and saved to .env"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
# Generate secrets for first time
|
generate_new_secrets
|
||||||
log_info "Generating new JWT secret..."
|
|
||||||
JWT_SECRET=$(generate_secret)
|
|
||||||
log_info "Generating new session secret..."
|
|
||||||
SESSION_SECRET=$(generate_secret)
|
|
||||||
log_info "Generating new storage encryption key..."
|
|
||||||
ENCRYPTION_KEY=$(generate_secret)
|
|
||||||
|
|
||||||
# Update .env file
|
|
||||||
sed -i "s|^AUTHELIA_JWT_SECRET=.*|AUTHELIA_JWT_SECRET=${JWT_SECRET}|" "$REPO_ENV_FILE"
|
|
||||||
sed -i "s|^AUTHELIA_SESSION_SECRET=.*|AUTHELIA_SESSION_SECRET=${SESSION_SECRET}|" "$REPO_ENV_FILE"
|
|
||||||
sed -i "s|^AUTHELIA_STORAGE_ENCRYPTION_KEY=.*|AUTHELIA_STORAGE_ENCRYPTION_KEY=${ENCRYPTION_KEY}|" "$REPO_ENV_FILE"
|
|
||||||
|
|
||||||
log_success "Secrets generated and saved to .env"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Prompt for admin password
|
# Prompt for admin password
|
||||||
echo ""
|
echo ""
|
||||||
log_info "Setting up Authelia admin user..."
|
log_info "Setting up Authelia admin user..."
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Enter admin username (default: admin): " ADMIN_USER
|
prompt_user "Enter admin username" "admin"
|
||||||
|
read -p "> " ADMIN_USER
|
||||||
ADMIN_USER=${ADMIN_USER:-admin}
|
ADMIN_USER=${ADMIN_USER:-admin}
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
@@ -313,8 +413,7 @@ fi
|
|||||||
echo ""
|
echo ""
|
||||||
log_info "Generating password hash..."
|
log_info "Generating password hash..."
|
||||||
|
|
||||||
# Generate hash and write DIRECTLY to file to avoid bash variable expansion of $ characters
|
# Generate hash and write DIRECTLY to file
|
||||||
# The argon2 hash contains multiple $ characters that bash would try to expand as variables
|
|
||||||
timeout 60 docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | \
|
timeout 60 docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2 --password "$ADMIN_PASSWORD" 2>&1 | \
|
||||||
grep -oP 'Digest: \K\$argon2.*' > /tmp/authelia_password_hash.tmp
|
grep -oP 'Digest: \K\$argon2.*' > /tmp/authelia_password_hash.tmp
|
||||||
|
|
||||||
@@ -322,16 +421,9 @@ HASH_EXIT_CODE=$?
|
|||||||
|
|
||||||
if [ $HASH_EXIT_CODE -eq 124 ]; then
|
if [ $HASH_EXIT_CODE -eq 124 ]; then
|
||||||
log_error "Password hash generation timed out after 60 seconds"
|
log_error "Password hash generation timed out after 60 seconds"
|
||||||
log_info "This is unusual. Please check:"
|
|
||||||
log_info " 1. System resources: top or htop"
|
|
||||||
log_info " 2. Docker status: docker ps"
|
|
||||||
log_info " 3. Try manually: docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2"
|
|
||||||
exit 1
|
exit 1
|
||||||
elif [ $HASH_EXIT_CODE -ne 0 ] || [ ! -s /tmp/authelia_password_hash.tmp ]; then
|
elif [ $HASH_EXIT_CODE -ne 0 ] || [ ! -s /tmp/authelia_password_hash.tmp ]; then
|
||||||
log_error "Failed to generate password hash"
|
log_error "Failed to generate password hash"
|
||||||
log_info "You can generate the hash manually after setup:"
|
|
||||||
log_info " docker run --rm authelia/authelia:4.37 authelia crypto hash generate argon2"
|
|
||||||
log_info " Then edit: /opt/stacks/core/authelia/users_database.yml"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -340,18 +432,16 @@ log_success "Password hash generated successfully"
|
|||||||
|
|
||||||
# Read admin email from .env or prompt
|
# Read admin email from .env or prompt
|
||||||
ADMIN_EMAIL=$(grep "^ADMIN_EMAIL=" "$REPO_ENV_FILE" | cut -d'=' -f2)
|
ADMIN_EMAIL=$(grep "^ADMIN_EMAIL=" "$REPO_ENV_FILE" | cut -d'=' -f2)
|
||||||
if [ -z "$ADMIN_EMAIL" ] || [ "$ADMIN_EMAIL" = "admin@example.com" ]; then
|
if [ -z "$ADMIN_EMAIL" ] || [ "$ADMIN_EMAIL" = "admin@example.com" ] || [ "$ADMIN_EMAIL" = "your-email@example.com" ]; then
|
||||||
read -p "Enter admin email address: " ADMIN_EMAIL
|
prompt_user "Enter admin email address"
|
||||||
|
read -p "> " ADMIN_EMAIL
|
||||||
sed -i "s|^ADMIN_EMAIL=.*|ADMIN_EMAIL=${ADMIN_EMAIL}|" "$REPO_ENV_FILE"
|
sed -i "s|^ADMIN_EMAIL=.*|ADMIN_EMAIL=${ADMIN_EMAIL}|" "$REPO_ENV_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_success "Admin user configured: $ADMIN_USER"
|
log_success "Admin user configured: $ADMIN_USER"
|
||||||
log_success "Password hash generated and will be applied during deployment"
|
log_success "Password hash generated and will be applied during deployment"
|
||||||
|
|
||||||
# Store the admin credentials for the deployment script
|
# Store credentials
|
||||||
# Password hash is already in /tmp/authelia_password_hash.tmp (written directly from Docker)
|
|
||||||
# This avoids bash variable expansion issues with $ characters in argon2 hashes
|
|
||||||
# Store in /opt/stacks/ which is accessible across user contexts
|
|
||||||
mkdir -p /opt/stacks/.setup-temp
|
mkdir -p /opt/stacks/.setup-temp
|
||||||
{
|
{
|
||||||
echo "ADMIN_USER=$ADMIN_USER"
|
echo "ADMIN_USER=$ADMIN_USER"
|
||||||
@@ -360,11 +450,10 @@ mkdir -p /opt/stacks/.setup-temp
|
|||||||
} > /opt/stacks/.setup-temp/authelia_admin_credentials.tmp
|
} > /opt/stacks/.setup-temp/authelia_admin_credentials.tmp
|
||||||
chmod 600 /opt/stacks/.setup-temp/authelia_admin_credentials.tmp
|
chmod 600 /opt/stacks/.setup-temp/authelia_admin_credentials.tmp
|
||||||
|
|
||||||
# Copy password hash to persistent location
|
|
||||||
cp /tmp/authelia_password_hash.tmp /opt/stacks/.setup-temp/authelia_password_hash.tmp
|
cp /tmp/authelia_password_hash.tmp /opt/stacks/.setup-temp/authelia_password_hash.tmp
|
||||||
chmod 600 /opt/stacks/.setup-temp/authelia_password_hash.tmp
|
chmod 600 /opt/stacks/.setup-temp/authelia_password_hash.tmp
|
||||||
|
|
||||||
# Also save to .env file for persistence across reboots
|
# Save to .env file for persistence
|
||||||
log_info "Saving credentials to .env file for persistence..."
|
log_info "Saving credentials to .env file for persistence..."
|
||||||
sed -i "/^AUTHELIA_ADMIN_USER=/d" "$REPO_ENV_FILE"
|
sed -i "/^AUTHELIA_ADMIN_USER=/d" "$REPO_ENV_FILE"
|
||||||
sed -i "/^AUTHELIA_ADMIN_EMAIL=/d" "$REPO_ENV_FILE"
|
sed -i "/^AUTHELIA_ADMIN_EMAIL=/d" "$REPO_ENV_FILE"
|
||||||
@@ -377,10 +466,13 @@ echo "AUTHELIA_ADMIN_PASSWORD=$ADMIN_PASSWORD" >> "$REPO_ENV_FILE"
|
|||||||
log_success "Credentials saved to .env file"
|
log_success "Credentials saved to .env file"
|
||||||
|
|
||||||
log_info "Credentials saved for deployment script"
|
log_info "Credentials saved for deployment script"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 8: Create Directory Structure
|
step_8_create_directories() {
|
||||||
log_info "Step 8/10: Creating directory structure..."
|
log_info "Step 8/$STEPS_TOTAL: Creating directory structure..."
|
||||||
mkdir -p /opt/stacks
|
mkdir -p /opt/stacks
|
||||||
mkdir -p /opt/dockge/data
|
mkdir -p /opt/dockge/data
|
||||||
mkdir -p /mnt/media/{movies,tv,music,books,photos}
|
mkdir -p /mnt/media/{movies,tv,music,books,photos}
|
||||||
@@ -398,21 +490,26 @@ chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/backups
|
|||||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/surveillance
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/surveillance
|
||||||
chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/git
|
chown -R "$ACTUAL_USER:$ACTUAL_USER" /mnt/git
|
||||||
|
|
||||||
|
|
||||||
log_success "Directory structure created"
|
log_success "Directory structure created"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 9: Create Docker Networks
|
step_9_create_networks() {
|
||||||
log_info "Step 9/10: Creating Docker networks..."
|
log_info "Step 9/$STEPS_TOTAL: Creating Docker networks..."
|
||||||
su - "$ACTUAL_USER" -c "docker network create homelab-network 2>/dev/null || true"
|
su - "$ACTUAL_USER" -c "docker network create homelab-network 2>/dev/null || true"
|
||||||
su - "$ACTUAL_USER" -c "docker network create traefik-network 2>/dev/null || true"
|
su - "$ACTUAL_USER" -c "docker network create traefik-network 2>/dev/null || true"
|
||||||
su - "$ACTUAL_USER" -c "docker network create media-network 2>/dev/null || true"
|
su - "$ACTUAL_USER" -c "docker network create media-network 2>/dev/null || true"
|
||||||
su - "$ACTUAL_USER" -c "docker network create dockerproxy-network 2>/dev/null || true"
|
su - "$ACTUAL_USER" -c "docker network create dockerproxy-network 2>/dev/null || true"
|
||||||
log_success "Docker networks created"
|
log_success "Docker networks created"
|
||||||
|
STEPS_COMPLETED=$((STEPS_COMPLETED + 1))
|
||||||
|
log_progress "Completed: $STEPS_COMPLETED/$STEPS_TOTAL steps (core setup)"
|
||||||
echo ""
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
# Step 10: Optional - Detect and Install NVIDIA Drivers
|
step_10_nvidia_drivers() {
|
||||||
log_info "Step 10/10 (Optional): Checking for NVIDIA GPU..."
|
log_info "Step 10/$STEPS_TOTAL (Optional): Checking for NVIDIA GPU..."
|
||||||
|
|
||||||
# Detect NVIDIA GPU
|
# Detect NVIDIA GPU
|
||||||
if lspci | grep -i nvidia > /dev/null; then
|
if lspci | grep -i nvidia > /dev/null; then
|
||||||
@@ -430,10 +527,98 @@ if lspci | grep -i nvidia > /dev/null; then
|
|||||||
else
|
else
|
||||||
log_warning "NVIDIA GPU detected but drivers not installed"
|
log_warning "NVIDIA GPU detected but drivers not installed"
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Do you want to install NVIDIA drivers now? (y/N): " -n 1 -r
|
|
||||||
|
if confirm "Install NVIDIA drivers now?"; then
|
||||||
|
install_nvidia_drivers "$GPU_INFO"
|
||||||
|
else
|
||||||
|
log_info "Skipping NVIDIA driver installation"
|
||||||
|
log_info "To install later, visit: https://www.nvidia.com/Download/index.aspx"
|
||||||
|
NVIDIA_INSTALLED=false
|
||||||
|
NVIDIA_REBOOT_NEEDED=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
log_info "No NVIDIA GPU detected, skipping driver installation"
|
||||||
|
NVIDIA_REBOOT_NEEDED=false
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_final_summary() {
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
log_success "AI-Homelab setup completed successfully!"
|
||||||
|
log_progress "All $STEPS_TOTAL core steps completed!"
|
||||||
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then
|
||||||
|
log_warning "⚠️ REBOOT REQUIRED FOR NVIDIA DRIVERS ⚠️"
|
||||||
|
echo ""
|
||||||
|
log_info "NVIDIA drivers were installed and require a system reboot."
|
||||||
|
log_info "Please reboot before running the deployment script."
|
||||||
|
echo ""
|
||||||
|
echo " After reboot, verify drivers with: nvidia-smi"
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "Next steps:"
|
||||||
|
echo ""
|
||||||
|
if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then
|
||||||
|
echo " 1. REBOOT YOUR SYSTEM for NVIDIA drivers to load"
|
||||||
|
echo " Run: sudo reboot"
|
||||||
|
echo ""
|
||||||
|
echo " 2. After reboot, deploy your homelab services"
|
||||||
|
else
|
||||||
|
echo " 1. Deploy your homelab services"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
log_info "Setup complete!"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Instructions for deployment
|
||||||
|
if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then
|
||||||
|
log_info "Please reboot your system for NVIDIA drivers, then run:"
|
||||||
|
else
|
||||||
|
log_info "Next step - deploy your homelab services:"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
echo " cd ~/AI-Homelab"
|
||||||
|
echo " sudo ./scripts/deploy-homelab.sh"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to generate secrets
|
||||||
|
generate_secret() {
|
||||||
|
openssl rand -hex 64
|
||||||
|
}
|
||||||
|
|
||||||
|
# Helper function to generate new Authelia secrets
|
||||||
|
generate_new_secrets() {
|
||||||
|
log_info "Generating new JWT secret..."
|
||||||
|
JWT_SECRET=$(generate_secret)
|
||||||
|
log_info "Generating new session secret..."
|
||||||
|
SESSION_SECRET=$(generate_secret)
|
||||||
|
log_info "Generating new storage encryption key..."
|
||||||
|
ENCRYPTION_KEY=$(generate_secret)
|
||||||
|
|
||||||
|
# Update .env file
|
||||||
|
sed -i "s|^AUTHELIA_JWT_SECRET=.*|AUTHELIA_JWT_SECRET=${JWT_SECRET}|" "$REPO_ENV_FILE"
|
||||||
|
sed -i "s|^AUTHELIA_SESSION_SECRET=.*|AUTHELIA_SESSION_SECRET=${SESSION_SECRET}|" "$REPO_ENV_FILE"
|
||||||
|
sed -i "s|^AUTHELIA_STORAGE_ENCRYPTION_KEY=.*|AUTHELIA_STORAGE_ENCRYPTION_KEY=${ENCRYPTION_KEY}|" "$REPO_ENV_FILE"
|
||||||
|
|
||||||
|
log_success "New secrets generated and saved to .env"
|
||||||
|
}
|
||||||
|
|
||||||
|
# NVIDIA Driver Installation Function
|
||||||
|
install_nvidia_drivers() {
|
||||||
|
local GPU_INFO="$1"
|
||||||
|
|
||||||
log_info "Installing NVIDIA drivers using official installer..."
|
log_info "Installing NVIDIA drivers using official installer..."
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
@@ -503,15 +688,8 @@ EOF
|
|||||||
NVIDIA_INSTALLED=false
|
NVIDIA_INSTALLED=false
|
||||||
NVIDIA_REBOOT_NEEDED=false
|
NVIDIA_REBOOT_NEEDED=false
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
log_info "Skipping NVIDIA driver installation"
|
|
||||||
log_info "To install later, visit: https://www.nvidia.com/Download/index.aspx"
|
|
||||||
NVIDIA_INSTALLED=false
|
|
||||||
NVIDIA_REBOOT_NEEDED=false
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check if NVIDIA Container Toolkit is installed
|
# Install NVIDIA Container Toolkit if drivers installed successfully
|
||||||
if [ "$NVIDIA_INSTALLED" = true ]; then
|
if [ "$NVIDIA_INSTALLED" = true ]; then
|
||||||
if command -v nvidia-container-runtime &> /dev/null; then
|
if command -v nvidia-container-runtime &> /dev/null; then
|
||||||
log_warning "NVIDIA Container Toolkit is already installed"
|
log_warning "NVIDIA Container Toolkit is already installed"
|
||||||
@@ -541,60 +719,70 @@ EOF
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#==========================================
|
||||||
|
# MAIN EXECUTION
|
||||||
|
#==========================================
|
||||||
|
|
||||||
|
# Add trap for cleanup on error
|
||||||
|
cleanup_on_error() {
|
||||||
|
local exit_code=$?
|
||||||
|
if [ $exit_code -ne 0 ]; then
|
||||||
|
log_error "Script failed with exit code: $exit_code"
|
||||||
echo ""
|
echo ""
|
||||||
else
|
log_info "Partial setup may have occurred. To resume:"
|
||||||
log_info "No NVIDIA GPU detected, skipping driver installation"
|
log_info " 1. Review error messages above"
|
||||||
NVIDIA_REBOOT_NEEDED=false
|
log_info " 2. Fix the issue if possible"
|
||||||
|
log_info " 3. Re-run: sudo ./setup-homelab.sh"
|
||||||
echo ""
|
echo ""
|
||||||
|
log_info "The script is designed to be idempotent (safe to re-run)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup_on_error EXIT
|
||||||
|
|
||||||
|
# Validate script is running from correct location
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
|
if [ ! -f "$REPO_DIR/.env.example" ] || [ ! -d "$REPO_DIR/docker-compose" ]; then
|
||||||
|
log_error "This script must be run from the AI-Homelab repository"
|
||||||
|
log_info "Expected location: AI-Homelab/scripts/setup-homelab.sh"
|
||||||
|
log_info "Current location: $SCRIPT_DIR"
|
||||||
|
echo ""
|
||||||
|
log_info "Please:"
|
||||||
|
log_info " 1. Clone the repository: git clone https://github.com/kelinfoxy/AI-Homelab.git"
|
||||||
|
log_info " 2. Enter the directory: cd AI-Homelab"
|
||||||
|
log_info " 3. Run: sudo ./scripts/setup-homelab.sh"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# If running as root (not via sudo), handle sudo grant and exit
|
||||||
# Final Summary
|
if [ "$ACTUAL_USER" = "root" ]; then
|
||||||
echo ""
|
handle_root_user
|
||||||
echo "=========================================="
|
|
||||||
log_success "AI-Homelab setup completed successfully!"
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then
|
|
||||||
log_warning "⚠️ REBOOT REQUIRED FOR NVIDIA DRIVERS ⚠️"
|
|
||||||
echo ""
|
|
||||||
log_info "NVIDIA drivers were installed and require a system reboot."
|
|
||||||
log_info "Please reboot before running the deployment script."
|
|
||||||
echo ""
|
|
||||||
echo " After reboot, verify drivers with: nvidia-smi"
|
|
||||||
echo ""
|
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Next steps:"
|
log_info "Setting up AI-Homelab for user: $ACTUAL_USER"
|
||||||
echo ""
|
if [ "$AUTO_YES" = true ]; then
|
||||||
echo " 1. Log out and log back in for group changes to take effect"
|
log_info "Running in automated mode (--yes flag enabled)"
|
||||||
echo " (or run: newgrp docker)"
|
|
||||||
echo ""
|
|
||||||
if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then
|
|
||||||
echo " 2. REBOOT YOUR SYSTEM for NVIDIA drivers to load"
|
|
||||||
echo " Run: sudo reboot"
|
|
||||||
echo ""
|
|
||||||
echo " 3. After reboot, run the deployment script to deploy your homelab"
|
|
||||||
else
|
|
||||||
echo " 2. Run the deployment script to deploy your homelab"
|
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
|
||||||
echo ""
|
|
||||||
log_info "Setup complete!"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Instructions for deployment
|
# Progress tracking
|
||||||
if [ "${NVIDIA_REBOOT_NEEDED:-false}" = true ]; then
|
STEPS_TOTAL=9
|
||||||
log_info "Please reboot your system for NVIDIA drivers, then run:"
|
STEPS_COMPLETED=0
|
||||||
else
|
|
||||||
log_info "Next step - deploy your homelab services:"
|
# Execute setup steps in order
|
||||||
fi
|
step_0_preflight_checks
|
||||||
echo ""
|
step_1_update_system
|
||||||
echo " cd ~/AI-Homelab"
|
step_2_install_packages
|
||||||
echo " sudo ./scripts/deploy-homelab.sh"
|
step_3_install_docker
|
||||||
echo ""
|
step_4_configure_user_groups
|
||||||
|
step_5_configure_firewall
|
||||||
|
step_6_configure_ssh
|
||||||
|
step_7_generate_authelia_secrets
|
||||||
|
step_8_create_directories
|
||||||
|
step_9_create_networks
|
||||||
|
step_10_nvidia_drivers
|
||||||
|
show_final_summary
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user