oclaw/runtime/operations/scripts/cutover_sqlite_to_postgresql.sh
oliver d14e9d3596 feat(persistence): PostgreSQL assistant store, chat persist fixes, gateway scripts
- Add SQLAlchemy Core repos, pg adapter/compat, assistant_store factory, Alembic bootstrap and migration/cutover scripts.

- Harden chat_message writes (NUL scrub for PG), turn_uuid on attempt failure, WS turn_runner fallbacks and gateway executed_turn_uuid init.

- start_gateway: log paths, PS7 stderr handling via cmd, background stdout/stderr redirect; runtime assistant_runtime_log_dir export.

- Ops: clear_all_chat_sessions with PG-only --postgresql and env-gated wipe; clear_postgres_chat_sessions.ps1.

- Tests: SA repos, pg compat, persist fallback, smoke env isolation; CI and docs touch-ups.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:33:50 +08:00

67 lines
2.4 KiB
Bash

#!/usr/bin/env bash
# Backup SQLite assistant DB, optional dry-run, then import into PostgreSQL (empty schema recommended).
#
# Loads _local/system.env via the Python migrator (--load-system-env) so AIA_ASSISTANT_DATABASE_URL /
# AIA_ASSISTANT_DB_PATH match the gateway. Override URL with extra args, e.g. --pg-url 'postgresql+...'
#
# Environment:
# SQLITE_PATH optional; default: db_path() after load_system_env
# BACKUP_DIR optional; default: <repo>/data/pg_cutover_backups
# NO_BACKUP=1 skip file copy
# SKIP_DRY_RUN=1 skip dry-run pass
# DRY_RUN_ONLY=1 only dry-run
#
# Example:
# chmod +x runtime/operations/scripts/cutover_sqlite_to_postgresql.sh
# ./runtime/operations/scripts/cutover_sqlite_to_postgresql.sh
# ./runtime/operations/scripts/cutover_sqlite_to_postgresql.sh --pg-url 'postgresql+psycopg://u:p@h:5432/oclaw'
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
cd "$ROOT"
export PYTHONPATH="${ROOT}${PYTHONPATH:+:${PYTHONPATH}}"
MIG="${ROOT}/runtime/operations/scripts/migrate_assistant_sqlite_to_postgresql.py"
resolve_sqlite() {
if [[ -n "${SQLITE_PATH:-}" ]]; then
printf '%s' "$SQLITE_PATH"
return
fi
python -c "import os,sys; sys.path.insert(0, r'''${ROOT}'''); os.chdir(r'''${ROOT}'''); from svc.config.bootstrap_env import load_system_env; load_system_env(force=True); from svc.config.paths import db_path; print(db_path(), end='')"
}
SQLITE="$(resolve_sqlite)"
if [[ ! -f "$SQLITE" ]]; then
echo "SQLite file not found: $SQLITE" >&2
exit 1
fi
BACKUP_DIR="${BACKUP_DIR:-${ROOT}/data/pg_cutover_backups}"
mkdir -p "$BACKUP_DIR"
STAMP="$(date +%Y%m%d_%H%M%S)"
BASE="$(basename "$SQLITE" .sqlite)"
BAK="${BACKUP_DIR}/${BASE}_pre_pg_${STAMP}.sqlite"
if [[ "${NO_BACKUP:-}" != "1" ]]; then
cp -f "$SQLITE" "$BAK"
echo "Backup written: $BAK"
else
echo "NO_BACKUP=1: skipping SQLite file backup." >&2
fi
COMMON=(--load-system-env --sqlite "$SQLITE")
if [[ "${SKIP_DRY_RUN:-}" != "1" ]]; then
echo "=== Dry-run (row counts, no PG writes) ===" >&2
python "$MIG" "${COMMON[@]}" "$@" --dry-run
fi
if [[ "${DRY_RUN_ONLY:-}" == "1" ]]; then
echo "DRY_RUN_ONLY=1: skipping live import." >&2
exit 0
fi
echo "=== Live import into PostgreSQL ===" >&2
python "$MIG" "${COMMON[@]}" "$@"
echo "" >&2
echo "Done. Set AIA_ASSISTANT_DB_BACKEND=postgresql and AIA_ASSISTANT_DATABASE_URL, then restart the gateway." >&2