Files
momentum-system/deploy/docker/migrate_db.sh
T
momentum devandClaude Opus 4.8 7427d14301 TrueNAS migration: dataset paths, 5433 coexistence port, dump/restore script
truenas-compose.yml now uses /mnt/volume/Apps/momentum and publishes the
new postgres on 5433 so it can coexist with the existing instance during
migration. deploy/docker/migrate_db.sh: dump / restore / verify steps
(pg_dump -Fc read-only source, parallel pg_restore, ANALYZE, row-count
diff) with env-overridable URLs and PG18 client autodetection —
connectivity smoke-tested against the live DB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 14:21:21 -04:00

60 lines
2.5 KiB
Bash

#!/usr/bin/env bash
# Migrate the momentum Postgres from the existing instance to the TrueNAS
# stack's new one, with verification. Safe to practice: the SOURCE is only
# ever read (pg_dump), never written.
#
# ./deploy/docker/migrate_db.sh dump # 1. dump source -> momentum.dump
# ./deploy/docker/migrate_db.sh restore # 2. restore dump -> target
# ./deploy/docker/migrate_db.sh verify # 3. row-count diff source vs target
#
# Override via env: SRC_URL, DST_URL, DUMP_FILE, PG_BIN.
# Requires postgres 18 client tools (server is 18.4) — defaults to the local
# PG18 install at /home/philip/pg/bin when present.
set -euo pipefail
PG_BIN="${PG_BIN:-$([ -x /home/philip/pg/bin/pg_dump ] && echo /home/philip/pg/bin || echo "")}"
DUMP_FILE="${DUMP_FILE:-./momentum.dump}"
SRC_URL="${SRC_URL:-postgresql://admin:CHANGE_ME@192.168.1.5:5432/momentum}"
# target = the TrueNAS compose postgres, published on 5433 (see truenas-compose.yml)
DST_URL="${DST_URL:-postgresql://momentum:CHANGE_ME@192.168.1.5:5433/momentum}"
pgdump="${PG_BIN:+$PG_BIN/}pg_dump"
pgrestore="${PG_BIN:+$PG_BIN/}pg_restore"
psql="${PG_BIN:+$PG_BIN/}psql"
# The big tables that matter: events (trading history), minute_bars/daily_bars
# (the year of backtest truth), plus every small state table. Nothing excluded
# by default; uncomment to skip pure observability bulk and shrink the dump:
EXCLUDES=()
# EXCLUDES+=(--exclude-table-data=scanner_snapshots --exclude-table-data=raw_fetch_attempts)
case "${1:-}" in
dump)
echo ">> dumping $SRC_URL -> $DUMP_FILE (custom format, compressed)"
time "$pgdump" -Fc --no-owner --no-privileges "${EXCLUDES[@]}" \
-d "$SRC_URL" -f "$DUMP_FILE"
ls -lh "$DUMP_FILE"
;;
restore)
echo ">> restoring $DUMP_FILE -> $DST_URL (4 parallel jobs)"
time "$pgrestore" --no-owner --no-privileges --clean --if-exists -j 4 \
-d "$DST_URL" "$DUMP_FILE"
echo ">> ANALYZE for fresh planner stats"
"$psql" -d "$DST_URL" -c "ANALYZE;"
;;
verify)
echo ">> row counts, source vs target (should match):"
q="SELECT relname, n_live_tup FROM pg_stat_user_tables
WHERE schemaname='public' ORDER BY n_live_tup DESC LIMIT 12"
paste <("$psql" -d "$SRC_URL" -At -F' ' -c "$q") \
<("$psql" -d "$DST_URL" -At -F' ' -c "$q") \
| awk '{printf " %-22s src=%-10s | %-22s dst=%s\n", $1, $2, $3, $4}'
echo ">> newest event on each side:"
for u in "$SRC_URL" "$DST_URL"; do
"$psql" -d "$u" -At -c "SELECT max(timestamp) FROM events" || true
done
;;
*)
echo "usage: $0 dump|restore|verify"; exit 1 ;;
esac