9fdb02c72a
Списки баз стенда и локальных дампов на одном экране, фильтр сужает их на каждом набранном символе. Enter по базе открывает окно параметров дампа с галочкой автоподнятия: снята — только dump, поднята — dump плюс restore. Правки полей уезжают в копию конфига, config.json не трогается. Ядро получило четыре хука для UI (лог, границы шагов, пароль, отмена), run() читает вывод утилит построчно через Popen, поэтому журнал наполняется во время работы pg_dump. Текстовый режим, CLI и --dry-run ведут себя как раньше; без textual или вне tty пульт не запускается. Журнал разворачивается на весь экран по F4 и сам на старте операции. Раскладка адаптивная: narrow ниже 100 колонок, short ниже 32 строк. Ctrl-хоткеи продублированы кириллицей — русская раскладка их не глушит. Запуск через run.ps1 (venv, UTF-8-консоль, -Setup и -Tests). Проверка — tests_tui.py: 46 тестов, TUI гоняется headless через Pilot.
188 lines
13 KiB
Markdown
188 lines
13 KiB
Markdown
# pg-stand-sync
|
||
|
||
Перенос базы PostgreSQL со стенда на локальный сервер: `pg_dump -Fc` → пересоздание локальной БД → `pg_restore -j`.
|
||
Ядро — stdlib Python 3.10+; TUI-пульт требует `textual` (см. `requirements.txt`), без него
|
||
работает прежний текстовый режим. Утилиты берутся из поставки pgAdmin 4.
|
||
|
||
Работает в двух режимах: пульт (запуск без аргументов) и команды CLI.
|
||
|
||
## Быстрый старт
|
||
|
||
```bash
|
||
copy config.example.json config.json
|
||
```
|
||
|
||
```powershell
|
||
.\run.ps1 -Setup
|
||
```
|
||
|
||
Заполнить `source` (стенд) и `target` (локальная PG), затем:
|
||
|
||
```powershell
|
||
.\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
|
||
|
||
```bash
|
||
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`, чтобы скрипт не подвисал на приглашении.
|
||
|
||
```powershell
|
||
$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` — уже в локальной
|
||
базе, чтобы стенд оставался нетронутым.
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
.venv/Scripts/python.exe -m pytest tests_tui.py -q
|
||
```
|
||
|
||
`tests_tui.py` гоняет пульт headless (`App.run_test()` + `Pilot`): настоящий терминал не
|
||
открывается, список баз подменяется фикстурой, к `postgresql.lan` никто не ходит. Там же
|
||
проверяется паритет текстового режима: состав `argv`, текст `StepError`, стриминг вывода,
|
||
отмена и то, что пароль не попадает в командную строку.
|