Fix deployment issues found during end-to-end testing

- install.sh: Use tar --strip-components=1 instead of mv for robust
  extraction when install dir partially exists (root-owned Docker
  artifacts)
- config.sh: Add --non-interactive mode (--domain, --admin-password,
  --enable-all flags) for CI/CD and automated deployments
- docker-entrypoint.sh: Validate critical env vars on startup, fail
  early with clear messages instead of silent failures
- docker-compose.yml: Change Redis eviction policy from allkeys-lru
  to noeviction (required by BullMQ job queues)
- Prisma: Add missing petitions.coverVideoId migration (schema had
  the column but migration omitted it, causing 500 on public endpoint)
- Add scripts/uninstall.sh for clean removal including root-owned files
- Add scripts/test-deployment.sh for automated post-install verification

Bunker Admin
This commit is contained in:
2026-04-07 14:06:05 -06:00
parent 74e5fa6475
commit 530551f568
8 changed files with 929 additions and 130 deletions

View File

@@ -7,6 +7,36 @@ if [ "$NODE_ENV" = "production" ] && [ "$NODE_TLS_REJECT_UNAUTHORIZED" = "0" ];
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
@@ -43,6 +73,7 @@ if [ -n "$MAXMIND_ACCOUNT_ID" ] && [ -n "$MAXMIND_LICENSE_KEY" ]; then
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);
@@ -51,7 +82,7 @@ if [ -n "$MAXMIND_ACCOUNT_ID" ] && [ -n "$MAXMIND_LICENSE_KEY" ]; then
res.pipe(out);
out.on('close', () => cb(null));
}).on('error', cb);
get('$DOWNLOAD_URL', (err) => { if (err) { console.error(err.message); process.exit(1); } });
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)
@@ -82,4 +113,9 @@ if [ -f "src/server.ts" ] && echo "$@" | grep -q "npm.*start\|node.*dist"; then
fi
echo "Starting server..."
exec "$@"
# 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

View File

@@ -0,0 +1,4 @@
-- AlterTable: Add missing coverVideoId column to petitions table
-- This column was present in the Prisma schema but omitted from the original
-- 20260402200000_add_petitions migration.
ALTER TABLE "petitions" ADD COLUMN "coverVideoId" INTEGER;