Профиль Windows Terminal вызывает .venv\Scripts\python.exe с PYTHONUTF8=1 в окружении профиля, поэтому промежуточный PowerShell и его настройка консоли больше не нужны.
pg-stand-sync
Перенос базы PostgreSQL со стенда на локальный сервер: pg_dump -Fc → пересоздание локальной БД → pg_restore -j.
Ядро — stdlib Python 3.10+; TUI-пульт требует textual (см. requirements.txt), без него
работает прежний текстовый режим. Утилиты берутся из поставки pgAdmin 4.
Работает в двух режимах: пульт (запуск без аргументов) и команды CLI.
Быстрый старт
copy config.example.json config.json
Заполнить source (стенд) и target (локальная PG), затем:
.venv\Scripts\python.exe pg_stand_sync.py
Окружение создаётся один раз:
python -m venv .venv; .venv\Scripts\python.exe -m pip install -r requirements.txt
В Windows Terminal заведён профиль Tools → pg-stand-sync: он вызывает
.venv\Scripts\python.exe pg_stand_sync.py напрямую, с PYTHONUTF8=1 в окружении профиля.
Откроется пульт: оба списка сразу на экране, отдельного шага «выбор действия» нет.
┌ pg-stand-sync ─────────────────────── ◆ DRY-RUN ─┐
│ стенд postgres@postgresql.lan → локально :5432 │
├──────────────────────────┬───────────────────────┤
│ Базы стенда показано 3/41│ Приёмник · localhost │
│ фильтр▸ zpa▌ │ режим DROP+CREATE │
│ ● zpas 1284 MB │ цель как на стенде │
│ ○ zpas_arch 312 MB ├──────────────────────┤
│ ○ zpas_test 88 MB │ Дампы показано 5/12 │
│ отмечено 1 │ ▸ zpas-…0313 1.2 ГБ │
├──────────────┬────────────┴──────────────────────┤
│ план │ журнал │
│ ✓ дамп 12.4c │ [03:14:07] дамп zpas: "…pg_dump…" │
│ ▸ DROP ⣾ │ pg_dump: сохранение "public…" │
├──────────────┴───────────────────────────────────┤
│ ⣾ sync zpas · шаг 3/6 · 00:42 ████████░░░ ^X │
└──────────────────────────────────────────────────┘
Фильтр сужает список на каждом набранном символе — Enter для этого не нужен.
| Клавиша | Что делает |
|---|---|
| набор букв | фильтр панели под фокусом; несколько слов — И |
↑ ↓ PgUp PgDn |
курсор в списке, фокус остаётся в поле фильтра |
Tab |
следующая панель |
Enter |
базы → окно параметров дампа с галочкой «поднять локально», дампы → Restore |
Space |
отметить базу (когда фокус в списке) |
Ctrl+D |
то же окно с выключённым автоподнятием — снять только дамп |
Ctrl+A / Ctrl+U |
отметить показанные / снять все отметки |
Esc |
очистить фильтр, затем отметки |
F4 |
журнал во весь экран и обратно (Esc — тоже назад) |
F5 |
перечитать оба списка |
Ctrl+X |
прервать операцию (это kill процессов, а не откат) |
Ctrl+L / Ctrl+S |
очистить журнал / сохранить его в dumps/session-<stamp>.log |
F1 / F2 / F3 |
помощь / тема / показывать ли вывод утилит |
Ctrl+C |
выход |
В окне операции: ↑ ↓ (и Tab) — переход по полям, Space — переключить галочку,
Ctrl+Enter — начать, Esc — отмена.
Раскладка клавиатуры значения не имеет: каждый Ctrl-хоткей продублирован кириллическим
двойником по ЙЦУКЕН (Ctrl+В = Ctrl+D, Ctrl+Ч = Ctrl+X и так далее).
На старте операции пульт сам разворачивает журнал на весь экран и сам сворачивает его обратно,
когда всё закончилось; если развернуть журнал руками (F4), пульт больше не трогает раскладку.
Раскладка адаптивная: уже 100 колонок — панели встают друг под друга, ниже 32 строк журнал
занимает долю экрана вместо фиксированных 20 строк. Списки, журнал и план листаются стрелками
и PgUp/PgDn.
Отметки переживают смену фильтра и не сужаются им: в операцию уходят все отмеченные базы,
сколько бы их ни показывала панель. Вывод pg_dump/pg_restore идёт в журнал построчно,
пока утилита работает.
Sync нескольких баз сразу возможен, только когда target.database = null (каждая база
льётся в одноимённую локальную). С заданным target.database пульт откажет: все отмеченные
базы легли бы по очереди в одну и ту же локальную базу.
Текстовый режим
Пульт не запускается, если не установлен textual, вывод не в терминал, задан --no-tui
или переменная PG_STAND_SYNC_NO_TUI. Тогда открывается прежнее меню, а список выглядит так:
| Ввод | Что делает |
|---|---|
| текст | фильтрует список по подстроке (регистр не важен), применяется по Enter |
| номер | выбирает пункт; в Dump можно 2,5,7 — несколько баз |
| пусто | сбрасывает фильтр |
q |
назад в меню |
Флаг --tui — наоборот: при отсутствии textual он завершается ошибкой, а не откатывается
молча в текст.
config.json в .gitignore — пароли в репозиторий не попадают.
CLI
python pg_stand_sync.py list # базы на стенде
python pg_stand_sync.py dumps # локальные дампы
python pg_stand_sync.py dump zpas # только дамп
python pg_stand_sync.py sync zpas --target-db zpas_l # дамп + restore
python pg_stand_sync.py restore dumps/zpas-20260820-031332.dump
Имя базы (или файла) можно не указывать — тогда откроется тот же список с фильтрацией.
| Команда / ключ | Что делает |
|---|---|
sync [DB…] |
dump со стенда + restore локально |
dump [DB…] |
только снять дампы со стенда (можно несколько баз) |
restore [FILE] |
залить готовый .dump локально, без обращения к стенду |
list / dumps |
базы стенда / файлы дампов |
menu |
интерактивное меню (то же, что запуск без команды) |
--target-db NAME |
имя локальной базы (по умолчанию как на стенде) |
--no-recreate |
не делать DROP/CREATE DATABASE, накатить поверх (pg_restore --clean --if-exists) |
-c, --config PATH |
другой конфиг (несколько стендов — несколько json) |
--dry-run |
напечатать команды, ничего не выполнять |
--no-tui |
меню текстом, без Textual (то же делает PG_STAND_SYNC_NO_TUI=1) |
--tui |
только пульт: без textual завершиться ошибкой, а не откатываться в текст |
Имя базы или файла у CLI-команд без аргумента по-прежнему спрашивается текстовым списком,
даже когда textual установлен: подсовывать полноэкранный пульт в середину sync DB значит
ломать pipe-сценарии.
Конфиг
| Поле | Значение по умолчанию | Смысл |
|---|---|---|
pg_bin_dir |
из PATH |
каталог с pg_dump.exe, pg_restore.exe, psql.exe |
dump_dir |
dumps |
куда складывать дампы (относительный путь — от каталога скрипта) |
keep_dumps |
5 |
сколько последних дампов хранить, 0 — не чистить |
source.maintenance_database |
postgres |
база, к которой подключаться для чтения списка баз |
source.exclude_databases |
[] |
массив строк: базы, которые не показывать в списках |
source.password_env |
— | имя переменной окружения с паролем вместо password в файле |
target.database |
null |
имя локальной базы; null — как на стенде |
target.recreate |
true |
DROP + CREATE локальной базы перед восстановлением |
dump.schemas / exclude_* |
[] |
ограничение состава: схемы, таблицы, данные таблиц |
restore.jobs |
4 |
параллельные воркеры pg_restore |
restore.exit_on_error |
false |
падать на первой ошибке восстановления |
post_restore_sql |
[] |
список SQL, выполняемых в целевой базе после восстановления |
"exclude_databases": ["_old", "keycloak", "*_tmp"]
Строка без спецсимволов — подстрока (регистр не важен), со *, ? или [ — маска.
Фильтр действует только на списки: базу, названную прямо (sync zpas_old), он не прячет.
Пароль ищется в три шага: password в конфиге → переменная окружения из password_env →
запрос с терминала (один раз за запуск, ввод скрыт). В неинтерактивном запуске третьего шага
нет — либо password_env, либо pgpass.conf. Пароль всегда уходит в дочерний процесс через
PGPASSWORD, в командной строке не появляется; если пароля нет вовсе, утилиты вызываются с
--no-password, чтобы скрипт не подвисал на приглашении.
$env:STAND_PGPASSWORD = '...'
python pg_stand_sync.py sync zpas
Как это работает
config.json
│
├─ pg_dump -Fc ──► dumps/<db>-<timestamp>.dump (стенд, только чтение)
│
├─ psql: pg_terminate_backend → DROP DATABASE → CREATE DATABASE (локально)
│
├─ pg_restore --jobs N --no-owner --no-privileges
│
└─ post_restore_sql: анонимизация, правка настроек, GRANT'ы
Обезличивание данных стенда, если оно нужно, делается через post_restore_sql — уже в локальной
базе, чтобы стенд оставался нетронутым.
Тесты
.venv/Scripts/python.exe -m pytest tests_tui.py -q
tests_tui.py гоняет пульт headless (App.run_test() + Pilot): настоящий терминал не
открывается, список баз подменяется фикстурой, к postgresql.lan никто не ходит. Там же
проверяется паритет текстового режима: состав argv, текст StepError, стриминг вывода,
отмена и то, что пароль не попадает в командную строку.