mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 14:03:33 +09:00
## Description `builder_backend_wait_for_db` checks readiness by running `psql` inside the `db` container, which connects over the **unix socket**. The official `postgres` image's entrypoint starts a temporary server with `listen_addresses=''` (socket only) while it runs its init phase. During that window the socket probe succeeds, but host-side clients (prisma migrate, postgrest) connecting over TCP still get `connection refused` . so the wait can return before the database is actually reachable. It's a timing race: it rarely bites with a fast init, but it's reproducible with slower images/machines (we hit it consistently in our fork's CI, which uses a pg image with a longer init phase). Fix: make the readiness probe connect the same way real clients do. Over TCP (`psql -h 127.0.0.1`). `PGPASSWORD` is needed because host connections use password auth, unlike the socket which is `trust`. Same one-line change to the probe in `builder_backend_wait_for_db`; behavior is otherwise identical. ## Steps for reproduction 1. `docker compose down -v` in `apps/builder` (force a fresh initdb) 2. run `dev/run-local.sh` (or anything using `builder_backend_wait_for_db`) 3. on a slow machine the wait returns during the init phase and the following `prisma migrate` / postgrest startup fails with `connection refused`; with this patch the wait only returns once TCP is accepting connections ## Code Review - [ ] hi @kof, I need you to do - detailed review (it's a one-line probe change in `dev/backend.sh`) ## Before requesting a review - [x] made a self-review - [x] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 0000) - [ ] updated test cases document n/a (dev tooling only) - [ ] added tests -> n/a (shell dev script) - [x] if any new env variables are added, added them to `.env` file -> none added Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
161 lines
4.3 KiB
Bash
161 lines
4.3 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
builder_backend_init() {
|
|
ROOT_DIR="${ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
|
|
SCHEMA_SNAPSHOT="${SCHEMA_SNAPSHOT:-$ROOT_DIR/apps/builder/e2e/schema/current.sql}"
|
|
COMPOSE_ENV=(
|
|
--env-file "$ROOT_DIR/apps/builder/.env"
|
|
)
|
|
if [ -f "$ROOT_DIR/apps/builder/.env.development" ]; then
|
|
COMPOSE_ENV+=(--env-file "$ROOT_DIR/apps/builder/.env.development")
|
|
fi
|
|
COMPOSE_FILES=(
|
|
-f "$ROOT_DIR/apps/builder/docker-compose.yaml"
|
|
-f "$COMPOSE_OVERRIDE_FILE"
|
|
)
|
|
}
|
|
|
|
builder_compose() {
|
|
docker compose "${COMPOSE_ENV[@]}" "${COMPOSE_FILES[@]}" "$@"
|
|
}
|
|
|
|
builder_backend_down() {
|
|
builder_compose down "$@"
|
|
}
|
|
|
|
builder_backend_start_db() {
|
|
builder_compose up -d db
|
|
}
|
|
|
|
builder_backend_start_postgrest() {
|
|
builder_compose up -d postgrest
|
|
}
|
|
|
|
builder_backend_wait_for_postgrest() {
|
|
local timeout_seconds="${1:-60}"
|
|
local port
|
|
port="$(builder_compose port postgrest 3000)"
|
|
local url="http://127.0.0.1:${port##*:}/UserProduct?select=userId&limit=0"
|
|
local timeout_at
|
|
timeout_at="$(($(date +%s) + timeout_seconds))"
|
|
|
|
until {
|
|
local status
|
|
status="$(curl -s -o /dev/null -w "%{http_code}" "$url" || true)"
|
|
[ "$status" -ge 200 ] && [ "$status" -lt 500 ]
|
|
}; do
|
|
if [ "$(date +%s)" -ge "$timeout_at" ]; then
|
|
echo "Timed out waiting for PostgREST" >&2
|
|
return 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
}
|
|
|
|
builder_backend_wait_for_db() {
|
|
local timeout_seconds="${1:-60}"
|
|
local timeout_at
|
|
timeout_at="$(($(date +%s) + timeout_seconds))"
|
|
|
|
# Connect over TCP, not the unix socket: the postgres image's entrypoint
|
|
# runs a temporary socket-only server (listen_addresses='') during init,
|
|
# which lets a socket probe pass while host clients (prisma migrate,
|
|
# postgrest) still get connection refused.
|
|
until builder_compose exec -T db \
|
|
sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -q -h 127.0.0.1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -c "SELECT 1"' \
|
|
>/dev/null 2>&1; do
|
|
if [ "$(date +%s)" -ge "$timeout_at" ]; then
|
|
echo "Timed out waiting for database" >&2
|
|
return 1
|
|
fi
|
|
sleep 0.2
|
|
done
|
|
}
|
|
|
|
builder_backend_schema_exists() {
|
|
[ "$(
|
|
builder_compose exec -T db \
|
|
sh -c 'psql -q -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT to_regclass('\''public.\"Project\"'\'') IS NOT NULL"'
|
|
)" = "t" ]
|
|
}
|
|
|
|
builder_backend_bootstrap_schema_snapshot() {
|
|
if [ ! -f "$SCHEMA_SNAPSHOT" ]; then
|
|
return 1
|
|
fi
|
|
|
|
builder_compose exec -T db \
|
|
sh -c 'psql -q -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1' \
|
|
<"$SCHEMA_SNAPSHOT" >/dev/null
|
|
}
|
|
|
|
builder_backend_migrate() {
|
|
pnpm --dir "$ROOT_DIR" --filter=./packages/prisma-client migrations migrate --dev --cwd ../../apps/builder
|
|
}
|
|
|
|
builder_backend_bootstrap() {
|
|
case "${1:-auto}" in
|
|
schema)
|
|
builder_backend_bootstrap_schema_snapshot
|
|
;;
|
|
migrations)
|
|
builder_backend_migrate
|
|
;;
|
|
auto)
|
|
if [ -f "$SCHEMA_SNAPSHOT" ]; then
|
|
builder_backend_bootstrap_schema_snapshot
|
|
echo "Bootstrapped database from $SCHEMA_SNAPSHOT"
|
|
else
|
|
echo "No schema snapshot found at $SCHEMA_SNAPSHOT; falling back to migrations"
|
|
builder_backend_migrate
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Unknown database bootstrap mode: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
builder_backend_bootstrap_if_empty() {
|
|
if builder_backend_schema_exists; then
|
|
echo "Database schema already exists; skipping bootstrap"
|
|
else
|
|
builder_backend_bootstrap auto
|
|
fi
|
|
}
|
|
|
|
builder_backend_write_schema_snapshot() {
|
|
mkdir -p "$(dirname "$SCHEMA_SNAPSHOT")"
|
|
builder_compose exec -T db \
|
|
sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --schema-only --no-owner --no-privileges' \
|
|
>"$SCHEMA_SNAPSHOT"
|
|
echo "Wrote $SCHEMA_SNAPSHOT"
|
|
}
|
|
|
|
builder_prisma_client_exists() {
|
|
[ -f "$ROOT_DIR/packages/prisma-client/src/__generated__/index.js" ]
|
|
}
|
|
|
|
builder_generate_prisma_client() {
|
|
case "${1:-auto}" in
|
|
true)
|
|
pnpm --dir "$ROOT_DIR" --filter=@webstudio-is/prisma-client generate
|
|
;;
|
|
false)
|
|
echo "Skipping Prisma client generation"
|
|
;;
|
|
auto)
|
|
if builder_prisma_client_exists; then
|
|
echo "Skipping Prisma client generation; generated client already exists"
|
|
else
|
|
pnpm --dir "$ROOT_DIR" --filter=@webstudio-is/prisma-client generate
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Unknown Prisma generation mode: $1" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|