#!/usr/bin/env bash # migrate-dev-db.sh — dump container system-db → brew localpg (PG18) on :5433 # # Prerequisites: # - LOCALPG_MODE=managed server once started (or this script can start pg via pg_ctl) # - container shuimu-context-pg-dev healthy # - brew postgresql@18 + pgvector installed # # Does NOT delete the docker volume (rollback: compose up system-db). set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" PG_BIN="${LOCALPG_BIN:-/home/linuxbrew/.linuxbrew/opt/postgresql@18/bin}" DATA_DIR="${DATA_DIR:-$ROOT/data/dev}" PGDATA="${PGDATA:-$DATA_DIR/pg}" PORT="${CONTEXT_DB_PORT:-5433}" DB_USER="${CONTEXT_DB_USER:-app}" DB_PASS="${CONTEXT_DB_PASSWORD:-dev_secret_db_pass}" DB_NAME="${CONTEXT_DB_NAME:-agent_system_dev}" CONTAINER="${DEV_PG_CONTAINER:-shuimu-context-pg-dev}" DUMP="${DUMP_PATH:-$ROOT/.tmp/dev-pg-migrate.dump}" info() { printf '\033[1;34m[migrate-dev-db]\033[0m %s\n' "$*"; } fail() { printf '\033[1;31m[migrate-dev-db]\033[0m %s\n' "$*"; exit 1; } [[ -x "$PG_BIN/pg_ctl" ]] || fail "pg_ctl not found at $PG_BIN (set LOCALPG_BIN)" docker inspect "$CONTAINER" >/dev/null 2>&1 || fail "container $CONTAINER not found" info "dumping $CONTAINER → $DUMP" docker exec "$CONTAINER" pg_dump -U "${SYSTEM_DB_USER:-app}" -d "${SYSTEM_DB_NAME:-agent_system_dev}" -Fc -f /tmp/dev.dump docker cp "$CONTAINER:/tmp/dev.dump" "$DUMP" # Free :5433 for localpg (do not remove volume — rollback via compose up). if docker inspect "$CONTAINER" --format '{{.State.Running}}' 2>/dev/null | grep -qi true; then info "stopping container $CONTAINER to free port $PORT (volume kept)" docker stop "$CONTAINER" >/dev/null fi SOCK="${PG_SOCKET_DIR:-${LOCALPG_SOCKET_DIR:-${XDG_RUNTIME_DIR:-/tmp}/as-pg-$PORT}}" mkdir -p "$(dirname "$DUMP")" "$SOCK" # Ensure local PG is up via pg_ctl (avoid agent-server migrations before restore). if ! "$PG_BIN/pg_isready" -h 127.0.0.1 -p "$PORT" >/dev/null 2>&1; then info "local PG not ready on :$PORT — initdb/pg_ctl bootstrap..." mkdir -p "$ROOT/.tmp/launch" if [[ ! -f "$PGDATA/PG_VERSION" ]]; then rm -rf "$PGDATA" mkdir -p "$PGDATA" "$PG_BIN/initdb" -D "$PGDATA" --locale=C --encoding=UTF8 --auth-local=trust --auth-host=scram-sha-256 -U postgres { echo "listen_addresses = '127.0.0.1'" echo "port = $PORT" echo "unix_socket_directories = '$SOCK'" } >>"$PGDATA/postgresql.conf" cat >"$PGDATA/pg_hba.conf" <<'HBA' # TYPE DATABASE USER ADDRESS METHOD local all all trust host all all 127.0.0.1/32 scram-sha-256 host all all ::1/128 scram-sha-256 HBA fi "$PG_BIN/pg_ctl" -D "$PGDATA" -l "$PGDATA/pg.log" -o "-p $PORT -k $SOCK" start for ((i = 0; i < 60; i++)); do "$PG_BIN/pg_isready" -h 127.0.0.1 -p "$PORT" >/dev/null 2>&1 && break sleep 1 done "$PG_BIN/pg_isready" -h 127.0.0.1 -p "$PORT" >/dev/null 2>&1 || fail "local PG still not listening after bootstrap" # Ensure app role exists before CREATE DATABASE OWNER. "$PG_BIN/psql" -h "$SOCK" -p "$PORT" -U postgres -v ON_ERROR_STOP=1 < pg_backend_pid(); DROP DATABASE IF EXISTS $DB_NAME; CREATE DATABASE $DB_NAME OWNER $DB_USER; \\c $DB_NAME CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS pg_trgm; GRANT ALL ON SCHEMA public TO $DB_USER; SQL info "restoring dump into local $DB_NAME" "$PG_BIN/pg_restore" -h "$SOCK" -p "$PORT" -U postgres -d "$DB_NAME" --no-owner --role="$DB_USER" --verbose "$DUMP" \ || info "pg_restore exited non-zero (often OK with extension noise); verify goose version next" info "goose version on local:" export PGPASSWORD="$DB_PASS" "$PG_BIN/psql" -h 127.0.0.1 -p "$PORT" -U "$DB_USER" -d "$DB_NAME" -c \ "SELECT COALESCE(MAX(version_id),0) AS goose_version FROM goose_db_version;" || true info "done. Next: docker compose -f docker-compose.dev.yml -p agent-dev stop system-db" info "Rollback: docker compose -f docker-compose.dev.yml -p agent-dev up -d system-db"