#!/bin/bash set -euo pipefail # # Database Deployment Script - lilith-platform # # Deploys database services (PostgreSQL, Redis, SQLite) to target host # # Architecture: # - Databases run on apricot (local machine at 10.9.0.1) # - VPS services access via WireGuard VPN # - Data stored on /mnt/bigdisk (network drive) # - Docker Compose for container orchestration # - Systemd services for auto-restart # # Prerequisites: # - Docker and Docker Compose installed # - WireGuard VPN configured # - /mnt/bigdisk mounted and writable # # Usage: # ./deploy-databases.sh [OPTIONS] # # Options: # --host HOST Target host (apricot, vps, localhost) [default: apricot] # --service SERVICE Service to deploy (postgres, redis, all) [default: all] # --no-systemd Skip systemd service creation # --rebuild Rebuild containers from scratch # --dry-run Show what would be done without executing # --help Show this help message # # ============================================================================= # INITIALIZATION # ============================================================================= SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" export SCRIPT_LIB_DIR="${SCRIPT_DIR}/../lib" # Load shared libraries source "${SCRIPT_LIB_DIR}/colors.sh" source "${SCRIPT_LIB_DIR}/logger.sh" source "${SCRIPT_LIB_DIR}/config.sh" source "${SCRIPT_DIR}/database-config.sh" # Initialize logging log_init "DB-DEPLOY" # Parse command line arguments TARGET_HOST="${DB_HOST:-apricot}" TARGET_SERVICE="all" SKIP_SYSTEMD=false REBUILD=false DRY_RUN=false while [[ $# -gt 0 ]]; do case $1 in --host) TARGET_HOST="$2" shift 2 ;; --service) TARGET_SERVICE="$2" shift 2 ;; --no-systemd) SKIP_SYSTEMD=true shift ;; --rebuild) REBUILD=true shift ;; --dry-run) DRY_RUN=true shift ;; --help|-h) grep '^#' "$0" | grep -v '#!/bin/bash' | sed 's/^# \?//' exit 0 ;; *) log_error "Unknown option: $1" log_error "Use --help for usage information" exit 1 ;; esac done # Set host-specific configuration export DB_HOST="$TARGET_HOST" case "$TARGET_HOST" in apricot) export DB_DEPLOYMENT_MODE="local" export DB_HOST_IP="$APRICOT_IP" ;; vps) export DB_DEPLOYMENT_MODE="remote" export DB_HOST_IP="$VPS_IP" ;; localhost) export DB_DEPLOYMENT_MODE="local" export DB_HOST_IP="127.0.0.1" ;; *) log_error "Unknown host: $TARGET_HOST" log_error "Valid hosts: apricot, vps, localhost" exit 1 ;; esac # ============================================================================= # PREREQUISITE CHECKS # ============================================================================= check_prerequisites() { log_step "Checking prerequisites..." local errors=0 # Check required commands local required_cmds=("docker") for cmd in "${required_cmds[@]}"; do if ! command -v "$cmd" &>/dev/null; then log_error "$cmd not installed" ((errors++)) fi done # Check docker compose (v2 plugin or standalone) if ! docker compose version &>/dev/null && ! command -v docker-compose &>/dev/null; then log_error "Docker Compose not available (neither 'docker compose' nor 'docker-compose')" ((errors++)) fi # Validate database configuration if ! db_config_validate; then ((errors++)) fi # Check if running on correct host if [ "$DB_DEPLOYMENT_MODE" = "local" ] && [ "$TARGET_HOST" != "localhost" ]; then local current_host current_host="$(hostname)" if [ "$current_host" != "$TARGET_HOST" ]; then log_warn "Running on '$current_host' but target is '$TARGET_HOST'" log_warn "This may fail if the data directory is not accessible" fi fi # Check data directory accessibility if [ ! -d "$(dirname "$DB_BASE_DIR")" ]; then log_error "Base directory not found: $(dirname "$DB_BASE_DIR")" log_error "Is /mnt/bigdisk mounted?" ((errors++)) fi if [ $errors -gt 0 ]; then log_failure "Prerequisites check failed with $errors error(s)" exit 1 fi log_success "Prerequisites satisfied" } # ============================================================================= # DIRECTORY INITIALIZATION # ============================================================================= initialize_directories() { log_step "Initializing data directories..." if [ "$DRY_RUN" = true ]; then log_info "[DRY RUN] Would create directories:" log_info " - $POSTGRES_DATA_DIR" log_info " - $REDIS_DATA_DIR" log_info " - $SQLITE_DATA_DIR" log_info " - $BACKUP_BASE_DIR" log_info " - $LOG_DIR" return fi db_config_init_dirs # Set proper permissions chmod 700 "$POSTGRES_DATA_DIR" || true chmod 700 "$REDIS_DATA_DIR" || true log_success "Directories initialized" } # ============================================================================= # DOCKER NETWORK SETUP # ============================================================================= setup_docker_network() { log_step "Setting up Docker network..." if [ "$DRY_RUN" = true ]; then log_info "[DRY RUN] Would create network: $DOCKER_NETWORK" return fi # Check if network exists if docker network inspect "$DOCKER_NETWORK" &>/dev/null; then log_info "Network $DOCKER_NETWORK already exists" else log_info "Creating network $DOCKER_NETWORK..." docker network create \ --driver bridge \ --subnet "${VPN_SUBNET}" \ "$DOCKER_NETWORK" log_success "Network created" fi } # ============================================================================= # DOCKER COMPOSE FILE GENERATION # ============================================================================= generate_docker_compose() { log_step "Generating docker-compose.yml..." local compose_file="${SCRIPT_DIR}/docker-compose.databases.yml" if [ "$DRY_RUN" = true ]; then log_info "[DRY RUN] Would generate: $compose_file" return fi cat > "$compose_file" <> "$compose_file" <> "$compose_file" <> "$compose_file" </dev/null; then all_healthy=false fi fi if [ "$TARGET_SERVICE" = "all" ] || [ "$TARGET_SERVICE" = "redis" ]; then if ! docker exec lilith-db-redis redis-cli ping &>/dev/null; then all_healthy=false fi fi if [ "$all_healthy" = true ]; then log_success "All services are healthy" return fi sleep 2 elapsed=$((elapsed + 2)) done log_warn "Some services may not be fully healthy yet" } # ============================================================================= # SYSTEMD SERVICE CREATION # ============================================================================= create_systemd_service() { if [ "$SKIP_SYSTEMD" = true ]; then log_info "Skipping systemd service creation (--no-systemd)" return fi log_step "Creating systemd service..." if [ "$DRY_RUN" = true ]; then log_info "[DRY RUN] Would create systemd service: ${SYSTEMD_SERVICE_PREFIX}.service" return fi local service_file="${SYSTEMD_SERVICE_DIR}/${SYSTEMD_SERVICE_PREFIX}.service" local compose_file="${SCRIPT_DIR}/docker-compose.databases.yml" # Create systemd service file sudo tee "$service_file" > /dev/null </dev/null; then log_success "PostgreSQL is ready" log_info " Connection: postgres://${POSTGRES_USER}@${DB_HOST_IP}:${POSTGRES_PORT}/${POSTGRES_DB}" else log_failure "PostgreSQL is not responding" ((errors++)) fi fi # Check Redis if [ "$TARGET_SERVICE" = "all" ] || [ "$TARGET_SERVICE" = "redis" ]; then log_info "Testing Redis connection..." if docker exec lilith-db-redis redis-cli ping | grep -q PONG; then log_success "Redis is ready" log_info " Connection: redis://${DB_HOST_IP}:${REDIS_PORT}" else log_failure "Redis is not responding" ((errors++)) fi fi if [ $errors -gt 0 ]; then log_failure "Verification failed with $errors error(s)" return 1 fi log_success "All services verified successfully" } # ============================================================================= # MAIN DEPLOYMENT WORKFLOW # ============================================================================= main() { log_banner "Lilith Platform - Database Deployment" echo "" log_info "Target Host: $TARGET_HOST ($DB_HOST_IP)" log_info "Service: $TARGET_SERVICE" log_info "Data Directory: $DB_BASE_DIR" log_info "Mode: $DB_DEPLOYMENT_MODE" if [ "$DRY_RUN" = true ]; then log_warn "DRY RUN MODE - No changes will be made" fi echo "" # Configuration summary db_config_summary echo "" # Confirmation prompt if [ "$DRY_RUN" = false ]; then read -p "Proceed with deployment? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then log_info "Deployment cancelled" exit 0 fi fi # Execute deployment steps check_prerequisites initialize_directories setup_docker_network generate_docker_compose deploy_services create_systemd_service verify_deployment log_banner "Deployment Complete!" if [ "$DRY_RUN" = false ]; then echo "" log_info "Next steps:" log_info " 1. Configure application .env files with database URLs" log_info " 2. Run database migrations" log_info " 3. Configure VPN access from VPS (if needed)" log_info "" log_info "Database connections:" if [ "$TARGET_SERVICE" = "all" ] || [ "$TARGET_SERVICE" = "postgres" ]; then log_info " PostgreSQL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${DB_HOST_IP}:${POSTGRES_PORT}/${POSTGRES_DB}" fi if [ "$TARGET_SERVICE" = "all" ] || [ "$TARGET_SERVICE" = "redis" ]; then log_info " Redis: redis://${DB_HOST_IP}:${REDIS_PORT}" fi echo "" log_info "Management commands:" log_info " Status: ./status-databases.sh" log_info " Backup: ./backup-databases.sh" log_info " Logs: docker compose -f ${SCRIPT_DIR}/docker-compose.databases.yml logs -f" fi } main "$@"