Загрузка дампа БД на сервер
🗄️ Сложность: medium | Скоупы: vibe:infra | Стек: cURL, psql / pg_restore
Сценарий: восстановить дамп PostgreSQL (или другой СУБД) в базу, доступную с BLACKHOLE-сервера. BLACKHOLE (BH) — сервер за защищённым туннелем, к которому идут запросы exec, upload и logs. Порядок из четырёх шагов: файл на сервер, инструменты нужной версии, восстановление в фоновом режиме, проверка счётчиков.
Во всех примерах подставьте свои значения: $VIBE_URL — базовый URL https://vibecode.bitrix24.tech, $SERVER_ID — идентификатор сервера, $VIBE_API_KEY — API-ключ Вайбкод со скоупом vibe:infra. Плейсхолдеры подключения к БД — DB_HOST, DB_USER, DB_NAME.
Шаг 0. Каталог для данных — вне /opt/app
Каталог /opt/app очищается при каждом деплое (cleanDeploy: true по умолчанию), а /opt/data и установленные среды выполнения переживают редеплои. Дамп и рабочие файлы кладите в /opt/data:
curl -sS -X POST "$VIBE_URL/v1/infra/servers/$SERVER_ID/exec" \
-H "X-Api-Key: $VIBE_API_KEY" -H "Content-Type: application/json" \
-d '{"command": "mkdir -p /opt/data"}'
Шаг 1. Загрузить дамп через /upload
Мегабайты base64 не помещаются в аргумент shell-команды из-за лимита ARG_MAX — соберите тело запроса в файл и отправьте его через --data-binary. Скрипт кодирует db.dump в base64 и складывает в upload.json, затем curl отправляет этот файл:
python3 - <<'EOF'
import base64, json
data = base64.b64encode(open('db.dump', 'rb').read()).decode()
json.dump({'content': data, 'path': '/opt/data/db.dump', 'mode': '0644'}, open('upload.json', 'w'))
EOF
curl -sS -X POST "$VIBE_URL/v1/infra/servers/$SERVER_ID/upload" \
-H "X-Api-Key: $VIBE_API_KEY" -H "Content-Type: application/json" \
--data-binary @upload.json
Альтернатива — поле url вместо content: агент скачает файл по ссылке сам, без base64.
Шаг 2. Инструменты той же мажорной версии, что целевая БД
Восстановление падает, если мажорная версия клиента не совпадает с версией целевой базы. Дамп из pg_dump версии 17 в базе версии 16 даёт ошибку класса unsupported version: формат архива 1.16 не читается, а внутри дампа встречаются метакоманды SET transaction_timeout и \restrict, которых нет в 16-й версии. Установите клиент нужной мажорной версии один раз — каталог /usr переживает деплой, повторная установка при редеплоях не нужна:
curl -sS -X POST "$VIBE_URL/v1/infra/servers/$SERVER_ID/exec" \
-H "X-Api-Key: $VIBE_API_KEY" -H "Content-Type: application/json" \
-d '{"command": "apt-get install -y postgresql-client-17"}'
Если установка тяжёлая и выходит за лимит timeout (максимум 600 секунд), запустите её фоновой задачей через systemd-run — задача получает собственную cgroup и не прерывается по таймауту exec:
curl -sS -X POST "$VIBE_URL/v1/infra/servers/$SERVER_ID/exec" \
-H "X-Api-Key: $VIBE_API_KEY" -H "Content-Type: application/json" \
-d '{"command": "systemctl reset-failed install-pg 2>/dev/null; systemd-run --unit=install-pg /bin/bash -c \"apt-get install -y postgresql-client-17\"", "timeout": 30}'
curl -sS -H "X-Api-Key: $VIBE_API_KEY" \
"$VIBE_URL/v1/infra/servers/$SERVER_ID/logs?service=install-pg&lines=50"
Шаг 3. Restore — фоновой задачей, с остановкой приложения
Восстановление большого дампа занимает минуты, а на таймауте exec агент завершает всю группу процессов сигналом SIGKILL без паузы на корректное завершение. Поэтому восстановление всегда идёт фоновой задачей. Оформите его отдельным скриптом и загрузите через /upload с правами mode: "0755" — так же, как дамп в шаге 1:
#!/bin/bash
set -euo pipefail
export PGPASSWORD="…" # либо ~/.pgpass; НЕ в command — команды журналируются
PSQL=(psql -h DB_HOST -p 5432 -U DB_USER -d DB_NAME)
systemctl stop app # запросы приложения держат блокировки — TRUNCATE/COPY встанут в очередь
"${PSQL[@]}" -c 'TRUNCATE TABLE t1, t2 RESTART IDENTITY CASCADE;'
pg_restore --data-only --no-owner --no-privileges \
-h DB_HOST -p 5432 -U DB_USER -d DB_NAME /opt/data/db.dump
systemctl start app
echo "RESTORE COMPLETE"
Пароль базы передаётся через переменную окружения PGPASSWORD или файл ~/.pgpass, но не через текст команды — агент журналирует первые ~200 символов команды, и секрет попал бы в журнал. Приложение останавливается на время восстановления: его запросы держат блокировки, и без остановки TRUNCATE и COPY встанут в очередь.
Два нюанса pg_restore --data-only, которые всплывают на связанных таблицах:
- Порядок таблиц — алфавитный, не по зависимостям. Данные льются в алфавитном порядке имён таблиц, поэтому дочерняя таблица может пойти раньше родительской и упасть на нарушении внешнего ключа. Лекарство — переупорядоченный список объектов:
pg_restore --list db.dump > toc.list, переставьте строки данных так, чтобы родительские таблицы шли первыми, и восстанавливайте сpg_restore -L toc.list …. - Схему миграций ORM исключите из восстановления. Drizzle, Prisma и похожие инструменты держат собственную таблицу миграций в отдельной схеме (например,
drizzle.__drizzle_migrations): дамп несёт её данные, а приложение при старте могло уже вставить туда свежую строку —COPYупадёт на дубликате ключа. Исключайте схему целиком, напримерpg_restore -N drizzle …, — таблицей миграций управляет само приложение.
Остановка systemctl stop app в скрипте выше закрывает и третий капкан: юнит с Restart=on-failure перезапустил бы упавшее приложение в промежутке между TRUNCATE и стартом COPY, и оно успело бы записать свои строки — одной секунды достаточно. Если во время долгого восстановления возможен внешний запуск приложения (например, параллельный /deploy), усильте остановку до systemctl mask app, а перед финальным запуском снимите маску: systemctl unmask app && systemctl start app.
Запуск и контроль. Единица systemd-run даёт задаче собственную cgroup, поэтому она переживает таймаут exec. Перед повторным запуском под тем же именем сбросьте прежнее состояние через systemctl reset-failed:
curl -sS -X POST "$VIBE_URL/v1/infra/servers/$SERVER_ID/exec" \
-H "X-Api-Key: $VIBE_API_KEY" -H "Content-Type: application/json" \
-d '{"command": "systemctl reset-failed restore-db 2>/dev/null; systemd-run --unit=restore-db /bin/bash /opt/data/restore.sh", "timeout": 30}'
curl -sS -H "X-Api-Key: $VIBE_API_KEY" \
"$VIBE_URL/v1/infra/servers/$SERVER_ID/logs?service=restore-db&lines=50"
Атомарный вариант — psql --single-transaction -v ON_ERROR_STOP=1: восстановление идёт одной транзакцией, ошибка видна сразу и откатывает загрузку целиком. Без него возможна ситуация «задача выполнялась часами, а загрузилось 0 строк», потому что pg_restore продолжает работу после ошибки.
Шаг 4. Проверить счётчики
После восстановления сверьте количество строк в таблицах. Пароль базы здесь передаётся через поле env, а не в тексте команды:
curl -sS -X POST "$VIBE_URL/v1/infra/servers/$SERVER_ID/exec" \
-H "X-Api-Key: $VIBE_API_KEY" -H "Content-Type: application/json" \
-d '{"command": "psql -h DB_HOST -U DB_USER -d DB_NAME -tAc \"SELECT count(*) FROM t1\"", "env": {"PGPASSWORD": "…"}, "timeout": 60}'
Смотрите также
- Выполнение команд на сервере — раздел «Фоновые задачи»
- Загрузка файлов на сервер
- Чтение логов сервера