Files
pg-stand-sync/README.md
T
mikhail 975934373b Отсев баз через source.exclude_databases
list_databases() прячит базы по массиву строк: подстрока без регистра
или маска со * ? [. Фильтр — единственное место рождения списка, поэтому
одинаков для меню, пульта и list; явное имя в CLI не трогает.
2026-08-20 06:02:55 +03:00

199 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | выход |
В окне операции: `↑` `↓``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
```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.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, выполняемых в целевой базе после восстановления |
```json
"exclude_databases": ["_old", "keycloak", "*_tmp"]
```
Строка без спецсимволов — подстрока (регистр не важен), со `*`, `?` или `[` — маска.
Фильтр действует только на списки: базу, названную прямо (`sync zpas_old`), он не прячет.
Пароль ищется в три шага: `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`, стриминг вывода,
отмена и то, что пароль не попадает в командную строку.