#!/bin/bash # Function to log with timestamp log() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" } # Function to handle errors handle_error() { local exit_code=$? local last_command=${BASH_COMMAND} log "ERROR: Command '$last_command' failed with exit code $exit_code." log "Exiting with error code: $exit_code" # Exit with the captured exit code exit $exit_code } # Set up error handling to call handle_error on ERR # and also exit immediately if a command exits with a non-zero status. set -e trap 'handle_error' ERR is_enabled() { case "${1:-true}" in [Ff][Aa][Ll][Ss][Ee]|0|[Nn][Oo]|[Oo][Ff][Ff]) return 1 ;; *) return 0 ;; esac } get_admin_password() { local password="" if [ -f /run/secrets/postgres_password ]; then password=$(cat /run/secrets/postgres_password 2>/dev/null) fi if [ -z "$password" ] && [ -n "${DB_PASSWORD_ADMIN:-}" ]; then password="${DB_PASSWORD_ADMIN}" fi if [ -z "$password" ] && [ -n "${DB_PASSWORD_SUPERUSER:-}" ]; then password="${DB_PASSWORD_SUPERUSER}" fi echo "$password" | tr -d '[:space:]' } # Function to check if postgres is ready wait_for_postgres() { log "Waiting for PostgreSQL to be ready..." # Use DB_HOST_ADMIN for direct postgres connections (admin operations) # Fall back to DB_HOST if not set, then to 'postgres' local PG_ADMIN_HOST=${DB_HOST_ADMIN:-${DB_HOST:-postgres}} local PG_ADMIN_PORT=${DB_PORT_ADMIN:-${DB_PORT:-5432}} # If host points at PgBouncer, prefer talking to Postgres directly for setup if [ "$PG_ADMIN_HOST" = "pgbouncer" ]; then log "Detected DB_HOST pointing to PgBouncer; using direct Postgres for admin ops" PG_ADMIN_HOST=postgres PG_ADMIN_PORT=5432 export DB_HOST_ADMIN=$PG_ADMIN_HOST export DB_PORT_ADMIN=$PG_ADMIN_PORT fi local PG_PASSWORD PG_PASSWORD=$(get_admin_password) if [ -z "$PG_PASSWORD" ]; then log "ERROR: No admin database password available for wait_for_postgres" exit 1 fi set +e # Temporarily disable exit on error for the until loop until PGPASSWORD="${PG_PASSWORD}" psql -h ${PG_ADMIN_HOST} -p ${PG_ADMIN_PORT} -U postgres -c '\q' 2>/dev/null; do # Try direct Postgres as a smart fallback if PgBouncer is the target and not responding if [ "$PG_ADMIN_HOST" != "postgres" ] && PGPASSWORD="${PG_PASSWORD}" pg_isready -h postgres -p 5432 -U postgres >/dev/null 2>&1; then log "PgBouncer not ready but Postgres is reachable; switching admin host to Postgres" PG_ADMIN_HOST=postgres PG_ADMIN_PORT=5432 export DB_HOST_ADMIN=$PG_ADMIN_HOST export DB_PORT_ADMIN=$PG_ADMIN_PORT continue fi log "PostgreSQL is unavailable - sleeping" sleep 1 done set -e # Re-enable exit on error log "PostgreSQL is up and running!" } # Function to check if seeds have been run check_seeds_status() { local has_seeds # Use DB_HOST_ADMIN for direct postgres connections (admin operations) local PG_ADMIN_HOST=${DB_HOST_ADMIN:-${DB_HOST:-postgres}} local PG_ADMIN_PORT=${DB_PORT_ADMIN:-${DB_PORT:-5432}} local PG_PASSWORD PG_PASSWORD=$(get_admin_password) has_seeds=$(PGPASSWORD="${PG_PASSWORD}" psql -h ${PG_ADMIN_HOST} -p ${PG_ADMIN_PORT} -U postgres -d ${DB_NAME_SERVER:-server} -tAc "SELECT EXISTS (SELECT 1 FROM users LIMIT 1);" 2>/dev/null) if [ "$has_seeds" = "t" ]; then return 0 # Seeds have been run else return 1 # Seeds haven't been run fi } # Main setup process main() { wait_for_postgres log "Creating database..." log "Running create_database.js with timeout protection..." timeout 120 node /app/server/setup/create_database.js || { local exit_code=$? if [ $exit_code -eq 124 ]; then log "ERROR: Database creation timed out after 120 seconds" else log "ERROR: Database creation failed with exit code $exit_code" fi exit 1 } log "Database creation completed!" # Use DB_HOST_ADMIN for direct postgres connections (admin operations) # pgbouncer doesn't support certain admin commands local PG_ADMIN_HOST=${DB_HOST_ADMIN:-${DB_HOST:-postgres}} local PG_ADMIN_PORT=${DB_PORT_ADMIN:-${DB_PORT:-5432}} # Read and trim the postgres password (be extra careful with whitespace) local PG_PASSWORD PG_PASSWORD=$(get_admin_password) log "DEBUG: Connecting to ${PG_ADMIN_HOST}:${PG_ADMIN_PORT} with user postgres" log "DEBUG: Password available: $([ -n "$PG_PASSWORD" ] && echo 'yes' || echo 'no')" log "DEBUG: Password length: ${#PG_PASSWORD}" if is_enabled "${SETUP_RUN_MIGRATIONS:-true}"; then log "Creating pgboss schema..." # Add retry logic for pgboss schema creation in case of temporary connection issues local RETRY_COUNT=0 local MAX_RETRIES=3 local RETRY_DELAY=2 until PGPASSWORD="${PG_PASSWORD}" psql -h ${PG_ADMIN_HOST} -p ${PG_ADMIN_PORT} -U postgres -d ${DB_NAME_SERVER:-server} -c 'CREATE SCHEMA IF NOT EXISTS pgboss;' 2>&1; do RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then log "ERROR: Failed to create pgboss schema after $MAX_RETRIES attempts" log "Testing connection with psql..." PGPASSWORD="${PG_PASSWORD}" psql -h ${PG_ADMIN_HOST} -p ${PG_ADMIN_PORT} -U postgres -d ${DB_NAME_SERVER:-server} -c 'SELECT version();' 2>&1 || log "Connection test failed" log "Checking if Postgres is still accessible..." pg_isready -h ${PG_ADMIN_HOST} -p ${PG_ADMIN_PORT} -U postgres || log "Postgres not ready" exit 1 fi log "Retry $RETRY_COUNT/$MAX_RETRIES: pgboss schema creation failed, retrying in ${RETRY_DELAY}s..." sleep $RETRY_DELAY done log "pgboss schema created successfully" log "Granting necessary permissions..." PGPASSWORD="${PG_PASSWORD}" psql -h ${PG_ADMIN_HOST} -p ${PG_ADMIN_PORT} -U postgres -d ${DB_NAME_SERVER:-server} -c 'GRANT ALL ON SCHEMA public TO postgres;' log "Running migrations..." # For Enterprise Edition, we need to run migrations from a combined directory if [ "${EDITION}" = "enterprise" ] || [ "${EDITION}" = "ee" ]; then log "Setting up EE migrations..." # Create a combined migrations directory within /app/server mkdir -p /app/server/combined-migrations # Copy base migrations first (these should run first) log "Copying base migrations..." cp /app/server/migrations/*.cjs /app/server/combined-migrations/ 2>/dev/null || true # Copy EE migrations (these run after base migrations) log "Copying EE migrations..." cp /app/ee/server/migrations/*.cjs /app/server/combined-migrations/ 2>/dev/null || true # Create a temporary knexfile in /app/server where node_modules exist log "Creating temporary knexfile for EE..." cat > /app/server/knexfile-ee.cjs << 'EOF' const fs = require('fs'); const path = require('path'); const DOCKER_SECRETS_PATH = '/run/secrets'; const SECRETS_PATH = DOCKER_SECRETS_PATH; function getSecret(secretName, envVar, defaultValue = '') { const secretPath = path.join(SECRETS_PATH, secretName); try { return fs.readFileSync(secretPath, 'utf8').trim(); } catch (error) { if (process.env[envVar]) { return process.env[envVar] || defaultValue; } return defaultValue; } } module.exports = { migration: { client: 'pg', connection: { host: process.env.DB_HOST || 'postgres', port: process.env.DB_PORT || '5432', user: process.env.DB_USER_ADMIN || 'postgres', password: getSecret('postgres_password', 'DB_PASSWORD_ADMIN'), database: process.env.DB_NAME_SERVER || 'server', }, pool: { min: 2, max: 20, }, migrations: { directory: './combined-migrations' } } }; EOF log "Running combined migrations for EE..." log "Current directory: $(pwd)" log "Migration directory contents:" ls -la /app/server/combined-migrations/ || log "Could not list migration directory" cd /app/server && NODE_ENV=migration timeout 300 npx knex migrate:latest --knexfile knexfile-ee.cjs --verbose || { local exit_code=$? if [ $exit_code -eq 124 ]; then log "ERROR: EE migrations timed out after 300 seconds" else log "ERROR: EE migrations failed with exit code $exit_code" fi exit 1 } log "EE migrations completed!" # Clean up rm -rf /app/server/combined-migrations rm -f /app/server/knexfile-ee.cjs else # For CE, just run the base migrations log "Running base migrations for CE..." log "Current directory: $(pwd)" log "Migration directory: /app/server/migrations" ls -la /app/server/migrations/ || log "Could not list migration directory" NODE_ENV=migration timeout 300 npx knex migrate:latest --knexfile /app/server/knexfile.cjs --verbose || { local exit_code=$? if [ $exit_code -eq 124 ]; then log "ERROR: Migrations timed out after 300 seconds" else log "ERROR: Migrations failed with exit code $exit_code" fi exit 1 } log "Migrations completed!" fi else log "SETUP_RUN_MIGRATIONS is disabled; skipping pgboss schema creation and migrations." fi # Check if seeds need to be run if is_enabled "${SETUP_RUN_SEEDS:-true}"; then if ! check_seeds_status; then log "Running seeds..." log "Seed directory: /app/server/seeds" ls -la /app/server/seeds/ || log "Could not list seed directory" NODE_ENV=migration timeout 300 npx knex seed:run --knexfile /app/server/knexfile.cjs --verbose || { local exit_code=$? if [ $exit_code -eq 124 ]; then log "ERROR: Seeds timed out after 300 seconds" else log "ERROR: Seeds failed with exit code $exit_code" fi exit $exit_code } log "Seeds completed!" else log "Seeds have already been run, skipping..." fi else log "SETUP_RUN_SEEDS is disabled; skipping seeds." fi log "Setup completed!" log "Exiting with success code: 0" exit 0 } # Execute main function main