vibecoded-personal-site/deploy/scripts/configure-haproxy.sh

294 lines
9.3 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "${SCRIPT_DIR}/common-lib.sh" ]; then
source "${SCRIPT_DIR}/common-lib.sh"
else
echo "❌ ERROR: common-lib.sh not found at ${SCRIPT_DIR}/common-lib.sh"
exit 1
fi
# Validate common required environment variables
require_var "DEPLOY_HOST"
readonly HAPROXY_BASE_DIR="/srv/haproxy"
readonly HAPROXY_CONFIGS_DIR="${HAPROXY_BASE_DIR}/configs"
readonly HAPROXY_COMPOSE_FILE="${HAPROXY_BASE_DIR}/docker-compose.yml"
readonly HAPROXY_MAIN_CONFIG="${HAPROXY_CONFIGS_DIR}/00-haproxy.cfg"
readonly BACKEND_TEMPLATE_PATH="${SCRIPT_DIR}/../haproxy.backend.cfg"
readonly HAPROXY_COMPOSE_TEMPLATE="${SCRIPT_DIR}/../docker-compose-haproxy.yml"
show_usage() {
cat << EOF
Usage: $0 [add|remove] <backend_name>
Commands:
add <backend_name> - Add backend configuration (uses \$DOMAIN and \$RELEASE_TYPE env vars)
remove <backend_name> - Remove backend configuration
Environment Variables:
DOMAIN - Domain name for the backend (required for add command)
RELEASE_TYPE - Release type (e.g., prod, staging) (required for add command)
Examples:
DOMAIN=badblocks.dev RELEASE_TYPE=prod $0 add portfolio-prod
$0 remove portfolio-staging
EOF
}
setup_infrastructure_dirs_and_certs() {
local certs_dir="${HAPROXY_BASE_DIR}/certs"
echo "🏗️ Setting up HAProxy directories..."
run_or_exit "Failed to setup HAProxy directories" run_on_target "mkdir -p '${HAPROXY_BASE_DIR}' '${HAPROXY_CONFIGS_DIR}' && chmod 755 '${HAPROXY_BASE_DIR}' '${HAPROXY_CONFIGS_DIR}'"
# Add certificate setup if environment variables are set
if [[ -n "${CF_PEM_CERT:-}" ]] && [[ -n "${CF_PEM_CA:-}" ]]; then
echo "🏗️ Setting up HAProxy certificates..."
run_or_exit "Failed to setup HAProxy directories" run_on_target "mkdir -p '${certs_dir}' && chmod 755 '${certs_dir}'"
run_or_exit "Failed to install crt-${DOMAIN}.pem certificate file" run_on_target "cat > '${certs_dir}/crt-${DOMAIN}.pem' << 'EOF'
$CF_PEM_CERT
EOF
chmod 644 '${certs_dir}/crt-${DOMAIN}.pem'"
run_or_exit "Failed to install Cloudflare CA file" run_on_target "cat > '${certs_dir}/ca.pem' << 'EOF'
$CF_PEM_CA
EOF
chmod 644 '${certs_dir}/ca.pem'"
fi
}
init_haproxy_infrastructure() {
echo "🚀 Initializing HAProxy infrastructure..."
setup_infrastructure_dirs_and_certs
# Check both config files in parallel and prepare uploads
local main_config_missing=false
local compose_config_missing=false
local upload_commands=()
if ! run_on_target "test -f '${HAPROXY_MAIN_CONFIG}'"; then
main_config_missing=true
upload_commands+=("scp '${SCRIPT_DIR}/../haproxy.cfg' deploy:'${HAPROXY_MAIN_CONFIG}'")
echo "📋 Main HAProxy config needs creation"
else
echo "✅ Main config already exists"
fi
if ! run_on_target "test -f '${HAPROXY_COMPOSE_FILE}'"; then
[ ! -f "$HAPROXY_COMPOSE_TEMPLATE" ] && error_exit "HAProxy compose template not found at $HAPROXY_COMPOSE_TEMPLATE"
compose_config_missing=true
upload_commands+=("scp '${HAPROXY_COMPOSE_TEMPLATE}' deploy:'${HAPROXY_COMPOSE_FILE}'")
echo "🐳 Docker-compose config needs creation"
else
echo "✅ Docker-compose config already exists"
fi
# Upload missing config files in parallel if any are needed
if [ ${#upload_commands[@]} -gt 0 ]; then
echo "📤 Uploading configuration files..."
for cmd in "${upload_commands[@]}"; do
eval "$cmd" || error_exit "Failed to upload config file: $cmd"
done &
wait
echo "✅ Configuration files uploaded"
fi
# Start HAProxy service (creates network automatically via compose)
if ! is_haproxy_running; then
echo "▶️ Starting HAProxy service..."
if run_on_target "cd '${HAPROXY_BASE_DIR}' && docker compose up -d"; then
echo "✅ HAProxy containers started"
echo "⏳ Waiting for HAProxy to be healthy..."
if wait_for_haproxy_healthy; then
echo "✅ HAProxy is healthy and ready"
else
echo "❌ ERROR: HAProxy failed to become healthy"
return 1
fi
else
echo "❌ ERROR: Failed to start HAProxy containers"
exit 1
fi
else
echo "✅ HAProxy is already running"
fi
echo "✅ HAProxy infrastructure ready"
}
wait_for_haproxy_healthy() {
local attempts=0
local max_attempts=30
while [ $attempts -lt $max_attempts ]; do
attempts=$((attempts + 1))
# Get status in single call and parse more efficiently
local container_info
container_info=$(run_on_target "cd '${HAPROXY_BASE_DIR}' && docker compose ps haproxy --format '{{.Status}}'")
# Check if container is healthy or running (single grep operation)
if echo "$container_info" | grep -qE "(health: healthy|Up.*[0-9])"; then
return 0
fi
[ $attempts -lt $max_attempts ] && sleep 2
done
echo "❌ ERROR: HAProxy failed to become healthy after $max_attempts attempts"
echo " Last status: $container_info"
return 1
}
is_haproxy_running() {
run_on_target "cd '${HAPROXY_BASE_DIR}' && docker compose ps haproxy --format '{{.Status}}' | grep -q 'Up'"
}
is_haproxy_infrastructure_ready() {
if ! run_on_target "test -d '${HAPROXY_BASE_DIR}'"; then
return 1
fi
if ! run_on_target "test -f '${HAPROXY_MAIN_CONFIG}'"; then
return 1
fi
if ! run_on_target "test -f '${HAPROXY_COMPOSE_FILE}'"; then
return 1
fi
if ! is_haproxy_running; then
return 1
fi
return 0
}
add_backend() {
local backend_name="$1"
[[ -z "$backend_name" ]] && {
show_usage
error_exit "add command requires backend_name"
}
[[ -z "${DOMAIN:-}" ]] && {
show_usage
error_exit "DOMAIN environment variable is required for add command"
}
[[ -z "${RELEASE_TYPE:-}" ]] && {
show_usage
error_exit "RELEASE_TYPE environment variable is required for add command"
}
echo " Adding backend: $backend_name"
# TODO: MAKE THIS IDEMPOTENT AND REMOVE THE is_haproxy_infrastructure_ready CHECK
if ! is_haproxy_infrastructure_ready; then
echo "🔧 Initializing HAProxy infrastructure..."
init_haproxy_infrastructure
fi
local backend_config_path="${HAPROXY_CONFIGS_DIR}/${backend_name}.cfg"
[ ! -f "$BACKEND_TEMPLATE_PATH" ] && error_exit "Backend template not found at $BACKEND_TEMPLATE_PATH"
local temp_config="/tmp/${backend_name}.cfg"
echo "📝 Generating backend config from template..."
cp "$BACKEND_TEMPLATE_PATH" "$temp_config"
substitute_env_vars "$temp_config"
echo "✅ Backend config generated"
echo "📤 Uploading backend config..."
scp "$temp_config" deploy:"$backend_config_path" || { rm -f "$temp_config"; error_exit "Failed to upload backend config"; }
echo "✅ Backend config uploaded"
rm -f "$temp_config"
echo "🔄 Reloading HAProxy..."
reload_haproxy
}
remove_backend() {
local backend_name="$1"
[ -z "$backend_name" ] && { show_usage; error_exit "remove command requires backend_name"; }
echo " Removing backend: $backend_name"
if ! is_haproxy_infrastructure_ready; then
echo "🔧 Initializing HAProxy infrastructure..."
init_haproxy_infrastructure
fi
local backend_config_path="${HAPROXY_CONFIGS_DIR}/${backend_name}.cfg"
if run_on_target "test -f '${backend_config_path}'"; then
run_on_target "rm -f '${backend_config_path}'"
echo "✅ Backend configuration removed"
reload_haproxy
else
echo "✅ Backend configuration already absent"
fi
}
reload_haproxy() {
if ! is_haproxy_infrastructure_ready; then
echo "🔧 Infrastructure not ready, initializing..."
init_haproxy_infrastructure
return 0 # Infrastructure init already handles health check
fi
run_or_exit "Failed to send reload signal to HAProxy" run_on_target "cd '${HAPROXY_BASE_DIR}' && docker kill -s HUP haproxy"
# Brief pause for reload to take effect, then single health check
sleep 1
if wait_for_haproxy_healthy; then
echo "✅ HAProxy configuration reloaded"
else
error_exit "HAProxy became unhealthy after reload"
fi
}
validate_backend_name() {
local backend_name="$1"
[ -z "$backend_name" ] && error_exit "Backend name cannot be empty"
[[ "$backend_name" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]] 2>/dev/null || error_exit "Invalid backend name '$backend_name'. Can only contain alphanumeric characters, hyphens, and underscores."
}
main() {
local command="${1:-}"
case "$command" in
"add")
local backend_name="${2:-}"
validate_backend_name "$backend_name"
add_backend "$backend_name"
;;
"remove")
local backend_name="${2:-}"
validate_backend_name "$backend_name"
remove_backend "$backend_name"
;;
"help"|"-h"|"--help")
show_usage
;;
*)
show_usage
error_exit "Unknown command '$command'"
;;
esac
echo "✅ HAProxy configuration completed"
}
[[ "${BASH_SOURCE[0]}" == "${0}" ]] && main "$@"