feat: Add Option 3 - Deploy Additional Server with multi-server support
Major features:
- Automated SSH key setup between remote and core servers
- Docker TLS configuration with shared CA certificates
- Automatic deployment of Dockge, Traefik, Sablier, and Infrastructure stacks
- Copy all stacks (except core) to remote server for on-demand deployment
- New standalone Traefik stack for remote server container discovery
- Locale-aware SSH/SCP commands to handle Raspberry Pi warnings
- Variable expansion support in .env files (${VAR} references)
- Comprehensive error handling and verbose deployment logging
Technical improvements:
- setup_ssh_key_to_core() - Automated RSA 4096-bit key generation and installation
- setup_multi_server_tls() - Fetch shared CA from core server via SSH
- copy_all_stacks_for_remote() - Deploy all stacks except core
- deploy_traefik_stack() - Local Traefik for container discovery
- Enhanced localization with envsubst support
- Docker network creation (traefik-network, homelab-network)
- Password authentication with special character handling
Fixes:
- Fixed SSH key path handling for non-root users
- Fixed SCP exit code checking (was checking grep instead of scp)
- Fixed CA file detection with proper test commands
- Removed unnecessary prepare_deployment() function call
- Added ACTUAL_USER variable initialization for remote deployments
This commit is contained in:
14
.env.example
14
.env.example
@@ -12,13 +12,6 @@ TZ=America/New_York
|
|||||||
SERVER_IP=192.168.1.100 # This server
|
SERVER_IP=192.168.1.100 # This server
|
||||||
SERVER_HOSTNAME=debian
|
SERVER_HOSTNAME=debian
|
||||||
|
|
||||||
# If deploying with option 3: Remote Core Server
|
|
||||||
# the REMOTE_SERVER is where the Core Stack (Traefik) is running
|
|
||||||
REMOTE_SERVER_IP=your.remote.ip.address
|
|
||||||
REMOTE_SERVER_HOSTNAME=your-remote-server
|
|
||||||
REMOTE_SERVER_USER=${DEFAULT_USER}
|
|
||||||
REMOTE_SERVER_PASSWORD=${DEFAULT_PASSWORD}
|
|
||||||
|
|
||||||
# Domain Configuration
|
# Domain Configuration
|
||||||
DUCKDNS_SUBDOMAINS=yourdomain # Without .duckdns.org
|
DUCKDNS_SUBDOMAINS=yourdomain # Without .duckdns.org
|
||||||
DUCKDNS_TOKEN=your-duckdns-token
|
DUCKDNS_TOKEN=your-duckdns-token
|
||||||
@@ -35,6 +28,13 @@ MEDIADIR=/mnt/media # Large media files on separate drive
|
|||||||
DOWNLOADDIR=/mnt/downloads # Downloads on separate drive
|
DOWNLOADDIR=/mnt/downloads # Downloads on separate drive
|
||||||
PROJECTDIR=~/projects # User's projects folder
|
PROJECTDIR=~/projects # User's projects folder
|
||||||
|
|
||||||
|
# If selecting option 3: Deploy Additional Server
|
||||||
|
# the CORE_SERVER is where the Core Traefik is running
|
||||||
|
CORE_SERVER_IP=192.168.1.101
|
||||||
|
CORE_SERVER_HOSTNAME=debian2
|
||||||
|
CORE_SERVER_USER=${DEFAULT_USER}
|
||||||
|
CORE_SERVER_PASSWORD=${DEFAULT_PASSWORD}
|
||||||
|
|
||||||
# ##########################################
|
# ##########################################
|
||||||
# #### NOTEABLE OPTIONAL CONFIGURATIONS ####
|
# #### NOTEABLE OPTIONAL CONFIGURATIONS ####
|
||||||
|
|
||||||
|
|||||||
40
docker-compose/traefik/docker-compose.yml
Normal file
40
docker-compose/traefik/docker-compose.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Traefik Service for Remote Servers
|
||||||
|
# This standalone Traefik instance runs on remote servers to discover local containers
|
||||||
|
# and communicate with the core Traefik on the core server via Docker TLS
|
||||||
|
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
# Local Traefik instance for container discovery on this remote server
|
||||||
|
image: traefik:v3
|
||||||
|
container_name: traefik
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- '--api.dashboard=true'
|
||||||
|
- '--api.insecure=false'
|
||||||
|
- '--providers.docker=true'
|
||||||
|
- '--providers.docker.exposedbydefault=false'
|
||||||
|
- '--providers.docker.network=traefik-network'
|
||||||
|
- '--providers.file.directory=/dynamic'
|
||||||
|
- '--providers.file.watch=true'
|
||||||
|
- '--log.level=INFO'
|
||||||
|
- '--accesslog=true'
|
||||||
|
- '--entrypoints.web.address=:80'
|
||||||
|
- '--entrypoints.websecure.address=:443'
|
||||||
|
environment:
|
||||||
|
- TZ=America/New_York
|
||||||
|
ports:
|
||||||
|
- '8080:8080' # Dashboard (optional, for debugging)
|
||||||
|
volumes:
|
||||||
|
- ./config:/config
|
||||||
|
- ./dynamic:/dynamic
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
networks:
|
||||||
|
- traefik-network
|
||||||
|
labels:
|
||||||
|
- 'homelab.category=infrastructure'
|
||||||
|
- 'homelab.description=Local reverse proxy for container discovery'
|
||||||
|
- 'traefik.enable=false' # This Traefik doesn't route itself
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-network:
|
||||||
|
external: true
|
||||||
@@ -94,6 +94,30 @@ load_env_file_safely() {
|
|||||||
fi
|
fi
|
||||||
done < "$env_file"
|
done < "$env_file"
|
||||||
|
|
||||||
|
# Second pass: expand any ${VAR} references in loaded variables
|
||||||
|
while IFS= read -r line || [ -n "$line" ]; do
|
||||||
|
# Skip comments and empty lines
|
||||||
|
[[ $line =~ ^[[:space:]]*# ]] && continue
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
# Parse KEY=VALUE
|
||||||
|
if [[ $line =~ ^([^=]+)=(.*)$ ]]; then
|
||||||
|
local key="${BASH_REMATCH[1]}"
|
||||||
|
key=$(echo "$key" | xargs)
|
||||||
|
|
||||||
|
# Get current value
|
||||||
|
local current_value="${!key}"
|
||||||
|
|
||||||
|
# Check if value contains ${...} and expand it
|
||||||
|
if [[ "$current_value" =~ \$\{[^}]+\} ]]; then
|
||||||
|
# Use eval to expand the variable reference safely within quotes
|
||||||
|
local expanded_value=$(eval echo "\"$current_value\"")
|
||||||
|
export "$key"="$expanded_value"
|
||||||
|
debug_log "Expanded $key variable reference"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$env_file"
|
||||||
|
|
||||||
debug_log "Env file loaded successfully"
|
debug_log "Env file loaded successfully"
|
||||||
}
|
}
|
||||||
load_env_file() {
|
load_env_file() {
|
||||||
@@ -226,134 +250,197 @@ generate_shared_ca() {
|
|||||||
log_success "Shared CA generated"
|
log_success "Shared CA generated"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Setup SSH key authentication to core server
|
||||||
|
setup_ssh_key_to_core() {
|
||||||
|
local key_name="id_rsa_${SERVER_HOSTNAME}_to_core"
|
||||||
|
local key_path="/home/$ACTUAL_USER/.ssh/$key_name"
|
||||||
|
|
||||||
|
log_info "Setting up SSH key authentication to core server..."
|
||||||
|
|
||||||
|
# Ensure .ssh directory exists
|
||||||
|
mkdir -p "/home/$ACTUAL_USER/.ssh"
|
||||||
|
chmod 700 "/home/$ACTUAL_USER/.ssh"
|
||||||
|
|
||||||
|
# Check if key already exists
|
||||||
|
if [ -f "$key_path" ]; then
|
||||||
|
log_info "SSH key already exists: $key_path"
|
||||||
|
|
||||||
|
# Test if key works with aggressive timeout
|
||||||
|
log_info "Testing existing SSH key..."
|
||||||
|
if timeout 3 bash -c "LC_ALL=C ssh -i '$key_path' -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no \
|
||||||
|
-o ServerAliveInterval=1 -o ServerAliveCountMax=1 -o LogLevel=ERROR \
|
||||||
|
'${CORE_SERVER_USER}@${CORE_SERVER_IP}' 'echo test' 2>&1 | grep -v 'locale\|LC_ALL\|setlocale' | grep -q 'test'"; then
|
||||||
|
log_success "Existing SSH key works, skipping key setup"
|
||||||
|
export SSH_KEY_PATH="$key_path"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_warning "Existing key doesn't work or connection failed, will regenerate and install"
|
||||||
|
rm -f "$key_path" "$key_path.pub"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate new SSH key
|
||||||
|
log_info "Generating SSH key: $key_path"
|
||||||
|
ssh-keygen -t rsa -b 4096 -f "$key_path" -N "" \
|
||||||
|
-C "${SERVER_HOSTNAME}-to-core-${CORE_SERVER_HOSTNAME}" 2>&1 | grep -v "^Generating\|^Your identification\|^Your public key"
|
||||||
|
|
||||||
|
if [ ${PIPESTATUS[0]} -ne 0 ]; then
|
||||||
|
log_error "Failed to generate SSH key"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "SSH key generated"
|
||||||
|
|
||||||
|
# Install key on core server using password
|
||||||
|
log_info "Installing SSH key on core server ${CORE_SERVER_IP}..."
|
||||||
|
|
||||||
|
if ! command -v sshpass &> /dev/null; then
|
||||||
|
log_info "sshpass is not installed. Installing now..."
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y sshpass >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Debug: Check if password is set
|
||||||
|
if [ -z "$CORE_SERVER_PASSWORD" ]; then
|
||||||
|
log_error "CORE_SERVER_PASSWORD is empty!"
|
||||||
|
log_error "Check your .env file - ensure CORE_SERVER_PASSWORD is set correctly"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show password length for debugging (not actual password)
|
||||||
|
log_info "Password length: ${#CORE_SERVER_PASSWORD} characters"
|
||||||
|
|
||||||
|
# Test SSH connection with password first
|
||||||
|
log_info "Testing SSH connection with password..."
|
||||||
|
if ! LC_ALL=C sshpass -p "$CORE_SERVER_PASSWORD" ssh \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o ConnectTimeout=10 \
|
||||||
|
-o LogLevel=ERROR \
|
||||||
|
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" "echo 'SSH connection successful'" 2>&1 | grep -v "locale\|LC_ALL\|setlocale" | grep -q "successful"; then
|
||||||
|
log_error "SSH password authentication failed"
|
||||||
|
log_error "Please verify the password in your .env file is correct"
|
||||||
|
log_error "You can test manually: sshpass -p 'YOUR_PASSWORD' ssh ${CORE_SERVER_USER}@${CORE_SERVER_IP}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "SSH password authentication works"
|
||||||
|
|
||||||
|
# Read the public key
|
||||||
|
local pub_key=$(cat "${key_path}.pub")
|
||||||
|
|
||||||
|
# Copy key to core server using direct SSH command (more reliable than ssh-copy-id)
|
||||||
|
log_info "Adding public key to authorized_keys on core server..."
|
||||||
|
LC_ALL=C sshpass -p "$CORE_SERVER_PASSWORD" ssh \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o ConnectTimeout=10 \
|
||||||
|
-o LogLevel=ERROR \
|
||||||
|
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
|
||||||
|
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$pub_key' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" 2>&1 | grep -v "locale\|LC_ALL\|setlocale"
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
log_error "Failed to copy SSH key to core server"
|
||||||
|
log_error "Please verify:"
|
||||||
|
echo " 1. Core server IP is correct: ${CORE_SERVER_IP}"
|
||||||
|
echo " 2. Username is correct: ${CORE_SERVER_USER}"
|
||||||
|
echo " 3. Password is correct"
|
||||||
|
echo " 4. SSH server is running on core server"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify key works
|
||||||
|
log_info "Verifying SSH key authentication..."
|
||||||
|
if LC_ALL=C ssh -i "$key_path" -o BatchMode=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o LogLevel=ERROR \
|
||||||
|
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" "echo 'SSH key authentication successful'" 2>&1 | grep -v "locale\|LC_ALL\|setlocale" | grep -q "successful"; then
|
||||||
|
log_success "SSH key authentication verified"
|
||||||
|
|
||||||
|
# Export key path for use by other functions
|
||||||
|
export SSH_KEY_PATH="$key_path"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_error "SSH key verification failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to setup multi-server TLS for remote servers
|
# Function to setup multi-server TLS for remote servers
|
||||||
setup_multi_server_tls() {
|
setup_multi_server_tls() {
|
||||||
local ca_dir="/opt/stacks/core/shared-ca"
|
local ca_dir="/opt/stacks/core/shared-ca"
|
||||||
|
|
||||||
|
# Use the SSH key path that was exported by setup_ssh_key_to_core
|
||||||
|
if [ -z "$SSH_KEY_PATH" ]; then
|
||||||
|
log_error "SSH_KEY_PATH not set. Please run setup_ssh_key_to_core first."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local key_path="$SSH_KEY_PATH"
|
||||||
|
|
||||||
|
log_info "Using SSH key: $key_path"
|
||||||
|
|
||||||
sudo mkdir -p "$ca_dir"
|
sudo mkdir -p "$ca_dir"
|
||||||
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$ca_dir"
|
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$ca_dir"
|
||||||
|
|
||||||
if [ -n "$CORE_SERVER_IP" ]; then
|
log_info "Fetching shared CA from core server ${CORE_SERVER_IP}..."
|
||||||
log_info "Setting up multi-server TLS using shared CA from core server $CORE_SERVER_IP..."
|
|
||||||
else
|
# Check if shared CA exists on core server
|
||||||
# Prompt for core server IP if not set
|
log_info "Checking for shared CA on core server..."
|
||||||
read -p "Enter the IP address of your core server: " CORE_SERVER_IP
|
|
||||||
while [ -z "$CORE_SERVER_IP" ]; do
|
SHARED_CA_PATH=""
|
||||||
log_warning "Core server IP is required for shared TLS"
|
|
||||||
read -p "Enter the IP address of your core server: " CORE_SERVER_IP
|
# Test for shared-ca directory (preferred location)
|
||||||
done
|
if LC_ALL=C ssh -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR "${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
|
||||||
log_info "Setting up multi-server TLS using shared CA from core server $CORE_SERVER_IP..."
|
"test -f /opt/stacks/core/shared-ca/ca.pem && test -f /opt/stacks/core/shared-ca/ca-key.pem && echo 'EXISTS'" 2>/dev/null | grep -q "EXISTS"; then
|
||||||
|
SHARED_CA_PATH="/opt/stacks/core/shared-ca"
|
||||||
|
log_success "Found shared CA in: $SHARED_CA_PATH"
|
||||||
|
# Test for docker-tls directory (alternative location)
|
||||||
|
elif LC_ALL=C ssh -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR "${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
|
||||||
|
"test -f /opt/stacks/core/docker-tls/ca.pem && test -f /opt/stacks/core/docker-tls/ca-key.pem && echo 'EXISTS'" 2>/dev/null | grep -q "EXISTS"; then
|
||||||
|
SHARED_CA_PATH="/opt/stacks/core/docker-tls"
|
||||||
|
log_success "Found shared CA in: $SHARED_CA_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Prompt for SSH username if not set
|
if [ -z "$SHARED_CA_PATH" ]; then
|
||||||
if [ -z "$SSH_USER" ]; then
|
log_error "Shared CA not found on core server"
|
||||||
DEFAULT_SSH_USER="${DEFAULT_USER:-$USER}"
|
log_error "Please ensure core server is fully deployed with Option 2 first"
|
||||||
read -p "SSH username for core server [$DEFAULT_SSH_USER]: " SSH_USER
|
log_info "Checking what exists on core server..."
|
||||||
SSH_USER="${SSH_USER:-$DEFAULT_SSH_USER}"
|
LC_ALL=C ssh -i "$key_path" -o StrictHostKeyChecking=no "${CORE_SERVER_USER}@${CORE_SERVER_IP}" \
|
||||||
fi
|
"ls -la /opt/stacks/core/shared-ca/ /opt/stacks/core/docker-tls/ 2>&1" | grep -v "locale\|LC_ALL\|setlocale"
|
||||||
|
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Shared CA not found on core server ${CORE_SERVER_IP}
|
||||||
# Test SSH connection - try key authentication first
|
|
||||||
log_info "Testing SSH connection to core server ($SSH_USER@$CORE_SERVER_IP)..."
|
|
||||||
if ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes "$SSH_USER@$CORE_SERVER_IP" "echo 'SSH connection successful'" 2>/dev/null; then
|
|
||||||
log_success "SSH connection established using key authentication"
|
|
||||||
USE_SSHPASS=false
|
|
||||||
else
|
|
||||||
# Key authentication failed, try password authentication
|
|
||||||
log_info "Key authentication failed, trying password authentication..."
|
|
||||||
read -s -p "Enter SSH password for $SSH_USER@$CORE_SERVER_IP: " SSH_PASSWORD
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if sshpass -p "$SSH_PASSWORD" ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "echo 'SSH connection successful'" 2>/dev/null; then
|
|
||||||
log_success "SSH connection established using password authentication"
|
|
||||||
USE_SSHPASS=true
|
|
||||||
else
|
|
||||||
log_error "Cannot connect to core server via SSH. Please check:"
|
|
||||||
echo " 1. SSH is running on the core server"
|
|
||||||
echo " 2. SSH keys are properly configured, or username/password are correct"
|
|
||||||
echo " 3. The core server IP is correct"
|
|
||||||
echo ""
|
|
||||||
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Cannot connect to core server $CORE_SERVER_IP via SSH
|
|
||||||
This will prevent Sablier from connecting to remote Docker daemons.
|
|
||||||
|
|
||||||
To fix this:
|
To fix this:
|
||||||
1. Ensure SSH is running on the core server
|
1. Deploy core server first using Option 2
|
||||||
2. Configure SSH keys or provide correct password
|
2. Verify CA exists: ssh ${CORE_SERVER_USER}@${CORE_SERVER_IP} 'ls -la /opt/stacks/core/shared-ca/'
|
||||||
3. Verify the core server IP is correct
|
3. Re-run Option 3 deployment"
|
||||||
4. Test SSH connection: ssh $SSH_USER@$CORE_SERVER_IP
|
return 1
|
||||||
|
|
||||||
Without SSH access, shared CA cannot be fetched for secure multi-server TLS."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fetch shared CA certificates from core server
|
# Copy shared CA from core server using SCP
|
||||||
log_info "Fetching shared CA certificates from core server..."
|
log_info "Copying shared CA certificates..."
|
||||||
SHARED_CA_EXISTS=false
|
|
||||||
|
|
||||||
# Check if shared CA exists on core server (check both old and new locations)
|
# Copy ca.pem
|
||||||
if [ "$USE_SSHPASS" = true ] && [ -n "$SSH_PASSWORD" ]; then
|
if ! LC_ALL=C scp -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR \
|
||||||
if sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/shared-ca/ca.pem ] && [ -f /opt/stacks/core/shared-ca/ca-key.pem ] && [ -r /opt/stacks/core/shared-ca/ca.pem ] && [ -r /opt/stacks/core/shared-ca/ca-key.pem ]" 2>/dev/null; then
|
"${CORE_SERVER_USER}@${CORE_SERVER_IP}:${SHARED_CA_PATH}/ca.pem" \
|
||||||
SHARED_CA_EXISTS=true
|
"$ca_dir/" 2>/dev/null; then
|
||||||
SHARED_CA_PATH="/opt/stacks/core/shared-ca"
|
log_error "Failed to copy ca.pem from core server"
|
||||||
log_info "Detected CA certificate and key in shared-ca location"
|
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Could not copy shared CA from ${CORE_SERVER_IP}
|
||||||
elif sshpass -p "$SSH_PASSWORD" ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/docker-tls/ca.pem ] && [ -f /opt/stacks/core/docker-tls/ca-key.pem ] && [ -r /opt/stacks/core/docker-tls/ca.pem ] && [ -r /opt/stacks/core/docker-tls/ca-key.pem ]" 2>/dev/null; then
|
|
||||||
SHARED_CA_EXISTS=true
|
To fix this:
|
||||||
SHARED_CA_PATH="/opt/stacks/core/docker-tls"
|
1. Verify SSH key works: ssh -i $key_path ${CORE_SERVER_USER}@${CORE_SERVER_IP}
|
||||||
log_info "Detected CA certificate and key in docker-tls location"
|
2. Check file permissions: ssh ${CORE_SERVER_USER}@${CORE_SERVER_IP} 'ls -la ${SHARED_CA_PATH}/'
|
||||||
fi
|
3. Manually copy if needed: scp -i $key_path ${CORE_SERVER_USER}@${CORE_SERVER_IP}:${SHARED_CA_PATH}/ca* $ca_dir/"
|
||||||
else
|
return 1
|
||||||
if ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/shared-ca/ca.pem ] && [ -f /opt/stacks/core/shared-ca/ca-key.pem ] && [ -r /opt/stacks/core/shared-ca/ca.pem ] && [ -r /opt/stacks/core/shared-ca/ca-key.pem ]" 2>/dev/null; then
|
|
||||||
SHARED_CA_EXISTS=true
|
|
||||||
SHARED_CA_PATH="/opt/stacks/core/shared-ca"
|
|
||||||
log_info "Detected CA certificate and key in shared-ca location"
|
|
||||||
elif ssh -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP" "[ -f /opt/stacks/core/docker-tls/ca.pem ] && [ -f /opt/stacks/core/docker-tls/ca-key.pem ] && [ -r /opt/stacks/core/docker-tls/ca.pem ] && [ -r /opt/stacks/core/docker-tls/ca-key.pem ]" 2>/dev/null; then
|
|
||||||
SHARED_CA_EXISTS=true
|
|
||||||
SHARED_CA_PATH="/opt/stacks/core/docker-tls"
|
|
||||||
log_info "Detected CA certificate and key in docker-tls location"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$SHARED_CA_EXISTS" = true ]; then
|
# Copy ca-key.pem
|
||||||
# Copy existing shared CA from core server
|
if ! LC_ALL=C scp -i "$key_path" -o StrictHostKeyChecking=no -o LogLevel=ERROR \
|
||||||
set +e
|
"${CORE_SERVER_USER}@${CORE_SERVER_IP}:${SHARED_CA_PATH}/ca-key.pem" \
|
||||||
scp_output=$(scp -o StrictHostKeyChecking=no "$SSH_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca.pem" "$SSH_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca-key.pem" "$ca_dir/" 2>&1)
|
"$ca_dir/" 2>/dev/null; then
|
||||||
scp_exit_code=$?
|
log_error "Failed to copy ca-key.pem from core server"
|
||||||
set -e
|
return 1
|
||||||
if [ $scp_exit_code -eq 0 ]; then
|
fi
|
||||||
log_success "Shared CA certificate and key fetched from core server"
|
|
||||||
|
log_success "Shared CA copied successfully"
|
||||||
|
|
||||||
|
# Now setup Docker TLS using the shared CA
|
||||||
setup_docker_tls
|
setup_docker_tls
|
||||||
else
|
|
||||||
log_error "Failed to fetch shared CA certificate and key from core server"
|
|
||||||
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Could not copy shared CA from core server $CORE_SERVER_IP
|
|
||||||
SCP Error: $scp_output
|
|
||||||
|
|
||||||
To fix this:
|
|
||||||
1. Ensure SSH key authentication works: ssh $ACTUAL_USER@$CORE_SERVER_IP
|
|
||||||
2. Verify core server has: $SHARED_CA_PATH/ca.pem and ca-key.pem
|
|
||||||
3. Check file permissions on core server: ls -la $SHARED_CA_PATH/
|
|
||||||
4. Manually copy CA: scp $ACTUAL_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca.pem $ca_dir/
|
|
||||||
scp $ACTUAL_USER@$CORE_SERVER_IP:$SHARED_CA_PATH/ca-key.pem $ca_dir/
|
|
||||||
5. Regenerate server certificates: run setup_docker_tls after copying
|
|
||||||
6. Restart Docker: sudo systemctl restart docker
|
|
||||||
|
|
||||||
Then restart Sablier on the core server to reconnect."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log_warning "Shared CA certificates not found on core server."
|
|
||||||
log_info "Please ensure the core server has been set up first and has generated the shared CA certificates."
|
|
||||||
TLS_ISSUES_SUMMARY="⚠️ TLS Configuration Issue: Shared CA certificates not found on core server $CORE_SERVER_IP
|
|
||||||
This will prevent Sablier from connecting to remote Docker daemons.
|
|
||||||
|
|
||||||
To fix this:
|
|
||||||
1. Ensure the core server is set up and has generated shared CA certificates
|
|
||||||
2. Verify SSH access: ssh $ACTUAL_USER@$CORE_SERVER_IP
|
|
||||||
3. Check core server locations: /opt/stacks/core/shared-ca/ or /opt/stacks/core/docker-tls/
|
|
||||||
4. Manually copy CA certificates if needed
|
|
||||||
5. Re-run the infrastructure deployment
|
|
||||||
|
|
||||||
Without shared CA, remote Docker access will not work securely."
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get script directory and repo directory
|
# Get script directory and repo directory
|
||||||
@@ -373,7 +460,6 @@ fi
|
|||||||
# Default values
|
# Default values
|
||||||
DOMAIN=""
|
DOMAIN=""
|
||||||
SERVER_IP=""
|
SERVER_IP=""
|
||||||
CORE_SERVER_IP=""
|
|
||||||
ADMIN_USER=""
|
ADMIN_USER=""
|
||||||
ADMIN_EMAIL=""
|
ADMIN_EMAIL=""
|
||||||
AUTHELIA_ADMIN_PASSWORD=""
|
AUTHELIA_ADMIN_PASSWORD=""
|
||||||
@@ -381,7 +467,13 @@ DEPLOY_CORE=false
|
|||||||
DEPLOY_INFRASTRUCTURE=false
|
DEPLOY_INFRASTRUCTURE=false
|
||||||
DEPLOY_DASHBOARDS=false
|
DEPLOY_DASHBOARDS=false
|
||||||
SETUP_STACKS=false
|
SETUP_STACKS=false
|
||||||
|
DEPLOY_REMOTE_SERVER=false
|
||||||
TLS_ISSUES_SUMMARY=""
|
TLS_ISSUES_SUMMARY=""
|
||||||
|
CORE_SERVER_IP=""
|
||||||
|
CORE_SERVER_HOSTNAME=""
|
||||||
|
CORE_SERVER_USER=""
|
||||||
|
CORE_SERVER_PASSWORD=""
|
||||||
|
SSH_KEY_PATH=""
|
||||||
|
|
||||||
# Required variables for configuration
|
# Required variables for configuration
|
||||||
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_SUBDOMAINS" "DUCKDNS_TOKEN" "DOMAIN" "DEFAULT_USER" "DEFAULT_PASSWORD" "DEFAULT_EMAIL")
|
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_SUBDOMAINS" "DUCKDNS_TOKEN" "DOMAIN" "DEFAULT_USER" "DEFAULT_PASSWORD" "DEFAULT_EMAIL")
|
||||||
@@ -1408,7 +1500,7 @@ set_required_vars_for_deployment() {
|
|||||||
debug_log "Set REQUIRED_VARS for core deployment"
|
debug_log "Set REQUIRED_VARS for core deployment"
|
||||||
;;
|
;;
|
||||||
"remote")
|
"remote")
|
||||||
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_DOMAIN" "DEFAULT_USER" "REMOTE_SERVER_IP" "REMOTE_SERVER_HOSTNAME" "REMOTE_SERVER_USER")
|
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DOMAIN" "DEFAULT_USER" "CORE_SERVER_IP" "CORE_SERVER_HOSTNAME" "CORE_SERVER_USER" "CORE_SERVER_PASSWORD")
|
||||||
debug_log "Set REQUIRED_VARS for remote deployment"
|
debug_log "Set REQUIRED_VARS for remote deployment"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -1420,9 +1512,23 @@ set_required_vars_for_deployment() {
|
|||||||
|
|
||||||
# Deploy remote server
|
# Deploy remote server
|
||||||
deploy_remote_server() {
|
deploy_remote_server() {
|
||||||
|
# Enable verbose mode for remote deployment
|
||||||
|
VERBOSE=true
|
||||||
|
|
||||||
log_info "Deploying Remote Server Configuration"
|
log_info "Deploying Remote Server Configuration"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Set ACTUAL_USER if not already set (needed for SSH key paths)
|
||||||
|
if [ -z "$ACTUAL_USER" ]; then
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
|
ACTUAL_USER=${SUDO_USER:-$USER}
|
||||||
|
else
|
||||||
|
ACTUAL_USER=$USER
|
||||||
|
fi
|
||||||
|
export ACTUAL_USER
|
||||||
|
debug_log "Set ACTUAL_USER=$ACTUAL_USER"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check Docker is installed
|
# Check Docker is installed
|
||||||
if ! check_docker_installed; then
|
if ! check_docker_installed; then
|
||||||
log_error "Docker must be installed before deploying remote server"
|
log_error "Docker must be installed before deploying remote server"
|
||||||
@@ -1430,34 +1536,91 @@ deploy_remote_server() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Ensure we have core server information
|
# Ensure we have core server information
|
||||||
if [ -z "$REMOTE_SERVER_IP" ] || [ -z "$REMOTE_SERVER_HOSTNAME" ]; then
|
if [ -z "$CORE_SERVER_IP" ] || [ -z "$CORE_SERVER_HOSTNAME" ]; then
|
||||||
log_error "Remote server IP and hostname are required"
|
log_error "Core server IP and hostname are required"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Configuring Docker TLS for remote API access..."
|
# Step 1: Setup SSH key authentication to core server
|
||||||
setup_docker_tls
|
log_info "Step 1: Setting up SSH key authentication to core server..."
|
||||||
|
log_info "Using ACTUAL_USER=$ACTUAL_USER for SSH key path"
|
||||||
|
log_info "Target: ${CORE_SERVER_USER}@${CORE_SERVER_IP}"
|
||||||
|
if ! setup_ssh_key_to_core; then
|
||||||
|
log_error "Failed to setup SSH key authentication"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_success "SSH key authentication setup complete"
|
||||||
|
echo ""
|
||||||
|
|
||||||
log_info "Fetching shared CA from core server..."
|
# Step 2: Fetch shared CA and setup Docker TLS
|
||||||
setup_multi_server_tls "$REMOTE_SERVER_IP" "$REMOTE_SERVER_USER"
|
log_info "Step 2: Fetching shared CA from core server..."
|
||||||
|
log_info "Using SSH key: $SSH_KEY_PATH"
|
||||||
|
if ! setup_multi_server_tls; then
|
||||||
|
log_error "Failed to setup multi-server TLS"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
log_info "Deploying Sablier stack for local lazy loading..."
|
# Step 3: Create required Docker networks
|
||||||
|
log_info "Step 3: Creating required Docker networks..."
|
||||||
|
docker network create traefik-network 2>/dev/null && log_success "Created traefik-network" || log_info "traefik-network already exists"
|
||||||
|
docker network create homelab-network 2>/dev/null && log_success "Created homelab-network" || log_info "homelab-network already exists"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 4: Install envsubst if not present
|
||||||
|
if ! command -v envsubst &> /dev/null; then
|
||||||
|
log_info "Installing envsubst (gettext-base)..."
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y gettext-base >/dev/null 2>&1
|
||||||
|
log_success "envsubst installed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Step 5: Copy all stacks to remote server
|
||||||
|
log_info "Step 5: Copying all stacks to remote server..."
|
||||||
|
copy_all_stacks_for_remote
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 6: Deploy Dockge
|
||||||
|
log_info "Step 6: Deploying Dockge..."
|
||||||
|
deploy_dockge
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 7: Deploy Traefik (local instance for container discovery)
|
||||||
|
log_info "Step 7: Deploying local Traefik..."
|
||||||
|
deploy_traefik_stack
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 8: Deploy Sablier stack for local lazy loading
|
||||||
|
log_info "Step 8: Deploying Sablier stack..."
|
||||||
deploy_sablier_stack
|
deploy_sablier_stack
|
||||||
|
echo ""
|
||||||
|
|
||||||
log_info "Registering this remote server with core Traefik..."
|
# Step 9: Deploy Infrastructure stack
|
||||||
|
log_info "Step 9: Deploying Infrastructure stack..."
|
||||||
|
deploy_infrastructure
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Step 10: Register this remote server with core Traefik
|
||||||
|
log_info "Step 10: Registering with core Traefik..."
|
||||||
register_remote_server_with_core
|
register_remote_server_with_core
|
||||||
|
echo ""
|
||||||
|
|
||||||
log_success "Remote server deployment complete!"
|
log_success "Remote server deployment complete!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "This server is now configured to:"
|
echo "This server is now configured to:"
|
||||||
echo " - Accept Docker API connections via TLS (port 2376)"
|
echo " - Accept Docker API connections via TLS (port 2376)"
|
||||||
|
echo " - Run local Traefik for container discovery"
|
||||||
echo " - Run Sablier for local container lazy loading"
|
echo " - Run Sablier for local container lazy loading"
|
||||||
|
echo " - Run infrastructure services"
|
||||||
echo " - Have its containers discovered by core Traefik"
|
echo " - Have its containers discovered by core Traefik"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Services deployed on this server will automatically:"
|
echo "Services deployed on this server will automatically:"
|
||||||
echo " - Be discovered by Traefik on the core server"
|
echo " - Be discovered by Traefik on the core server"
|
||||||
echo " - Get SSL certificates via core Traefik"
|
echo " - Get SSL certificates via core Traefik"
|
||||||
echo " - Be accessible at: https://servicename.${DUCKDNS_DOMAIN}"
|
echo " - Be accessible at: https://servicename.${DOMAIN}"
|
||||||
|
echo ""
|
||||||
|
echo "Additional stacks available in /opt/stacks/ (not started):"
|
||||||
|
echo " - dashboards, media, media-management, monitoring, productivity"
|
||||||
|
echo " - transcoders, utilities, vpn, wikis, homeassistant, alternatives"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1465,15 +1628,19 @@ deploy_remote_server() {
|
|||||||
register_remote_server_with_core() {
|
register_remote_server_with_core() {
|
||||||
debug_log "Registering remote server with core Traefik via SSH"
|
debug_log "Registering remote server with core Traefik via SSH"
|
||||||
|
|
||||||
if [ -z "$REMOTE_SERVER_IP" ] || [ -z "$REMOTE_SERVER_USER" ]; then
|
local key_name="id_rsa_${SERVER_HOSTNAME}_to_core"
|
||||||
log_error "REMOTE_SERVER_IP and REMOTE_SERVER_USER are required"
|
local key_path="/home/$ACTUAL_USER/.ssh/$key_name"
|
||||||
|
|
||||||
|
if [ -z "$CORE_SERVER_IP" ] || [ -z "$CORE_SERVER_USER" ]; then
|
||||||
|
log_error "CORE_SERVER_IP and CORE_SERVER_USER are required"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_info "Connecting to core server to register this remote server..."
|
log_info "Connecting to core server to register this remote server..."
|
||||||
|
|
||||||
# SSH to core server and run registration function
|
# SSH to core server and run registration function
|
||||||
ssh -o ConnectTimeout=10 "${REMOTE_SERVER_USER}@${REMOTE_SERVER_IP}" bash <<EOF
|
LC_ALL=C ssh -i "$key_path" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o LogLevel=ERROR \
|
||||||
|
"${CORE_SERVER_USER}@${CORE_SERVER_IP}" bash <<EOF 2>&1 | grep -v "locale\|LC_ALL\|setlocale"
|
||||||
# Source common.sh to get registration function
|
# Source common.sh to get registration function
|
||||||
source ~/EZ-Homelab/scripts/common.sh
|
source ~/EZ-Homelab/scripts/common.sh
|
||||||
|
|
||||||
@@ -1499,28 +1666,137 @@ deploy_sablier_stack() {
|
|||||||
|
|
||||||
local sablier_dir="/opt/stacks/sablier"
|
local sablier_dir="/opt/stacks/sablier"
|
||||||
|
|
||||||
# Create sablier stack directory
|
# Create sablier stack directory with sudo
|
||||||
if [ ! -d "$sablier_dir" ]; then
|
if [ ! -d "$sablier_dir" ]; then
|
||||||
mkdir -p "$sablier_dir"
|
sudo mkdir -p "$sablier_dir" || { log_error "Failed to create $sablier_dir"; return 1; }
|
||||||
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir"
|
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir"
|
||||||
|
log_success "Created $sablier_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if source files exist
|
||||||
|
if [ ! -f "$REPO_DIR/docker-compose/sablier/docker-compose.yml" ]; then
|
||||||
|
log_error "Sablier docker-compose.yml not found in repo at $REPO_DIR/docker-compose/sablier/"
|
||||||
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Copy stack files
|
# Copy stack files
|
||||||
cp "$REPO_DIR/docker-compose/sablier/docker-compose.yml" "$sablier_dir/"
|
log_info "Copying Sablier stack files from $REPO_DIR/docker-compose/sablier/..."
|
||||||
cp "$REPO_DIR/.env" "$sablier_dir/"
|
cp "$REPO_DIR/docker-compose/sablier/docker-compose.yml" "$sablier_dir/" || { log_error "Failed to copy docker-compose.yml"; return 1; }
|
||||||
|
cp "$REPO_DIR/.env" "$sablier_dir/" || { log_error "Failed to copy .env"; return 1; }
|
||||||
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/docker-compose.yml"
|
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/docker-compose.yml"
|
||||||
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/.env"
|
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/.env"
|
||||||
|
log_success "Stack files copied"
|
||||||
|
|
||||||
|
# Remove Authelia and other unnecessary variables from sablier .env
|
||||||
|
sed -i '/^AUTHELIA_/d' "$sablier_dir/.env"
|
||||||
|
sed -i '/^DEFAULT_PASSWORD=/d' "$sablier_dir/.env"
|
||||||
|
sed -i '/^CORE_SERVER_PASSWORD=/d' "$sablier_dir/.env"
|
||||||
|
|
||||||
# Localize the docker-compose file
|
# Localize the docker-compose file
|
||||||
localize_compose_labels "$sablier_dir/docker-compose.yml"
|
localize_compose_labels "$sablier_dir/docker-compose.yml"
|
||||||
|
|
||||||
# Deploy
|
# Deploy
|
||||||
|
log_info "Starting Sablier container..."
|
||||||
cd "$sablier_dir"
|
cd "$sablier_dir"
|
||||||
run_cmd docker compose up -d
|
if ! docker compose up -d; then
|
||||||
|
log_error "Failed to start Sablier stack"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
log_success "Sablier stack deployed at $sablier_dir"
|
log_success "Sablier stack deployed at $sablier_dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Copy all stacks for remote server (except core)
|
||||||
|
copy_all_stacks_for_remote() {
|
||||||
|
debug_log "Copying all stacks for remote server"
|
||||||
|
|
||||||
|
# Create base stacks directory
|
||||||
|
sudo mkdir -p /opt/stacks
|
||||||
|
sudo mkdir -p /opt/dockge
|
||||||
|
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks
|
||||||
|
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/dockge
|
||||||
|
|
||||||
|
# List of stacks to copy (all except core and dockge - dockge is handled separately)
|
||||||
|
local stacks=(
|
||||||
|
"alternatives"
|
||||||
|
"dashboards"
|
||||||
|
"homeassistant"
|
||||||
|
"infrastructure"
|
||||||
|
"media"
|
||||||
|
"media-management"
|
||||||
|
"monitoring"
|
||||||
|
"productivity"
|
||||||
|
"sablier"
|
||||||
|
"traefik"
|
||||||
|
"transcoders"
|
||||||
|
"utilities"
|
||||||
|
"vpn"
|
||||||
|
"wikis"
|
||||||
|
)
|
||||||
|
|
||||||
|
local copied_count=0
|
||||||
|
for stack in "${stacks[@]}"; do
|
||||||
|
local src_dir="$REPO_DIR/docker-compose/$stack"
|
||||||
|
local dest_dir="/opt/stacks/$stack"
|
||||||
|
|
||||||
|
# Skip if source doesn't exist
|
||||||
|
if [ ! -d "$src_dir" ]; then
|
||||||
|
debug_log "Skipping $stack - source not found"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create destination directory
|
||||||
|
mkdir -p "$dest_dir"
|
||||||
|
|
||||||
|
# Copy docker-compose.yml and any config directories
|
||||||
|
if [ -f "$src_dir/docker-compose.yml" ]; then
|
||||||
|
cp "$src_dir/docker-compose.yml" "$dest_dir/"
|
||||||
|
cp "$REPO_DIR/.env" "$dest_dir/"
|
||||||
|
|
||||||
|
# Copy any subdirectories (config, etc.)
|
||||||
|
for item in "$src_dir"/*; do
|
||||||
|
if [ -d "$item" ]; then
|
||||||
|
cp -r "$item" "$dest_dir/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clean up sensitive data from .env
|
||||||
|
sed -i '/^AUTHELIA_/d' "$dest_dir/.env"
|
||||||
|
sed -i '/^DEFAULT_PASSWORD=/d' "$dest_dir/.env"
|
||||||
|
sed -i '/^CORE_SERVER_PASSWORD=/d' "$dest_dir/.env"
|
||||||
|
|
||||||
|
# Localize compose file
|
||||||
|
localize_compose_labels "$dest_dir/docker-compose.yml" || true
|
||||||
|
|
||||||
|
copied_count=$((copied_count + 1))
|
||||||
|
debug_log "Copied $stack to $dest_dir"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
log_success "Copied $copied_count stacks to /opt/stacks/"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deploy Traefik stack (standalone for remote servers)
|
||||||
|
deploy_traefik_stack() {
|
||||||
|
debug_log "Deploying Traefik stack"
|
||||||
|
|
||||||
|
local traefik_dir="/opt/stacks/traefik"
|
||||||
|
|
||||||
|
# Create required directories
|
||||||
|
mkdir -p "$traefik_dir/config"
|
||||||
|
mkdir -p "$traefik_dir/dynamic"
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
log_info "Starting Traefik container..."
|
||||||
|
cd "$traefik_dir"
|
||||||
|
if ! docker compose up -d; then
|
||||||
|
log_error "Failed to start Traefik stack"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_success "Traefik stack deployed at $traefik_dir"
|
||||||
|
}
|
||||||
|
|
||||||
# Show help function
|
# Show help function
|
||||||
show_help() {
|
show_help() {
|
||||||
echo "EZ-Homelab Setup & Deployment Script"
|
echo "EZ-Homelab Setup & Deployment Script"
|
||||||
@@ -1808,14 +2084,7 @@ main() {
|
|||||||
echo "⚠️ IMPORTANT: Deploying an additional server requires an existing core server to be already deployed."
|
echo "⚠️ IMPORTANT: Deploying an additional server requires an existing core server to be already deployed."
|
||||||
echo "The core server provides essential services like Traefik, Authelia, and shared TLS certificates."
|
echo "The core server provides essential services like Traefik, Authelia, and shared TLS certificates."
|
||||||
echo ""
|
echo ""
|
||||||
read -p "Do you have an existing core server deployed? (y/N): " -n 1 -r
|
|
||||||
echo ""
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
log_info "Returning to main menu. Please deploy a core server first using Option 2."
|
|
||||||
echo ""
|
|
||||||
sleep 2
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
DEPLOY_CORE=false
|
DEPLOY_CORE=false
|
||||||
DEPLOY_INFRASTRUCTURE=false
|
DEPLOY_INFRASTRUCTURE=false
|
||||||
DEPLOY_DASHBOARDS=false
|
DEPLOY_DASHBOARDS=false
|
||||||
@@ -1851,9 +2120,6 @@ main() {
|
|||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Prepare deployment environment
|
|
||||||
prepare_deployment
|
|
||||||
|
|
||||||
# Handle remote server deployment separately
|
# Handle remote server deployment separately
|
||||||
if [ "$DEPLOY_REMOTE_SERVER" = true ]; then
|
if [ "$DEPLOY_REMOTE_SERVER" = true ]; then
|
||||||
# Prompt for configuration values
|
# Prompt for configuration values
|
||||||
@@ -1862,6 +2128,9 @@ main() {
|
|||||||
# Save configuration
|
# Save configuration
|
||||||
save_env_file
|
save_env_file
|
||||||
|
|
||||||
|
# Reload .env file to get all variables including expanded ones
|
||||||
|
load_env_file
|
||||||
|
|
||||||
# Deploy remote server
|
# Deploy remote server
|
||||||
deploy_remote_server
|
deploy_remote_server
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user