# Runbook резервного копирования и восстановления

## Паспорт документа

- Статус документа: working reference
- Актуально на: 31 марта 2026 года
- Владелец: backend/platform-команда
- Пересмотр: при изменении runtime topology, storage-модели, путей данных или стратегии backup
- Область применения: текущий production-контур Qadam на host-based runtime, автоматизированном backup baseline и off-host retention
- Связанные документы:
  - [Runbook эксплуатации и деплоя](./deployment-runbook.md)
  - [Environment matrix](./environment-matrix.md)
  - [Текущее состояние](../project/current-state.md)

## Цель документа

Этот документ фиксирует канонический backup/restore-контур Qadam для текущего production-сервера. На сегодня базовый backup уже автоматизирован через `qadam-backup.service` и `qadam-backup.timer`, но restore по-прежнему остаётся осознанной операционной процедурой, которую нужно выполнять аккуратно и вручную.

## 1. Текущий production backup-контур

- Автоматический backup запускается через `qadam-backup.service`.
- Планировщик работает через `qadam-backup.timer` ежедневно в `03:30 UTC` с небольшим `RandomizedDelaySec`.
- Канонический исполняемый runtime находится в `/data/qadam-core/scripts/backup-runtime.mjs`.
- Локальные snapshot-каталоги хранятся в `/var/backups/qadam/<STAMP>`.
- Последний результат backup фиксируется в `/var/lib/qadam-backup/state.json`.
- Off-host archive выгружается в S3-compatible storage по префиксу `backups/runtime/<STAMP>.tar.gz`.
- Текущая retention policy:
  - локальные snapshot-каталоги: `7` дней;
  - off-host archive в object storage: `30` дней.
- Telegram alerting работает без лишнего шума: backup шлёт сообщение при первом переходе в `FAIL` и отдельное `RECOVERY` после следующего успешного прогона; обычные success-run'ы молчат, если специально не включён `QADAM_BACKUP_NOTIFY_ON_SUCCESS=true`.

## 2. Что обязательно защищать

### Данные PostgreSQL

- Канонический источник: `DATABASE_URL` из `/etc/qadam/qadam.env`
- Текущий production runtime использует локальный PostgreSQL на хосте
- Критичность: максимальная

### API uploads

- Каталог: `/var/lib/qadam-core/uploads/images`
- Содержимое: изображения, загруженные через `POST /api/v1/upload/image`
- Критичность: высокая

### Данные roadmap-сервиса

- Загруженные markdown-файлы: `/var/lib/qadam-roadmap/uploads`
- Комментарии: `/var/lib/qadam-roadmap/comments.json`
- Feedback overlay frontend-команды: `/var/lib/qadam-roadmap/frontend-package-feedback.json`
- Критичность: средняя

### Конфигурация runtime

- `/etc/qadam/qadam.env`
- `/etc/qadam/qadam-roadmap.env`
- `/etc/qadam/qadam-monitor.env`
- `/etc/qadam/qadam-backup.env`
- `/etc/systemd/system/qadam-api.service`
- `/etc/systemd/system/qadam-web.service`
- `/etc/systemd/system/qadam-roadmap.service`
- `/etc/systemd/system/qadam-monitor.service`
- `/etc/systemd/system/qadam-monitor.timer`
- `/etc/systemd/system/qadam-backup.service`
- `/etc/systemd/system/qadam-backup.timer`
- `/etc/nginx/sites-available/qadam.2fab.app.conf`
- `/etc/nginx/sites-available/qadam-roadmap.2fab.app.conf`
- Критичность: высокая

## 3. Когда backup обязателен

- Перед любой Prisma migration на production, даже если ночной backup уже работает по timer.
- Перед релизом, который меняет auth, registration, billing, upload/storage или другую критичную бизнес-логику.
- Перед ручным ремонтом PostgreSQL, Redis, `systemd` или `nginx`.
- Перед изменением storage paths, env-файлов или TLS-контура.
- Перед rollback, если текущее состояние уже могло изменить данные.
- После существенного изменения backup policy, env-модели или storage account boundaries.

## 4. Состав backup-пакета

Каждый backup-пакет должен содержать:

1. `postgres.dump` — custom-format dump production БД.
2. `api-uploads.tar.gz` — архив API uploads.
3. `roadmap-uploads.tar.gz` — архив roadmap uploads.
4. `comments.json` — comments storage roadmap-сервиса.
5. `frontend-package-feedback.json` — operational overlay со статусами frontend adoption.
6. `metadata.json` — служебное описание snapshot, host и off-host object key.
7. Копии актуальных `env`, `systemd` и `nginx` файлов в `config/`.
8. `SHA256SUMS` — контрольные суммы файлов backup-пакета.

## 5. Как запускать backup

### Канонический способ

```bash
systemctl start qadam-backup.service
systemctl status qadam-backup.service --no-pager
cat /var/lib/qadam-backup/state.json
```

### Ручной запуск тем же runtime без systemd

```bash
set -a
source /etc/qadam/qadam.env
source /etc/qadam/qadam-backup.env
set +a

cd /data/qadam-core
pnpm backup:run
```

### Что делает скрипт автоматически

- снимает `pg_dump` production PostgreSQL;
- архивирует API uploads и roadmap uploads;
- копирует roadmap comments и frontend feedback overlay;
- копирует актуальные `env`, `systemd` и `nginx` файлы;
- пишет `metadata.json` и `SHA256SUMS`;
- локально верифицирует dump через `pg_restore --list`;
- локально проверяет контрольные суммы;
- собирает off-host archive и выгружает его в S3-compatible storage;
- подрезает локальные и off-host backup'ы по retention policy;
- пишет последний status/result в `/var/lib/qadam-backup/state.json`.

## 6. Минимальная верификация backup

```bash
systemctl status qadam-backup.timer qadam-backup.service --no-pager
cat /var/lib/qadam-backup/state.json
find /var/backups/qadam -maxdepth 1 -mindepth 1 | sort
```

Минимум, который должен подтвердиться:

- `qadam-backup.timer` находится в `active (waiting)`;
- последний `qadam-backup.service` завершился `status=0/SUCCESS`; состояние `inactive (dead)` после этого нормально, потому что сервис `Type=oneshot`;
- в `state.json` есть `status: "ok"`, `backupDir` и `offsiteObjectKey`;
- каталог snapshot существует локально в `/var/backups/qadam/<STAMP>`;
- off-host object существует в S3-compatible storage.

## 7. Процедура восстановления

### До восстановления

1. Зафиксировать причину restore и момент времени.
2. Остановить запись в систему.
3. Снять аварийный backup текущего состояния, если это ещё возможно.

### Остановить приложения

```bash
systemctl stop qadam-web qadam-roadmap qadam-api
```

### Если восстанавливаемся из off-host archive

1. Скачать `backups/runtime/<STAMP>.tar.gz` из object storage любым S3-compatible client или через provider console.
2. Развернуть архив в локальный backup root:

```bash
mkdir -p /var/backups/qadam
tar -xzf "/path/to/20260331T100420Z.tar.gz" -C /var/backups/qadam
```

После этого используется тот же локальный путь `/var/backups/qadam/<STAMP>`, как и для локального snapshot.

### Восстановить PostgreSQL

```bash
set -a
source /etc/qadam/qadam.env
set +a

pg_restore \
  --clean \
  --if-exists \
  --no-owner \
  --no-privileges \
  --dbname="${DATABASE_URL}" \
  "/var/backups/qadam/<STAMP>/postgres.dump"
```

### Восстановить файловые артефакты

```bash
rm -rf /var/lib/qadam-core/uploads/*
tar -xzf "/var/backups/qadam/<STAMP>/api-uploads.tar.gz" \
  -C /var/lib/qadam-core/uploads

rm -rf /var/lib/qadam-roadmap/uploads/*
tar -xzf "/var/backups/qadam/<STAMP>/roadmap-uploads.tar.gz" \
  -C /var/lib/qadam-roadmap/uploads

cp "/var/backups/qadam/<STAMP>/comments.json" /var/lib/qadam-roadmap/comments.json
cp "/var/backups/qadam/<STAMP>/frontend-package-feedback.json" \
  /var/lib/qadam-roadmap/frontend-package-feedback.json
```

### Восстановить конфигурацию при необходимости

Если проблема затрагивала env, `systemd` или `nginx`, вернуть файлы из backup-пакета и затем выполнить:

```bash
systemctl daemon-reload
systemctl start qadam-api qadam-web qadam-roadmap
systemctl reload nginx
```

## 8. Проверка после восстановления

```bash
systemctl status qadam-api qadam-web qadam-roadmap nginx
curl -I https://qadam.2fab.app
curl https://qadam.2fab.app/api/v1/health
curl -I https://qadam.2fab.app/api/docs
curl -I -u <login>:<password> https://qadam-roadmap.2fab.app
curl -u <login>:<password> https://qadam-roadmap.2fab.app/api/health
```

Минимум, который должен подтвердиться:

- `qadam-api`, `qadam-web`, `qadam-roadmap`, `nginx` находятся в `active`;
- `https://qadam.2fab.app/api/v1/health` отвечает `200`;
- `https://qadam-roadmap.2fab.app/api/health` отвечает `200` после basic auth;
- критичные пользовательские данные, uploads и roadmap feedback state на месте.

## 9. Ограничения текущей модели

- Product data и roadmap storage по-прежнему сначала живут на том же хосте, что и runtime; backup лишь снижает риск потери, а не устраняет shared-failure domain.
- Off-host backup использует те же object storage account boundaries, что и текущий S3-compatible storage для product media; отдельный backup-only bucket или отдельные credentials пока не выделены.
- Restore регулярно не прогоняется на staging/backup-контуре, поэтому recovery-time по-настоящему подтверждён только частично.

## 10. Что ещё нужно улучшить

- Вынести backup в отдельный bucket и отдельные credentials, если requirements по разделению доступов станут жёстче.
- Добавить archive-level encryption, если backup-контур начнёт жить вне доверенного object storage perimeter.
- Добавить регулярную проверку restore-процедуры на staging/backup-контуре.
