# История getpgdata — 2026-08-18 ## Суть проекта `getpgdata` — отдельное приложение для доступа к PostgreSQL (managed k8s, internal-only). Разворачивается на платформе Nubes как **NodeJS managed service** (`nodejsk8s`). Репозиторий: `https://gitea.services.ngcloud.ru/Nail/getpgdata.git` (ветка `master`). Локальный путь: `/home/naeel/ipwhitelist-app/getpgdata`. ## 1. Создание (Flask-версия, ошибочная) Первоначально приложение было собрано как **Flask (Python)** по howto `~/nubes/howto-flask-nubes.md` (структура `site/app.py` + `requirements.txt`). Запушено коммитом `5c5e11c`. **Функции Flask-версии:** главная со списком таблиц, просмотр таблицы, SQL-консоль (read-only), `/healthz`. ## 2. Ошибка пользователя и замена на Node.js Пользователь указал, что перепутал стек — нужно **Node.js**, а не Flask. **Решение:** Flask-файлы (`site/`, `requirements.txt`) удалены, создана Node.js структура по образцу рабочего `ipwhitelist-app`: ``` getpgdata/ ├── package.json # version 1.0.x; main server.js; start: node server.js ├── server.js # точка входа: Express + pg Pool ├── public/style.css └── views/ ├── index.ejs # главная: подключение + список таблиц ├── table.ejs # просмотр таблицы с пагинацией ├── query.ejs # SQL-консоль (read-only, только SELECT) └── error.ejs ``` Коммит замены: `b061dcd`. **Проверено:** `node -c server.js`, `npm install` (90 пакетов), реальный запуск сервера: - `GET /` → 200 (рендер страницы) - `GET /healthz` при недоступной БД → 503 (degraded, приложение не падает) - `GET /table` без имени → 302 redirect - `POST /query` не-SELECT → блокируется (read-only) - статика `/public/style.css` → 200 ## 3. Настройки подключения к БД - Хост: `postgresqlk8s-master.60bdf3e3-5087-41ff-b760-fe6ea544a80e.svc.cluster.local` - Порт: `5432` - БД: `ipwhitelist` (коммит `102689d` — дефолт изменён с `postgres` на `ipwhitelist`) - Пользователи: `super` (полный доступ) и `contracts` — оба с паролями из jsonEnv - `DB_SSLMODE`: `disable` Пароли в коде НЕ хранятся (читаются через `process.env.DB_PASS`). Готовые jsonEnv-блоки на обоих пользователей добавлены в `README.md` (коммит `a890bb9`). ## 4. Проблема деплоя: под `nodejsk8s` не стартует **Симптом (Nubes UI):** > Приложение не запустилось. Производится полный откат установки. > Error: jlib.k8s [correctReplicaActive] | ERROR | Под(ы) не работают: 'nodejsk8s' (Deployment). **Диагноз (сравнение с работающим `whitelist`):** | Параметр | `whitelist` (работает) | `getpgdata` (падал) | |---|---|---| | Точка входа | `server.js`, `main` в package.json | `server.js` ✓ | | `npm start` | `node server.js` | `node server.js` ✓ | | **Дефолтный порт** | **3000** | **5000** ✗ | | Liveness | `/healthz` | `/healthz` ✓ | | Старт без доступной БД | стартует | стартует ✓ | **Гипотеза:** дефолтный порт `5000` (перенесён из Flask-версии) не совпадает с портом `3000`, который ожидает платформа Nubes для NodeJS managed service. Health-проба не находила сервис → под не Ready → «не работает nodejsk8s» → откат. **Исправление:** дефолтный `PORT` в `server.js` изменён с `5000` на `3000`; в `README.md` jsonEnv-блоки обновлены (`PORT: 3000`); `package.json` version → `1.0.1`. **Проверено:** сервер запущен без env `PORT` → слушает `0.0.0.0:3000`, `/` → 200, `/healthz` → 503 (degraded). **⚠️ НЕ проверено:** доступ к кластеру Nubes с этой машины отсутствует (`iot-naeel` и `naeel-test-3` требуют OIDC-аутентификацию, managed-сервисы в них не видны). Поэтому точную причину «почему под упал» по логам пода подтвердить нельзя — требуется лог пода из Nubes UI. Если деплой после фикса порта снова не поднимется — смотреть логи пода (`nodejsk8s` Deployment) в UI, а не только полагаться на гипотезу о порте. ## 5. Прочее - `package-lock.json` закоммичен (зависимости: express, ejs, pg). - `node_modules/` в `.gitignore`. - Версия в `package.json` повышается при каждой правке кода (правило проекта). ## 6. Минимальный вывод на экран (фикс падения пода) Несмотря на фикс порта (п.4), деплой снова упал — под `nodejsk8s` не поднялся. **Требование пользователя:** «сделай код минимальным. Сам код не удаляй — сделай вывод на экран какой-то информации и всё». **Решение (коммит c8a776e, версия 1.0.2):** - Роут `GET /` переписан так, чтобы **не зависеть от EJS-шаблонов**: отдаёт простой HTML напрямую (`res.send`), выводя: - версию Node, `APP_ENV`, PID - конфиг БД (host, port, database, user) - статус подключения к БД (или текст ошибки) - список таблиц (или сообщение об ошибке) - Каждая попытка работы с БД обёрнута в try/catch, поэтому страница **всегда** отдаёт 200 с информацией, даже если БД недоступна. - Весь остальной код (роуты `/table`, `/query`, `/healthz`, `dbConfig`, `pool`) **не удалён** — только главная страница упрощена. **Проверено:** синтаксис OK; запуск без env `PORT` → слушает `0.0.0.0:3000`; `GET /` → 200 (HTML с конфигом и статусом БД), `GET /healthz` при недоступной БД → 503 degraded. **⚠️ Точная причина падения пода по-прежнему не подтверждена** (нет доступа к логам Nubes). Если под не поднимется и после этого — смотреть логи `nodejsk8s` Deployment в Nubes UI: replica не активна (CrashLoopBackOff, invalidImage, порт/health, зависший старт). ## 7. JSON API POST /api/sql (перенос данных в новую ПГ) **Контекст:** нужно перенести белые списки (IP, кто создал, когда) из прежней БД в **новую** PostgreSQL (тот же realm k8s). Доступ к новой БД — через Keycloak, автоматизировать нельзя. Решение: код в репе `getpgdata` + передеплой с кредами новой ПГ. **Что добавлено (версия 1.0.3):** - Эндпоинт **`POST /api/sql`** — JSON API для произвольных SQL-команд. Body: `{ "sql": "...", "params": [...] }` (params → `$1..$n`). - `SELECT`/`WITH` → `{ ok:true, fields, rows, rowCount }` (всегда доступен) - `INSERT`/`UPDATE`/`DELETE`/DDL → `{ ok:true, rowCount }` **только при `WRITE_SQL=1`** - иначе → HTTP 403 «Write-SQL отключён» - **Защита:** выполняется только первый statement (split по `;`) — блокирует мульти-команды вроде `SELECT 1; DROP TABLE ...`. - Флаг в env: `WRITE_SQL=1` включает запись. **Логика переноса:** 1. Приложение подключено к прежней БД — читать данные через `/api/sql` (SELECT). 2. Сменить креды в jsonEnv на новую ПГ (+ `WRITE_SQL=1`). 3. Передеплой — теперь `/api/sql` выполняет запросы уже в новой БД; выполнить INSERT данных. **Проверено:** `node -c server.js` OK; локально: пустой SQL → 400; SELECT → идёт в БД; `INSERT` без `WRITE_SQL` → 403 (до попытки подключения); `WRITE_SQL=1` + мульти-statement → берётся только первый; процесс проверки остановлен. ## 8. Перенос данных из прежней БД в новую (60bdf3e3 → a5e9d24f) **Цель:** перенести белые списки (IP, кто создал, когда) и связанные данные в **новую** PostgreSQL (инстанс `a5e9d24f`), тот же realm k8s. Доступ к новой БД — через Keycloak, автоматизировать нельзя → перенос выполнен через `getpgdata` + `POST /api/sql`. ### 8.1 Выгрузка данных из прежней БД Пока приложение было подключено к прежней БД (`60bdf3e3`), все таблицы выгружены через `/api/sql` (SELECT * ORDER BY id) и **сохранены в репо** в `data/*.json`: | Файл | Таблица | Строк | Статус | |---|---|---|---| | `data/companies.json` | companies | 9 | полный, валиден | | `data/whitelist_entries.json` | whitelist_entries | 20 | полный, валиден | | `data/audit_log.json` | audit_log | 39 | полный, валиден | | `data/_migrations.json` | _migrations | 2 | полный, валиден | | session | session | 1147 | исключён (битый дамп, не нужен для переноса) | Сессии намеренно НЕ переносятся: это временные пользовательские сессии входа (`connect-pg-simple`), в новой БД они создаются заново. Коммиты: данные `~`, генератор `data/import.sql` (коммит `0b2d7ac`). ⚠️ **Урок (зафиксирован в памяти):** нужно было выгрузить ВСЕ таблицы до смены кредов. Первая выгрузка сохранила только `whitelist_entries`; `companies`/`audit_log` пришлось догонять после временного возврата кредов на прежнюю БД. ### 8.2 Переключение на новую БД В jsonEnv Nubes (`modify`) заданы креды новой ПГ + `WRITE_SQL=1`: ```json { "DB_HOST": "postgresqlk8s-master.a5e9d24f-0b5d-4196-8903-7ed60f6e6557.svc.cluster.local", "DB_PORT": "5432", "DB_NAME": "ipwhitelist", "DB_USER": "super", "DB_PASS": "<пароль новой ПГ>", "DB_SSLMODE": "disable", "WRITE_SQL": "1", "PORT": "3000" } ``` После `modify` проверено: `GET /` показывает host новой ПГ; `CREATE TEMP TABLE` → 200 (запись разрешена). ### 8.3 Проблема: несоответствие id компаний В новой БД **изначально уже были компании** под другими id (типовая схема), поэтому при прямой вставке по старым id возникли конфликты: - Вставка с явным id: `WZ03709`(id=23) упал — duplicate key по `client_id` (в новой БД `WZ03709` уже был под id=2) - `WZ01112` и `WZ03709` оказались на неверных id (1 и 2 вместо 2 и 23) - `WZ01325` (должен быть id=1) — вообще отсутствовал **Причина:** уникальный индекс `companies_client_id_key` (client_id), id в новой БД не совпадали со старой. **Решение (безопасно, т.к. записи/аудит в новой БД были пусты и FK на companies отсутствуют):** 1. `DELETE FROM companies WHERE client_id IN ('WZ01112','WZ03709')` — убрать с неверных id 2. Вставить заново под правильные id: id=1→WZ01325, id=2→WZ01112, id=23→WZ03709 3. Восстановить `custom_limit` и даты для этих 3 строк из дампа (первая вставка ставила `NOW()`) После этого маппинг id компаний новой БД **полностью совпал** со старой. ### 8.4 Вставка данных через /api/sql Так как `/api/sql` выполняет **один statement** за вызов, INSERT отправлены по одному стейтменту (клиент на Python): 1. `companies` — 9 вставок (с явными id, `ON CONFLICT (id) DO NOTHING`) 2. `whitelist_entries` — 20 вставок (id, company_id, value_cidr, comment, created_by, created_at, updated_by, updated_at, deleted_by, deleted_at) 3. `audit_log` — 39 вставок 4. `SELECT setval(...)` — сброс sequence для трёх таблиц на MAX(id) ### 8.5 Проверка целостности и соответствия После переноса выполнена **посимвольная сверка** новой БД против дампа старой: | Таблица | Строк old/new | missing | extra | diffs | Результат | |---|---|---|---|---|---| | whitelist_entries | 20/20 | 0 | 0 | 0 | **MATCH** | | audit_log | 39/39 | 0 | 0 | 0 | **MATCH** | | companies | 9/9 | 0 | 0 | 0 (после фикса) | **MATCH** | - `orphan` (записи без компании): **0** — все связи верны - sequences: companies→368, whitelist_entries→20, audit_log→39 ### 8.6 Итоговое состояние новой БД - companies = 9 - whitelist_entries = 20 (из них активных 12, удалённых 8 — перенесены с deleted_at) - audit_log = 39 - Данные идентичны прежней БД. **Активные белые списки (12):** WZ02425 (2), WZ02727 (6), WZ03656 (2), WZ01112 (8.8.8.8).