mikhail e8fd19714a Компактный диалог операции и кнопки, которые не уезжают
Пары Switch + Label заменены на Checkbox с встроенной подписью: строка
вместо трёх. Поля стали однострочными, чекбоксы дампа встали в ряд,
заголовок и имя базы слились в одну строку — диалог ужался с почти
целого экрана до 17-18 строк на любом размере консоли.

Ряд кнопок пропадал потому, что .dialog с height: auto складывал высоту
детей и упирался в max-height, обрезая нижнюю строку. Теперь у диалога
операции своя высота, а форма забирает остаток через height: 1fr и
скроллится сама. Диалог restore приведён к тому же виду.
2026-08-20 05:36:01 +03:00

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
.\run.ps1 -Setup

Заполнить source (стенд) и target (локальная PG), затем:

.\run.ps1

run.ps1 — обёртка запуска: берёт интерпретатор из .venv (иначе системный), переключает консоль в UTF-8 и пробрасывает остальные аргументы в скрипт (.\run.ps1 list, .\run.ps1 --dry-run sync zpas). -Setup создаёт окружение и ставит зависимости, -Tests прогоняет тесты. Без обёртки всё то же работает через python pg_stand_sync.py.

Откроется пульт: оба списка сразу на экране, отдельного шага «выбор действия» нет.

┌ 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 выход

Раскладка клавиатуры значения не имеет: каждый 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.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, выполняемых в целевой базе после восстановления

Пароль ищется в три шага: 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, стриминг вывода, отмена и то, что пароль не попадает в командную строку.

S
Description
Перенос базы PostgreSQL со стенда на локальную PG: pg_dump -Fc + pg_restore, конфиг в JSON
Readme 303 KiB
Languages
Python 100%