diff --git a/packages/super-sync-server/README.md b/packages/super-sync-server/README.md index 268d91cdf6..8787a87529 100644 --- a/packages/super-sync-server/README.md +++ b/packages/super-sync-server/README.md @@ -51,6 +51,11 @@ are disabled by default so app restarts cannot race the deploy migrator. `./scripts/deploy.sh` runs `prisma migrate deploy` once before replacing the app container, then brings the stack up and verifies the health endpoint. +Leave `DATABASE_URL` unset when using the bundled Postgres service. The default +connection uses `postgres:5432`; existing installs that already set +`DATABASE_URL` with `db:5432` keep working because the Compose service exposes +`db` as a network alias. + > **Upgrade note:** because `RUN_MIGRATIONS_ON_STARTUP` defaults to `false`, > `docker compose pull && docker compose up -d` can leave the app running > against unapplied migrations. Use `./scripts/deploy.sh` for production diff --git a/packages/super-sync-server/docker-compose.yml b/packages/super-sync-server/docker-compose.yml index b1cebca6a9..eaa5351ad7 100644 --- a/packages/super-sync-server/docker-compose.yml +++ b/packages/super-sync-server/docker-compose.yml @@ -79,10 +79,10 @@ services: - postgres-data:/var/lib/postgresql/data healthcheck: test: - [ - 'CMD-SHELL', - 'pg_isready -U ${POSTGRES_USER:-supersync} -d ${POSTGRES_DB:-supersync}', - ] + - CMD-SHELL + - > + pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" && + psql -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -c "SELECT 1" > /dev/null 2>&1 interval: 10s timeout: 5s retries: 5 @@ -91,7 +91,10 @@ services: - no-new-privileges:true mem_limit: 1g networks: - - internal + internal: + aliases: + # Keep existing .env files with DATABASE_URL=...@db:5432/... working. + - db logging: *default-logging caddy: diff --git a/packages/super-sync-server/env.example b/packages/super-sync-server/env.example index 357af2a22c..392fec3dcb 100644 --- a/packages/super-sync-server/env.example +++ b/packages/super-sync-server/env.example @@ -28,7 +28,9 @@ POSTGRES_PASSWORD=your-secure-postgres-password # POSTGRES_DB=supersync # External PostgreSQL connection string. Leave unset to use the bundled compose -# postgres service. If set, also set POSTGRES_SERVICE= below. +# postgres service. The bundled service is reachable as postgres:5432; db:5432 +# is also kept as a compatibility alias for older .env files. If you point this +# at an external host, also set POSTGRES_SERVICE= below. # DATABASE_URL=postgresql://supersync:password@example.com:5432/supersync # ============================================================================= diff --git a/packages/super-sync-server/scripts/deploy.sh b/packages/super-sync-server/scripts/deploy.sh index b23a415aac..31bd655443 100755 --- a/packages/super-sync-server/scripts/deploy.sh +++ b/packages/super-sync-server/scripts/deploy.sh @@ -59,13 +59,31 @@ echo "==> Pulling latest code..." git pull --ff-only || { echo "WARNING: git pull failed — continuing with current files"; } echo "" -# Load GHCR credentials from .env (for private images) -if [ -f ".env" ]; then - GHCR_VARS=$(grep -E '^(GHCR_USER|GHCR_TOKEN)=' ".env" 2>/dev/null | xargs || true) - if [ -n "$GHCR_VARS" ]; then - export $GHCR_VARS +# Load deploy-script settings from .env when they were not already exported. +load_env_value() { + local key="$1" + local line + + if [ -n "${!key+x}" ] || [ ! -f ".env" ]; then + return fi -fi + + line=$(grep -E "^${key}=" ".env" 2>/dev/null | tail -n 1 || true) + if [ -z "$line" ]; then + return + fi + + local value="${line#*=}" + value="${value%\"}" + value="${value#\"}" + value="${value%\'}" + value="${value#\'}" + export "$key=$value" +} + +for env_key in GHCR_USER GHCR_TOKEN DATABASE_URL POSTGRES_SERVICE POSTGRES_WAIT_TIMEOUT MIGRATION_TIMEOUT DEPLOY_WAIT_TIMEOUT; do + load_env_value "$env_key" +done # Login to GHCR if credentials provided if [ -n "${GHCR_TOKEN:-}" ] && [ -n "${GHCR_USER:-}" ]; then @@ -111,7 +129,11 @@ fi # running app available while online index builds run, and it fails the deploy # before the app is restarted if Prisma cannot apply a migration. POSTGRES_WAIT_TIMEOUT="${POSTGRES_WAIT_TIMEOUT:-60}" -POSTGRES_SERVICE="${POSTGRES_SERVICE:-postgres}" +POSTGRES_SERVICE="${POSTGRES_SERVICE-postgres}" +if [ "$POSTGRES_SERVICE" = "postgres" ] && [[ "${DATABASE_URL:-}" == *@db:5432/* ]]; then + export DATABASE_URL="${DATABASE_URL/@db:5432/@postgres:5432}" + echo "==> Rewriting legacy bundled DATABASE_URL host db to postgres for this deploy" +fi echo "" if [ -n "$POSTGRES_SERVICE" ]; then echo "==> Ensuring $POSTGRES_SERVICE is running (wait timeout: ${POSTGRES_WAIT_TIMEOUT}s)..." @@ -125,10 +147,31 @@ echo "" # for arbitrarily long. Wrap the migrator with a timeout so a stuck deploy fails # loudly instead of hanging this script forever. Exit code 124 = timed out. MIGRATION_TIMEOUT="${MIGRATION_TIMEOUT:-900}" +MIGRATOR_RUN="docker compose $COMPOSE_FILES run --rm --no-deps --interactive=false -T supersync" +echo "==> Verifying database connectivity from the supersync image..." +set +e +timeout "$POSTGRES_WAIT_TIMEOUT" \ + $MIGRATOR_RUN sh -ec 'printf "SELECT 1;" | npx prisma db execute --schema prisma/schema.prisma --stdin > /dev/null' +DB_CHECK_STATUS=$? +set -e +if [ "$DB_CHECK_STATUS" -eq 124 ]; then + echo "" + echo "ERROR: database connectivity check timed out after ${POSTGRES_WAIT_TIMEOUT}s." + echo " Check DATABASE_URL and the compose Postgres service health." + exit 1 +fi +if [ "$DB_CHECK_STATUS" -ne 0 ]; then + echo "" + echo "ERROR: database connectivity check failed (exit $DB_CHECK_STATUS)." + echo " Check DATABASE_URL. For the bundled database, leave it unset or use postgres:5432." + exit "$DB_CHECK_STATUS" +fi +echo " Database reachable" +echo "" echo "==> Applying database migrations before app restart (timeout: ${MIGRATION_TIMEOUT}s)..." set +e timeout "$MIGRATION_TIMEOUT" \ - docker compose $COMPOSE_FILES run --rm --no-deps supersync npx prisma migrate deploy + $MIGRATOR_RUN sh -ec 'echo " Migrator container started"; npx prisma migrate deploy' MIGRATE_STATUS=$? set -e if [ "$MIGRATE_STATUS" -eq 124 ]; then diff --git a/packages/super-sync-server/tests/migration-sql.spec.ts b/packages/super-sync-server/tests/migration-sql.spec.ts index 8b4fa116f5..ab5a216342 100644 --- a/packages/super-sync-server/tests/migration-sql.spec.ts +++ b/packages/super-sync-server/tests/migration-sql.spec.ts @@ -61,14 +61,20 @@ describe('performance migrations', () => { const deployScript = readFileSync(join(currentDir, '../scripts/deploy.sh'), 'utf8'); const dockerfile = readFileSync(join(currentDir, '../Dockerfile'), 'utf8'); const composeFile = readFileSync(join(currentDir, '../docker-compose.yml'), 'utf8'); - const migrationCommand = 'run --rm --no-deps supersync npx prisma migrate deploy'; + const migrationCommand = 'npx prisma migrate deploy'; const startCommand = 'up -d --wait --wait-timeout "$WAIT_TIMEOUT"'; const externalDbStartCommand = 'up -d --wait --wait-timeout "$WAIT_TIMEOUT" --no-deps supersync caddy'; expect(deployScript).toContain('POSTGRES_WAIT_TIMEOUT'); - expect(deployScript).toContain('POSTGRES_SERVICE="${POSTGRES_SERVICE:-postgres}"'); + expect(deployScript).toContain('load_env_value()'); + expect(deployScript).toContain('POSTGRES_SERVICE="${POSTGRES_SERVICE-postgres}"'); + expect(deployScript).toContain('@db:5432'); + expect(deployScript).toContain('@postgres:5432'); + expect(deployScript).toContain('run --rm --no-deps --interactive=false -T supersync'); + expect(deployScript).toContain('prisma db execute'); expect(deployScript).toContain(migrationCommand); + expect(deployScript).toContain('Migrator container started'); expect(deployScript).toContain(externalDbStartCommand); expect(deployScript).toContain('RUN_MIGRATIONS_ON_STARTUP'); expect(deployScript.indexOf(migrationCommand)).toBeLessThan( @@ -78,5 +84,10 @@ describe('performance migrations', () => { expect(composeFile).toContain( 'RUN_MIGRATIONS_ON_STARTUP=${RUN_MIGRATIONS_ON_STARTUP:-false}', ); + expect(composeFile).toContain( + 'psql -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" -c "SELECT 1"', + ); + expect(composeFile).toContain('aliases:'); + expect(composeFile).toContain('- db'); }); });