#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PYTHON_BIN="${PYTHON_BIN:-}" LIVE_SMOKE_USE_DOCKER="${LIVE_SMOKE_USE_DOCKER:-auto}" COMPOSE_FILE_PATH="${COMPOSE_FILE:-docker-compose.yml}" run_docker_smoke() { service_name="$1" cd "$ROOT" echo "== GeoIntel live migration smoke ==" echo "Using Docker Compose service: ${service_name}." docker compose -f "$COMPOSE_FILE_PATH" exec -T "$service_name" sh <<'CONTAINER_SCRIPT' set -eu cd /app echo "Database URL is read from backend container settings / DATABASE_URL." python - <<'PY' from sqlalchemy import text from app.db.session import get_engine with get_engine().connect() as connection: connection.execute(text("SELECT 1")) print("Database connection: ok") PY python -m alembic upgrade head python - <<'PY' from sqlalchemy import text from app.db.session import get_engine required_objects = [ "public.projects", "public.areas", "public.datasets", "public.jobs", "public.vector_features", "public.quality_checks", "public.metrics", "public.analysis_runs", "public.detections", "public.segmentations", "public.ix_vector_features_geometry", "public.ix_detections_geometry", "public.ix_segmentations_geometry", ] with get_engine().connect() as connection: postgis_version = connection.execute(text("SELECT PostGIS_Version()")).scalar() print(f"PostGIS version: {postgis_version}") missing = [ object_name for object_name in required_objects if connection.execute(text("SELECT to_regclass(:object_name)"), {"object_name": object_name}).scalar() is None ] if missing: raise SystemExit(f"Missing expected migrated schema objects: {', '.join(missing)}") print("Required runtime schema objects: ok") PY HEAD_COUNT="$(python -m alembic heads | grep -c 'head')" if [ "$HEAD_COUNT" -ne 1 ]; then echo "Expected exactly one Alembic head, found $HEAD_COUNT" >&2 python -m alembic heads >&2 exit 1 fi python -m alembic heads echo "== Live migration smoke passed ==" CONTAINER_SCRIPT } if [ "$LIVE_SMOKE_USE_DOCKER" != "0" ] && command -v docker >/dev/null 2>&1 && [ -f "$ROOT/$COMPOSE_FILE_PATH" ]; then cd "$ROOT" for service_name in geointel backend; do if docker compose -f "$COMPOSE_FILE_PATH" ps -q "$service_name" >/dev/null 2>&1 \ && [ -n "$(docker compose -f "$COMPOSE_FILE_PATH" ps -q "$service_name" 2>/dev/null)" ]; then if [ "$LIVE_SMOKE_USE_DOCKER" = "1" ] \ || docker compose -f "$COMPOSE_FILE_PATH" ps --status running "$service_name" | grep -q "$service_name"; then run_docker_smoke "$service_name" exit 0 fi fi done fi if [ -z "$PYTHON_BIN" ]; then for candidate in python3 python.exe python; do if command -v "$candidate" >/dev/null 2>&1 && "$candidate" -c "import sys" >/dev/null 2>&1; then PYTHON_BIN="$candidate" break fi done fi if [ -z "$PYTHON_BIN" ]; then echo "No usable Python interpreter found. Set PYTHON_BIN=/path/to/python and retry." >&2 exit 1 fi cd "$ROOT/backend" echo "== GeoIntel live migration smoke ==" echo "Using Python: $PYTHON_BIN" echo "Database URL is read from backend settings / DATABASE_URL." "$PYTHON_BIN" - <<'PY' from sqlalchemy import text from app.db.session import get_engine with get_engine().connect() as connection: connection.execute(text("SELECT 1")) print("Database connection: ok") PY "$PYTHON_BIN" -m alembic upgrade head "$PYTHON_BIN" - <<'PY' from sqlalchemy import text from app.db.session import get_engine required_objects = [ "public.projects", "public.areas", "public.datasets", "public.jobs", "public.vector_features", "public.quality_checks", "public.metrics", "public.analysis_runs", "public.detections", "public.segmentations", "public.ix_vector_features_geometry", "public.ix_detections_geometry", "public.ix_segmentations_geometry", ] with get_engine().connect() as connection: postgis_version = connection.execute(text("SELECT PostGIS_Version()")).scalar() print(f"PostGIS version: {postgis_version}") missing = [ object_name for object_name in required_objects if connection.execute(text("SELECT to_regclass(:object_name)"), {"object_name": object_name}).scalar() is None ] if missing: raise SystemExit(f"Missing expected migrated schema objects: {', '.join(missing)}") print("Required runtime schema objects: ok") PY HEAD_COUNT="$("$PYTHON_BIN" -m alembic heads | grep -c 'head')" if [ "$HEAD_COUNT" -ne 1 ]; then echo "Expected exactly one Alembic head, found $HEAD_COUNT" >&2 "$PYTHON_BIN" -m alembic heads >&2 exit 1 fi "$PYTHON_BIN" -m alembic heads echo "== Live migration smoke passed =="