190 lines
7.4 KiB
Bash
Executable File
190 lines
7.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# Entrypoint script for Next.js application
|
|
# Validates environment variables at runtime (not during build)
|
|
set -e
|
|
|
|
echo "🚀 Starting application... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
# Validate critical environment variables at runtime
|
|
if [ "$NODE_ENV" = "production" ]; then
|
|
echo "🔍 Validating production environment... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
# Required vars (expand as needed)
|
|
# Check each variable individually for better portability
|
|
if [ -z "$DATABASE_URL" ]; then
|
|
echo "❌ ERROR: DATABASE_URL is required in production $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit 1
|
|
fi
|
|
if [ -z "$SESSION_SECRET" ]; then
|
|
echo "❌ ERROR: SESSION_SECRET is required in production $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit 1
|
|
fi
|
|
if [ -z "$ADMIN_PASSWORD" ]; then
|
|
echo "❌ ERROR: ADMIN_PASSWORD is required in production $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit 1
|
|
fi
|
|
if [ -z "$CSRF_SECRET" ]; then
|
|
echo "❌ ERROR: CSRF_SECRET is required in production $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit 1
|
|
fi
|
|
if [ -z "$SUPABASE_SERVICE_ROLE_KEY" ]; then
|
|
echo "❌ ERROR: SUPABASE_SERVICE_ROLE_KEY is required in production $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit 1
|
|
fi
|
|
|
|
# Optional: Check URL formats (basic regex)
|
|
if ! echo "$DATABASE_URL" | grep -qE '^postgres(ql)?://'; then
|
|
echo "❌ ERROR: DATABASE_URL must be a valid PostgreSQL connection string $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Environment validation passed $(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
# Allow skipping health check database validation for debugging
|
|
if [ "$SKIP_HEALTH_DB_CHECK" = "1" ]; then
|
|
echo "⚠️ Skipping database check in health endpoint (SKIP_HEALTH_DB_CHECK=1) $(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
# Run Prisma migrations at container startup (DB is reachable now).
|
|
# IMPORTANT: we use `npx --no-install` so the container never tries to download packages.
|
|
if [ "$SKIP_MIGRATIONS" = "1" ]; then
|
|
echo "⏭️ Skipping database migrations (SKIP_MIGRATIONS=1) $(date '+%Y-%m-%d %H:%M:%S')"
|
|
elif [ -n "$DATABASE_URL" ]; then
|
|
echo "🔄 Preparing to run Prisma migrations... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
# Prefer running Prisma CLI directly from node_modules (no network, no npx cache quirks)
|
|
PRISMA_NODE="/app/node_modules/prisma/build/index.js"
|
|
PRISMA_BIN="/app/node_modules/.bin/prisma"
|
|
PRISMA_RUN=""
|
|
|
|
# Ensure schema + migrations are present in the runtime image
|
|
if [ ! -f "/app/prisma/schema.prisma" ]; then
|
|
echo "❌ ERROR: Prisma schema not found at /app/prisma/schema.prisma $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "This deployment requires prisma/schema.prisma to run migrations."
|
|
exit 1
|
|
fi
|
|
if [ ! -d "/app/prisma/migrations" ]; then
|
|
echo "❌ ERROR: Prisma migrations folder not found at /app/prisma/migrations $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "This deployment requires prisma/migrations to run migrations."
|
|
exit 1
|
|
fi
|
|
|
|
# Resolve Prisma runner
|
|
if [ -x "$PRISMA_BIN" ]; then
|
|
PRISMA_RUN="$PRISMA_BIN"
|
|
elif [ -f "$PRISMA_NODE" ]; then
|
|
PRISMA_RUN="node $PRISMA_NODE"
|
|
else
|
|
echo "❌ ERROR: Prisma CLI not found in runtime image. $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "Expected one of:"
|
|
echo " - $PRISMA_BIN"
|
|
echo " - $PRISMA_NODE"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate Prisma can execute (print underlying error output if it can't)
|
|
set +e
|
|
PRISMA_VERSION_OUTPUT=$(sh -c "$PRISMA_RUN --version" 2>&1)
|
|
PRISMA_VERSION_STATUS=$?
|
|
set -e
|
|
if [ "$PRISMA_VERSION_STATUS" -ne 0 ]; then
|
|
echo "❌ ERROR: Prisma CLI is present but failed to run. $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "$PRISMA_VERSION_OUTPUT"
|
|
exit 1
|
|
fi
|
|
|
|
# Retry loop (helps when DB starts slower than app container)
|
|
MIGRATION_MAX_RETRIES="${MIGRATION_MAX_RETRIES:-10}"
|
|
MIGRATION_RETRY_DELAY_SECONDS="${MIGRATION_RETRY_DELAY_SECONDS:-3}"
|
|
attempt=1
|
|
|
|
echo "🔄 Running database migrations (attempts: ${MIGRATION_MAX_RETRIES})... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
did_auto_resolve_email_logs=0
|
|
|
|
while [ "$attempt" -le "$MIGRATION_MAX_RETRIES" ]; do
|
|
echo "➡️ Attempt ${attempt}/${MIGRATION_MAX_RETRIES}: prisma migrate deploy"
|
|
|
|
# Run migrations with timeout to prevent hanging.
|
|
# Capture output so we can detect non-retryable failures (e.g. P3009 failed migrations).
|
|
set +e
|
|
MIGRATION_OUTPUT=$(timeout 120 sh -c "$PRISMA_RUN migrate deploy" 2>&1)
|
|
MIGRATION_STATUS=$?
|
|
set -e
|
|
|
|
if [ "$MIGRATION_STATUS" -eq 0 ]; then
|
|
echo "✅ Prisma migrations applied $(date '+%Y-%m-%d %H:%M:%S')"
|
|
break
|
|
fi
|
|
|
|
echo "$MIGRATION_OUTPUT"
|
|
echo "⚠️ Prisma migrations attempt ${attempt} failed (status: ${MIGRATION_STATUS}) $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
# Non-retryable: Prisma has a failed migration recorded (P3009).
|
|
# For our known case, auto-resolve the broken email_logs migration and retry once.
|
|
if echo "$MIGRATION_OUTPUT" | grep -q "Error: P3009"; then
|
|
if echo "$MIGRATION_OUTPUT" | grep -q "20260116150000_add_email_logs_table" && [ "$did_auto_resolve_email_logs" -eq 0 ]; then
|
|
did_auto_resolve_email_logs=1
|
|
echo "🛠️ Detected failed migration 20260116150000_add_email_logs_table (P3009). Attempting auto-resolve as applied so deployment can continue..."
|
|
set +e
|
|
$PRISMA_RUN migrate resolve --applied 20260116150000_add_email_logs_table 2>&1
|
|
RESOLVE_STATUS=$?
|
|
set -e
|
|
if [ "$RESOLVE_STATUS" -ne 0 ]; then
|
|
echo "❌ ERROR: Failed to resolve migration state automatically. Please run:"
|
|
echo " prisma migrate resolve --applied 20260116150000_add_email_logs_table"
|
|
exit 1
|
|
fi
|
|
echo "✅ Marked 20260116150000_add_email_logs_table as applied. Retrying migrate deploy..."
|
|
attempt=$((attempt + 1))
|
|
continue
|
|
fi
|
|
|
|
echo "❌ ERROR: Prisma reports failed migrations in the target database (P3009). This is not retryable."
|
|
echo "Fix by resolving the failed migration in the DB, then redeploy. Common command:"
|
|
echo " prisma migrate resolve --applied <MIGRATION_NAME>"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$attempt" -ge "$MIGRATION_MAX_RETRIES" ]; then
|
|
echo "❌ ERROR: Prisma migrations failed after ${MIGRATION_MAX_RETRIES} attempts $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "Database URL: $(echo "$DATABASE_URL" | sed 's|//.*@|//***:***@|')" # Hide credentials in logs
|
|
|
|
# In production, fail fast to avoid running with a mismatched schema.
|
|
if [ "$NODE_ENV" = "production" ]; then
|
|
exit 1
|
|
fi
|
|
|
|
echo "⚠️ Continuing startup despite migration issues (non-production only)..."
|
|
break
|
|
fi
|
|
|
|
attempt=$((attempt + 1))
|
|
echo "⏳ Waiting ${MIGRATION_RETRY_DELAY_SECONDS}s before retry..."
|
|
sleep "$MIGRATION_RETRY_DELAY_SECONDS"
|
|
done
|
|
|
|
echo "✅ Migrations step complete $(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
# Optional: Run seeding if RUN_SEED=1 (set in env vars)
|
|
if [ -n "$RUN_SEED" ]; then
|
|
echo "🌱 Running database seeding... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
npm run db:seed || { echo "❌ ERROR: Seeding failed $(date '+%Y-%m-%d %H:%M:%S')"; exit 1; }
|
|
echo "✅ Seeding complete $(date '+%Y-%m-%d %H:%M:%S')"
|
|
fi
|
|
|
|
# Start the application
|
|
echo "🎯 Starting Next.js server... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
|
|
# Check if server.js exists (Next.js standalone output)
|
|
if [ -f "server.js" ]; then
|
|
echo "✅ Found server.js, starting with standalone build... $(date '+%Y-%m-%d %H:%M:%S')"
|
|
exec node server.js
|
|
else
|
|
echo "❌ ERROR: server.js not found. This might indicate a build issue. $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "Contents of /app directory:"
|
|
ls -la
|
|
exit 1
|
|
fi |