v0.1.2: Multi-server architecture + security cleanup
- Implement multi-server Traefik + Sablier architecture - Add label-based automatic service discovery - Create separate Sablier stack deployment - Add remote server deployment workflow (Option 3) - Add 9 new functions for multi-server management - Remove deprecated config-templates folder - Replace hardcoded private data with placeholders - Update backup timestamp format to YY_MM_DD_hh_mm - Add markup.yml to .gitignore Breaking changes: - Removed Sablier from core docker-compose.yml (now separate stack) - Config templates moved from config-templates/ to docker-compose/core/ - REQUIRED_VARS now dynamic based on deployment type
This commit is contained in:
@@ -215,3 +215,130 @@ run_cmd() {
|
||||
fi
|
||||
fi
|
||||
}
|
||||
# =============================================
|
||||
# MULTI-SERVER FUNCTIONS
|
||||
# =============================================
|
||||
|
||||
# Detect server role based on deployed stacks
|
||||
detect_server_role() {
|
||||
debug_log "Detecting server role"
|
||||
|
||||
if [ -d "/opt/stacks/core" ] && [ -f "/opt/stacks/core/docker-compose.yml" ]; then
|
||||
echo "core"
|
||||
debug_log "Detected role: core"
|
||||
else
|
||||
echo "remote"
|
||||
debug_log "Detected role: remote"
|
||||
fi
|
||||
}
|
||||
|
||||
# Generate Traefik provider configuration for a remote Docker host
|
||||
generate_traefik_provider_config() {
|
||||
local server_ip="$1"
|
||||
local server_hostname="$2"
|
||||
local output_file="$3"
|
||||
|
||||
debug_log "Generating Traefik provider config for $server_hostname ($server_ip)"
|
||||
|
||||
if [ -z "$server_ip" ] || [ -z "$server_hostname" ] || [ -z "$output_file" ]; then
|
||||
log_error "generate_traefik_provider_config requires server_ip, server_hostname, and output_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cat > "$output_file" <<EOF
|
||||
# Traefik Docker Provider for Remote Server: $server_hostname
|
||||
# Auto-generated by EZ-Homelab
|
||||
# Last updated: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
providers:
|
||||
docker:
|
||||
endpoint: "tcp://${server_ip}:2376"
|
||||
exposedByDefault: false
|
||||
network: traefik-network
|
||||
watch: true
|
||||
tls:
|
||||
ca: /shared-ca/ca.pem
|
||||
cert: /shared-ca/cert.pem
|
||||
key: /shared-ca/key.pem
|
||||
insecureSkipVerify: false
|
||||
# Server-specific constraints
|
||||
defaultRule: "Host(\`{{ normalize .Name }}.${DUCKDNS_DOMAIN}\`)"
|
||||
EOF
|
||||
|
||||
log_success "Generated Traefik provider config: $output_file"
|
||||
debug_log "Provider config written to $output_file"
|
||||
}
|
||||
|
||||
# Generate Sablier middleware configuration for remote server
|
||||
generate_sablier_middleware_config() {
|
||||
local server_hostname="$1"
|
||||
local server_ip="$2"
|
||||
local output_file="$3"
|
||||
|
||||
debug_log "Generating Sablier middleware config for $server_hostname ($server_ip)"
|
||||
|
||||
if [ -z "$server_hostname" ] || [ -z "$server_ip" ] || [ -z "$output_file" ]; then
|
||||
log_error "generate_sablier_middleware_config requires server_hostname, server_ip, and output_file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
cat > "$output_file" <<EOF
|
||||
# Sablier Middleware for Remote Server: $server_hostname
|
||||
# Auto-generated by EZ-Homelab
|
||||
# Last updated: $(date '+%Y-%m-%d %H:%M:%S')
|
||||
#
|
||||
# This middleware enables lazy loading for services on $server_hostname
|
||||
# Each server has its own Sablier instance managing local containers
|
||||
|
||||
http:
|
||||
middlewares:
|
||||
sablier-${server_hostname}:
|
||||
plugin:
|
||||
sablier:
|
||||
sablierUrl: "http://${server_ip}:10000"
|
||||
sessionDuration: "5m"
|
||||
dynamic:
|
||||
theme: "hacker-terminal"
|
||||
EOF
|
||||
|
||||
log_success "Generated Sablier middleware config: $output_file"
|
||||
debug_log "Sablier middleware config written to $output_file"
|
||||
}
|
||||
|
||||
# Register remote server with core Traefik
|
||||
add_remote_server_to_traefik() {
|
||||
local server_ip="$1"
|
||||
local server_hostname="$2"
|
||||
|
||||
debug_log "Registering remote server $server_hostname with core Traefik"
|
||||
|
||||
if [ -z "$server_ip" ] || [ -z "$server_hostname" ]; then
|
||||
log_error "add_remote_server_to_traefik requires server_ip and server_hostname"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if core stack exists
|
||||
if [ ! -d "/opt/stacks/core" ]; then
|
||||
log_error "Core stack not found at /opt/stacks/core - cannot register remote server"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local traefik_dynamic_dir="/opt/stacks/core/traefik/dynamic"
|
||||
|
||||
# Create dynamic directory if it doesn't exist
|
||||
if [ ! -d "$traefik_dynamic_dir" ]; then
|
||||
log_info "Creating Traefik dynamic config directory"
|
||||
mkdir -p "$traefik_dynamic_dir"
|
||||
fi
|
||||
|
||||
# Generate provider configuration
|
||||
local provider_file="${traefik_dynamic_dir}/docker-provider-${server_hostname}.yml"
|
||||
generate_traefik_provider_config "$server_ip" "$server_hostname" "$provider_file"
|
||||
|
||||
# Generate Sablier middleware configuration
|
||||
local sablier_file="${traefik_dynamic_dir}/sablier-middleware-${server_hostname}.yml"
|
||||
generate_sablier_middleware_config "$server_hostname" "$server_ip" "$sablier_file"
|
||||
|
||||
log_success "Registered remote server $server_hostname with core Traefik"
|
||||
log_info "Traefik will auto-reload configurations within 2 seconds"
|
||||
}
|
||||
@@ -206,17 +206,6 @@ localize_deployment() {
|
||||
done < <(find "$REPO_DIR/docker-compose" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Process config-templates files
|
||||
if [ -d "$REPO_DIR/config-templates" ]; then
|
||||
while IFS= read -r -d '' file_path; do
|
||||
if [ -f "$file_path" ]; then
|
||||
debug_log "Processing config template file: $file_path"
|
||||
localize_yml_file "$file_path" false
|
||||
processed_files=$((processed_files + 1))
|
||||
fi
|
||||
done < <(find "$REPO_DIR/config-templates" -name "*.yml" -o -name "*.yaml" -print0 2>/dev/null)
|
||||
fi
|
||||
|
||||
log_success "Deployment localization completed - processed $processed_files files"
|
||||
debug_log "Localization completed for $processed_files files"
|
||||
|
||||
@@ -909,9 +898,9 @@ deploy_core() {
|
||||
# Copy and configure Traefik config
|
||||
debug_log "Setting up Traefik configuration"
|
||||
if [ -d "/opt/stacks/core/traefik" ]; then
|
||||
mv /opt/stacks/core/traefik /opt/stacks/core/traefik.backup.$(date +%Y%m%d_%H%M%S)
|
||||
mv /opt/stacks/core/traefik /opt/stacks/core/traefik.backup.$(date +%y_%m_%d_%H_%M)
|
||||
fi
|
||||
cp -r "$REPO_DIR/config-templates/traefik" /opt/stacks/core/
|
||||
cp -r "$REPO_DIR/docker-compose/core/traefik" /opt/stacks/core/
|
||||
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/traefik
|
||||
|
||||
# Move Traefik config file to the correct location for Docker mount
|
||||
@@ -954,9 +943,9 @@ deploy_core() {
|
||||
# Copy and configure Authelia config
|
||||
debug_log "Setting up Authelia configuration"
|
||||
if [ -d "/opt/stacks/core/authelia" ]; then
|
||||
mv /opt/stacks/core/authelia /opt/stacks/core/authelia.backup.$(date +%Y%m%d_%H%M%S)
|
||||
mv /opt/stacks/core/authelia /opt/stacks/core/authelia.backup.$(date +%y_%m_%d_%H_%M)
|
||||
fi
|
||||
cp -r "$REPO_DIR/config-templates/authelia" /opt/stacks/core/
|
||||
cp -r "$REPO_DIR/docker-compose/core/authelia" /opt/stacks/core/
|
||||
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" /opt/stacks/core/authelia
|
||||
|
||||
# Replace all placeholders in Authelia config files
|
||||
@@ -991,6 +980,11 @@ deploy_core() {
|
||||
run_cmd docker compose up -d || true
|
||||
log_success "Core infrastructure deployed"
|
||||
echo ""
|
||||
|
||||
# Deploy Sablier stack for lazy loading
|
||||
log_info "Deploying Sablier stack for lazy loading..."
|
||||
deploy_sablier_stack
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Deploy infrastructure stack function
|
||||
@@ -1363,6 +1357,154 @@ show_main_menu() {
|
||||
echo ""
|
||||
}
|
||||
|
||||
# =============================================
|
||||
# MULTI-SERVER DEPLOYMENT FUNCTIONS
|
||||
# =============================================
|
||||
|
||||
# Check if Docker is installed and accessible
|
||||
check_docker_installed() {
|
||||
debug_log "Checking if Docker is installed"
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker is not installed on this system"
|
||||
log_info "Please run Option 1 (Install Prerequisites) first"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! docker ps &> /dev/null; then
|
||||
log_error "Docker is installed but not accessible"
|
||||
log_info "Current user may not be in docker group. Try logging out and back in."
|
||||
return 1
|
||||
fi
|
||||
|
||||
debug_log "Docker is installed and accessible"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Set required variables based on deployment type
|
||||
set_required_vars_for_deployment() {
|
||||
local deployment_type="$1"
|
||||
debug_log "Setting required vars for deployment type: $deployment_type"
|
||||
|
||||
case "$deployment_type" in
|
||||
"core")
|
||||
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_SUBDOMAINS" "DUCKDNS_TOKEN" "DOMAIN" "DEFAULT_USER" "DEFAULT_PASSWORD" "DEFAULT_EMAIL")
|
||||
debug_log "Set REQUIRED_VARS for core deployment"
|
||||
;;
|
||||
"remote")
|
||||
REQUIRED_VARS=("SERVER_IP" "SERVER_HOSTNAME" "DUCKDNS_DOMAIN" "DEFAULT_USER" "REMOTE_SERVER_IP" "REMOTE_SERVER_HOSTNAME" "REMOTE_SERVER_USER")
|
||||
debug_log "Set REQUIRED_VARS for remote deployment"
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown deployment type: $deployment_type"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Deploy remote server
|
||||
deploy_remote_server() {
|
||||
log_info "Deploying Remote Server Configuration"
|
||||
echo ""
|
||||
|
||||
# Check Docker is installed
|
||||
if ! check_docker_installed; then
|
||||
log_error "Docker must be installed before deploying remote server"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ensure we have core server information
|
||||
if [ -z "$REMOTE_SERVER_IP" ] || [ -z "$REMOTE_SERVER_HOSTNAME" ]; then
|
||||
log_error "Remote server IP and hostname are required"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Configuring Docker TLS for remote API access..."
|
||||
setup_docker_tls
|
||||
|
||||
log_info "Fetching shared CA from core server..."
|
||||
setup_multi_server_tls "$REMOTE_SERVER_IP" "$REMOTE_SERVER_USER"
|
||||
|
||||
log_info "Deploying Sablier stack for local lazy loading..."
|
||||
deploy_sablier_stack
|
||||
|
||||
log_info "Registering this remote server with core Traefik..."
|
||||
register_remote_server_with_core
|
||||
|
||||
log_success "Remote server deployment complete!"
|
||||
echo ""
|
||||
echo "This server is now configured to:"
|
||||
echo " - Accept Docker API connections via TLS (port 2376)"
|
||||
echo " - Run Sablier for local container lazy loading"
|
||||
echo " - Have its containers discovered by core Traefik"
|
||||
echo ""
|
||||
echo "Services deployed on this server will automatically:"
|
||||
echo " - Be discovered by Traefik on the core server"
|
||||
echo " - Get SSL certificates via core Traefik"
|
||||
echo " - Be accessible at: https://servicename.${DUCKDNS_DOMAIN}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Register remote server with core Traefik
|
||||
register_remote_server_with_core() {
|
||||
debug_log "Registering remote server with core Traefik via SSH"
|
||||
|
||||
if [ -z "$REMOTE_SERVER_IP" ] || [ -z "$REMOTE_SERVER_USER" ]; then
|
||||
log_error "REMOTE_SERVER_IP and REMOTE_SERVER_USER are required"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Connecting to core server to register this remote server..."
|
||||
|
||||
# SSH to core server and run registration function
|
||||
ssh -o ConnectTimeout=10 "${REMOTE_SERVER_USER}@${REMOTE_SERVER_IP}" bash <<EOF
|
||||
# Source common.sh to get registration function
|
||||
source ~/EZ-Homelab/scripts/common.sh
|
||||
|
||||
# Register this remote server
|
||||
add_remote_server_to_traefik "${SERVER_IP}" "${SERVER_HOSTNAME}"
|
||||
|
||||
# Restart Traefik to reload configs
|
||||
cd /opt/stacks/core
|
||||
docker compose restart traefik
|
||||
EOF
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
log_success "Successfully registered with core server"
|
||||
else
|
||||
log_error "Failed to register with core server via SSH"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Deploy Sablier stack
|
||||
deploy_sablier_stack() {
|
||||
debug_log "Deploying Sablier stack"
|
||||
|
||||
local sablier_dir="/opt/stacks/sablier"
|
||||
|
||||
# Create sablier stack directory
|
||||
if [ ! -d "$sablier_dir" ]; then
|
||||
mkdir -p "$sablier_dir"
|
||||
sudo chown -R "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir"
|
||||
fi
|
||||
|
||||
# Copy stack files
|
||||
cp "$REPO_DIR/docker-compose/sablier/docker-compose.yml" "$sablier_dir/"
|
||||
cp "$REPO_DIR/.env" "$sablier_dir/"
|
||||
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/docker-compose.yml"
|
||||
sudo chown "$ACTUAL_USER:$ACTUAL_USER" "$sablier_dir/.env"
|
||||
|
||||
# Localize the docker-compose file
|
||||
localize_compose_labels "$sablier_dir/docker-compose.yml"
|
||||
|
||||
# Deploy
|
||||
cd "$sablier_dir"
|
||||
run_cmd docker compose up -d
|
||||
|
||||
log_success "Sablier stack deployed at $sablier_dir"
|
||||
}
|
||||
|
||||
# Show help function
|
||||
show_help() {
|
||||
echo "EZ-Homelab Setup & Deployment Script"
|
||||
@@ -1611,15 +1753,42 @@ main() {
|
||||
;;
|
||||
2)
|
||||
log_info "Selected: Deploy Core Server"
|
||||
|
||||
# Check Docker first
|
||||
if ! check_docker_installed; then
|
||||
echo ""
|
||||
log_error "Docker must be installed before deploying core server"
|
||||
log_info "Please run Option 1 (Install Prerequisites) first"
|
||||
echo ""
|
||||
read -p "Press Enter to return to main menu..."
|
||||
continue
|
||||
fi
|
||||
|
||||
DEPLOY_CORE=true
|
||||
DEPLOY_INFRASTRUCTURE=true
|
||||
DEPLOY_DASHBOARDS=true
|
||||
SETUP_STACKS=true
|
||||
DEPLOY_REMOTE_SERVER=false
|
||||
|
||||
# Set required variables for core deployment
|
||||
set_required_vars_for_deployment "core"
|
||||
|
||||
break
|
||||
;;
|
||||
3)
|
||||
log_info "Selected: Deploy Additional Server"
|
||||
echo ""
|
||||
|
||||
# Check Docker first
|
||||
if ! check_docker_installed; then
|
||||
echo ""
|
||||
log_error "Docker must be installed before deploying remote server"
|
||||
log_info "Please run Option 1 (Install Prerequisites) first"
|
||||
echo ""
|
||||
read -p "Press Enter to return to main menu..."
|
||||
continue
|
||||
fi
|
||||
|
||||
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 ""
|
||||
@@ -1632,9 +1801,14 @@ main() {
|
||||
continue
|
||||
fi
|
||||
DEPLOY_CORE=false
|
||||
DEPLOY_INFRASTRUCTURE=true
|
||||
DEPLOY_INFRASTRUCTURE=false
|
||||
DEPLOY_DASHBOARDS=false
|
||||
SETUP_STACKS=true
|
||||
SETUP_STACKS=false
|
||||
DEPLOY_REMOTE_SERVER=true
|
||||
|
||||
# Set required variables for remote deployment
|
||||
set_required_vars_for_deployment "remote"
|
||||
|
||||
break
|
||||
;;
|
||||
4)
|
||||
@@ -1664,6 +1838,22 @@ main() {
|
||||
# Prepare deployment environment
|
||||
prepare_deployment
|
||||
|
||||
# Handle remote server deployment separately
|
||||
if [ "$DEPLOY_REMOTE_SERVER" = true ]; then
|
||||
# Prompt for configuration values
|
||||
validate_and_prompt_variables
|
||||
|
||||
# Save configuration
|
||||
save_env_file
|
||||
|
||||
# Deploy remote server
|
||||
deploy_remote_server
|
||||
|
||||
echo ""
|
||||
log_success "Remote server deployment complete!"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Prompt for configuration values
|
||||
validate_and_prompt_variables
|
||||
|
||||
|
||||
Reference in New Issue
Block a user