9bb89ee62f
- schema.sql: all user_id FKs now ON DELETE SET NULL (was RESTRICT) - migrate_user_fk_set_null.py: drop+readd constraints on existing DBs - entrypoint.sh: runs migration automatically on startup (non-fatal) - db.py: rollback moved before FK check (cleanup) Deleting a user now nullifies their references in audit_log, project_context, context_files, change_requests, reviews instead of blocking with a 409.
77 lines
2.5 KiB
Bash
77 lines
2.5 KiB
Bash
#!/bin/bash
|
|
set -e
|
|
|
|
wait_for_postgres() {
|
|
if [ -z "$DATABASE_URL" ]; then
|
|
return 0
|
|
fi
|
|
echo "ctxd: waiting for PostgreSQL (DATABASE_URL is set)..."
|
|
python3 <<'PY'
|
|
import os, sys, time
|
|
|
|
url = os.environ.get("DATABASE_URL", "")
|
|
max_wait = int(os.environ.get("CTXD_PG_WAIT_SECONDS", "120"))
|
|
interval = int(os.environ.get("CTXD_PG_WAIT_INTERVAL", "2"))
|
|
deadline = time.time() + max_wait
|
|
last_err = ""
|
|
attempt = 0
|
|
while time.time() < deadline:
|
|
attempt += 1
|
|
try:
|
|
import psycopg
|
|
conn = psycopg.connect(url, connect_timeout=5)
|
|
conn.close()
|
|
print("ctxd: PostgreSQL is ready")
|
|
sys.exit(0)
|
|
except Exception as e:
|
|
last_err = str(e)
|
|
if attempt == 1 or attempt % 10 == 0:
|
|
print(f"ctxd: postgres not ready (attempt {attempt}): {last_err}", file=sys.stderr)
|
|
time.sleep(interval)
|
|
|
|
print("ctxd: FATAL: PostgreSQL not reachable within wait window.", file=sys.stderr)
|
|
print(f"ctxd: last error: {last_err}", file=sys.stderr)
|
|
print("ctxd:", file=sys.stderr)
|
|
print("ctxd: Fix: start the full stack (postgres + ctxd), e.g.", file=sys.stderr)
|
|
print("ctxd: cd app && docker compose up -d postgres ctxd", file=sys.stderr)
|
|
print("ctxd:", file=sys.stderr)
|
|
print("ctxd: Avoid: docker compose up -d --no-deps ctxd (skips postgres)", file=sys.stderr)
|
|
print("ctxd: If postgres is on another host, fix DATABASE_URL / network.", file=sys.stderr)
|
|
sys.exit(1)
|
|
PY
|
|
}
|
|
|
|
wait_for_postgres
|
|
|
|
# Initialize if needed
|
|
if [ -n "$DATABASE_URL" ]; then
|
|
# PostgreSQL mode — check if schema exists
|
|
NEEDS_INIT=$(python3 -c "
|
|
import psycopg, os, sys
|
|
try:
|
|
conn = psycopg.connect(os.environ['DATABASE_URL'])
|
|
cur = conn.execute(\"SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname='public' AND tablename='users')\")
|
|
if not cur.fetchone()[0]:
|
|
print('yes')
|
|
else:
|
|
print('no')
|
|
except Exception:
|
|
print('yes')
|
|
" 2>/dev/null || echo "yes")
|
|
if [ "$NEEDS_INIT" = "yes" ]; then
|
|
echo "ctxd: initializing PostgreSQL database"
|
|
python3 -m ctxd init --home "$CTXD_HOME"
|
|
else
|
|
echo "ctxd: running pending migrations"
|
|
python3 -m ctxd.migrate_user_fk_set_null 2>&1 || echo "ctxd: migration warning (non-fatal)"
|
|
fi
|
|
else
|
|
# SQLite mode — check for db file
|
|
if [ ! -f "$CTXD_HOME/ctxd.db" ]; then
|
|
echo "ctxd: initializing database at $CTXD_HOME"
|
|
python3 -m ctxd init --home "$CTXD_HOME"
|
|
fi
|
|
fi
|
|
|
|
echo "ctxd: starting daemon on ${CTXD_HOST:-0.0.0.0}:${CTXD_PORT:-9091}"
|
|
exec python3 -m ctxd |