#!/bin/sh set -e # Block NODE_TLS_REJECT_UNAUTHORIZED=0 in production if [ "$NODE_ENV" = "production" ] && [ "$NODE_TLS_REJECT_UNAUTHORIZED" = "0" ]; then echo "FATAL: NODE_TLS_REJECT_UNAUTHORIZED=0 is not allowed in production" exit 1 fi # Validate critical environment variables ENV_ERRORS=0 check_env() { if [ -z "$2" ] || echo "$2" | grep -q "REQUIRED_STRONG_PASSWORD\|GENERATE_WITH_openssl"; then echo "FATAL: $1 is not set or contains a placeholder value" ENV_ERRORS=$((ENV_ERRORS + 1)) fi } check_env "DATABASE_URL" "$DATABASE_URL" check_env "REDIS_URL" "$REDIS_URL" check_env "JWT_ACCESS_SECRET" "$JWT_ACCESS_SECRET" check_env "JWT_REFRESH_SECRET" "$JWT_REFRESH_SECRET" check_env "ENCRYPTION_KEY" "$ENCRYPTION_KEY" check_env "INITIAL_ADMIN_PASSWORD" "$INITIAL_ADMIN_PASSWORD" if [ "$ENV_ERRORS" -gt 0 ]; then echo "" echo "FATAL: $ENV_ERRORS required environment variable(s) missing or invalid." echo "Run the configuration wizard: bash config.sh" echo "Or set them manually in .env and restart." exit 1 fi # Fix permissions for mounted volumes (host dirs may be root-owned on first run) if [ "$(id -u)" = "0" ]; then mkdir -p /app/logs /data/geoip /app/uploads 2>/dev/null || true chown -R node:node /app/logs /data/geoip /app/uploads 2>/dev/null || true fi # Wait for PostgreSQL to be ready before running migrations echo "Waiting for database..." MAX_WAIT=30 WAITED=0 until echo "SELECT 1" | npx prisma db execute --stdin --schema ./prisma/schema.prisma 2>/dev/null; do sleep 2 WAITED=$((WAITED + 2)) if [ $WAITED -ge $MAX_WAIT ]; then echo "FATAL: Database not available after ${MAX_WAIT}s" exit 1 fi done echo "Database ready (${WAITED}s)" # Run migrations — fail hard on error (never fall back to db push, which causes drift) echo "Running Prisma migrations..." npx prisma migrate deploy 2>&1 echo "Migrations complete." echo "Running database seed..." npx prisma db seed 2>&1 || echo "WARNING: Seed failed (non-fatal — seed.ts may require source files not present in production image)" echo "Seed step done." # Download MaxMind GeoLite2 database if credentials are provided and DB is missing/stale if [ -n "$MAXMIND_ACCOUNT_ID" ] && [ -n "$MAXMIND_LICENSE_KEY" ]; then GEOIP_DIR="/data/geoip" GEOIP_DB="$GEOIP_DIR/GeoLite2-City.mmdb" mkdir -p "$GEOIP_DIR" 2>/dev/null || true # Re-download weekly (if file is older than 7 days or missing) if [ ! -f "$GEOIP_DB" ] || [ "$(find "$GEOIP_DB" -mtime +7 2>/dev/null | wc -l)" -gt 0 ]; then echo "Downloading MaxMind GeoLite2-City database..." DOWNLOAD_URL="https://download.maxmind.com/geoip/databases/GeoLite2-City/download?suffix=tar.gz" # Use Node.js for download (BusyBox wget leaks auth header on redirects) if node -e " const https = require('https'); const fs = require('fs'); setTimeout(() => { console.error('GeoIP download timed out after 60s'); process.exit(1); }, 60000).unref(); const auth = Buffer.from('$MAXMIND_ACCOUNT_ID:$MAXMIND_LICENSE_KEY').toString('base64'); const get = (url, cb) => https.get(url, { headers: url.includes('maxmind.com') ? { Authorization: 'Basic ' + auth } : {} }, (res) => { if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) return get(res.headers.location, cb); if (res.statusCode !== 200) { cb(new Error('HTTP ' + res.statusCode)); return; } const out = fs.createWriteStream('/tmp/geolite2.tar.gz'); res.pipe(out); out.on('close', () => cb(null)); }).on('error', cb); get('$DOWNLOAD_URL', (err) => { if (err) { console.error(err.message); process.exit(1); } else { process.exit(0); } }); "; then tar -xzf /tmp/geolite2.tar.gz -C /tmp/ 2>/dev/null MMDB_FILE=$(find /tmp -name 'GeoLite2-City.mmdb' -type f 2>/dev/null | head -1) if [ -n "$MMDB_FILE" ]; then mv "$MMDB_FILE" "$GEOIP_DB" echo "GeoLite2-City database updated." else echo "WARNING: Downloaded archive but no .mmdb file found (non-fatal)" fi rm -rf /tmp/geolite2.tar.gz /tmp/GeoLite2-City_* 2>/dev/null else echo "WARNING: Failed to download GeoLite2 database (non-fatal — geo features disabled)" fi else echo "GeoLite2-City database is up to date." fi else echo "MaxMind credentials not set — GeoIP lookup disabled." fi # If running production mode (node dist/server.js) and dist is stale, recompile if [ -f "src/server.ts" ] && echo "$@" | grep -q "npm.*start\|node.*dist"; then if [ ! -f "dist/server.js" ] || [ "src/server.ts" -nt "dist/server.js" ]; then echo "Compiling TypeScript (dist/ is missing or stale)..." npx tsc 2>&1 || echo "WARNING: TypeScript compilation had errors" echo "Compilation complete." fi fi echo "Starting server..." # Drop to node user if running as root (production image uses su-exec) if [ "$(id -u)" = "0" ] && command -v su-exec >/dev/null 2>&1; then exec su-exec node "$@" else exec "$@" fi