---
title: "Резервни копия, възстановяване към момент и проба за възстановяване"
description: "Архивирайте Quire, възстановете го към определен момент и докажете възможността с пробно възстановяване."
image: "https://docs.quirelms.com/og.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.quirelms.com/bg/llms.txt
> Use this file to discover all available pages before exploring further.

# Резервни копия, възстановяване към момент и проба за възстановяване

<span id="backup-point-in-time-recovery-and-the-restore-drill"></span>

Проектът е описан в раздел 8 на `docs/architecture/23-ops.md`. Това е оперативното ръководство за продукта Docker Compose. То е написано така, че да го изпълнява човек, който не го е съставил; ако някоя стъпка не е ясна, това е дефект в документа.

## Какво се защитава и как <!--quire:what-is-protected-and-how-->

| Актив | Начин | Местоположение |
| --- | --- | --- |
| Базата данни | Непрекъснато архивиране на WAL, най-много през 60 секунди от първото стартиране | Том `pgwal` |
| Базата данни | Базови резервни копия с `pg_basebackup`, по подразбиране ежедневно (`backup-scheduler`) | Том `pgbackup` |
| Базата данни | Криптирани копия на базовите резервни копия и WAL на всеки пет минути (`backup-offsite`) | Отделно зададено хранилище |
| Файлове | Том `files`. Копирайте го с хостовия инструмент за архивиране или използвайте хранилище за обекти с версии | Том `files` |
| Тайни | `docker/.env`, особено `QUIRE_MASTER_KEY` (и все още използвания `QUIRE_MASTER_KEY_RETIRED`), `QUIRE_BACKUP_ENCRYPTION_KEY` и `docker/secrets/audit-signing-key.pem` | Запазете копие извън този хост |
| Индекси за търсене, кешове, производни файлове | Не се архивират; изграждат се отново | |

Цели: точка за възстановяване до 60 секунди преди повредата и възстановяване
до 60 минути при база данни от 500 GB.

Често се допускат две грешки. Възстановена база данни **без файловете** показва
счупени страници. Възстановена база данни **без `QUIRE_MASTER_KEY`** не може
да декриптира SSO идентификационните данни, уебкуките и интеграциите; докато
ротацията на главния ключ не завърши без неразрешени стойности
([key-rotation.md](/bg/ops/key-rotation/)), това включва и оттеглените ключове. И двете са част от резервното копие.

## Създаване на резервни копия <!--quire:taking-backups-->

Базово резервно копие на целия клъстер:

```sh
docker compose -f docker/compose.yaml --profile backup run --rm backup
```

Съхраняват се най-новите базови копия в брой `QUIRE_BACKUP_KEEP` (по подразбиране 5), а WAL, който вече не е нужен на най-старото копие, се изчиства, така че архивът да не расте безкрайно.
Настройте ежедневно изпълнение с cron или systemd timer на хост:

```cron
15 2 * * * cd /srv/quire && docker compose -f docker/compose.yaml --profile backup run --rm backup >> /var/log/quire-backup.log 2>&1
```

Или оставете стека да го планира: профилът `backup` изпълнява `backup-scheduler`,
който създава базово резервно копие през `QUIRE_BACKUP_INTERVAL_HOURS` (по подразбиране 24 часа),
както и `backup-offsite`, описан по-долу.

```sh
docker compose -f docker/compose.yaml --profile backup up -d
```

## Криптирани копия извън хоста <!--quire:encrypted-off-host-copies-->

И двата тома са на същия хост като базата данни, а резервно копие на
счупената машина не е резервно копие. `backup-offsite` копира всяко базово копие
и всеки архивиран WAL сегмент към отделно хранилище през порт за съхранение,
криптирано, и ги задържа според правилата за съхранение:

- **Криптиране.** AES-256-GCM с `QUIRE_BACKUP_ENCRYPTION_KEY` (или файла,
  посочен чрез `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): 32 байта, създадени с
  `openssl rand -hex 32`. Всеки файл има собствен nonce и маркер за удостоверяване,
  така че копието не може да бъде прочетено без ключа и всяка промяна се засича. Съхранявайте ключа заедно с `QUIRE_MASTER_KEY`, далеч от този хост и от хранилището на резервните копия. Без ключа няма възстановяване.
- **Местоположение.** `QUIRE_BACKUP_STORAGE_DRIVER` е `s3`, `azure` или `local` (монтиран отдалечен диск на `QUIRE_BACKUP_STORAGE_ROOT`). Настройките са тези за файловото хранилище с префикс `QUIRE_BACKUP_`: `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`, `QUIRE_BACKUP_S3_ACCESS_KEY_ID` и други. Използвайте различна кофа и, в идеалния случай, различен акаунт от този за файловете; задайте идентификационни данни за запис, но не и за изтриване, ако доставчикът го позволява.
- **Съхранение.** Най-новите базови резервни копия в брой `QUIRE_BACKUP_OFFSITE_KEEP` (по подразбиране стойността на `QUIRE_BACKUP_KEEP`, иначе 7) и WAL, необходим на най-старото от тях; по-старите набори и сегменти се изтриват от хранилището.
- **Честота.** На всеки `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (по подразбиране 300). Прехвърлянето е идемпотентно: вече съхраненото се пропуска, а базовото копие се счита за съхранено едва след последния запис на манифеста.

Същите команди могат да се изпълнят ръчно:

```sh
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts ship
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
```

За възстановяване на нов хост първо върнете набор, след това изпълнете стъпките по-долу, като използвате извлечената директория вместо тома `pgbackup`, а извлечения `wal-archive` вместо `pgwal`:

```sh
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
```

## Възстановяване към определен момент <!--quire:restoring-to-a-point-in-time-->

Използвайте това след загуба на данни: неуспешен импорт, изтрит курс или миграция за премахване, която трябва да отмените. Тя заменя активната база данни, затова първо упражнете процедурата с пробното възстановяване по-долу.

1. **Изберете целевия момент** в UTC, точно преди повредата:
   `2026-09-24 09:30:00+00`. Журналът за одит (`/admin/audit`) обикновено показва момента.
2. **Спрете всичко, което записва**:
   `docker compose -f docker/compose.yaml stop web content worker scheduler collab`
3. **Запазете повредения клъстер**, докато не бъде проверено възстановяването:
   ```sh
   docker compose -f docker/compose.yaml stop postgres
   docker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
   ```
4. **Разархивирайте най-новото базово копие, по-старо от целевия момент**, в тома с данни и поискайте възстановяване до целта:
   ```sh
   docker run --rm -v quire_pgbackup:/backups:ro -v quire_postgres18-data:/var/lib/postgresql postgres:18-alpine sh -euc '
     base="$(ls -1d /backups/base-* | sort | tail -n 1)"   # or the one before the target
     rm -rf /var/lib/postgresql/18/docker && mkdir -p /var/lib/postgresql/18/docker
     tar -xzf "$base/base.tar.gz" -C /var/lib/postgresql/18/docker
     touch /var/lib/postgresql/18/docker/recovery.signal
     chown -R postgres:postgres /var/lib/postgresql/18/docker && chmod 700 /var/lib/postgresql/18/docker'
   ```
5. **Възстановете**: стартирайте Postgres веднъж с настройките за възстановяване от Compose override, така че нормалният файл да остане непроменен:
   ```yaml
   # docker/compose.recover.yaml
   services:
     postgres:
       command: [postgres, -c, "restore_command=cp /var/lib/postgresql/wal-archive/%f %p",
                 -c, "recovery_target_time=2026-09-24 09:30:00+00",
                 -c, recovery_target_action=promote, -c, archive_mode=off,
                 -c, max_connections=200, -c, hba_file=/etc/postgresql/pg_hba.conf]
   ```
   Override заменя цялата команда, затова включва двете настройки, от които зависи възстановяването: `max_connections` не трябва да е по-ниско от стойността на основния сървър (иначе възстановяването спира с „insufficient parameter settings“) и монтирания файл `pg_hba.conf`.
   ```sh
   docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgres
   docker compose -f docker/compose.yaml logs -f postgres   # wait for "database system is ready"
   ```
6. **Проверете** преди да допуснете потребители: веригата за одит
   (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`)
   и че изгубените данни са възстановени.
7. **Върнете се към нормална работа**: `docker compose -f docker/compose.yaml up -d`. Така Postgres се рестартира с включено архивиране и започва нова WAL времева линия. Веднага създайте ново базово резервно копие.

Името на проекта `quire` е префикс за всеки том; точните имена се виждат чрез `docker volume ls`.

## Планирана проверка <!--quire:the-scheduled-verification-drill-->

`backup-offsite` изпълнява пробно възстановяване през `QUIRE_BACKUP_DRILL_INTERVAL_HOURS`
(по подразбиране 168, седмично), както и при следващото изпълнение след неуспешна проверка. То извлича най-новото базово копие извън хоста и всички WAL сегменти след него, декриптира ги (доказвайки, че ключът все още ги отваря и че нищо не е променяно), сравнява всеки файл с манифеста му, проверява, че архивът е директория с данни на Postgres, и проверява, че във WAL след резервното копие няма пропуски. Отчетът се записва в хранилището като `reports/drill-<time>.json` и в журнала на услугата; неуспешната проверка посочва файла или първия липсващ сегмент.

## Пробно възстановяване <!--quire:the-restore-drill-->

Резервно копие, което никога не е възстановявано, не е резервно копие. Пробната операция възстановява най-новото пълно базово копие, направено преди целевия момент, заедно с WAL архива, във временен Postgres, който не споделя нищо с активната база, и проверява резултата:

```sh
docker/scripts/restore-drill.sh                                  # to ninety minutes ago
docker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
```

Целевият час е UTC в точно този формат. Нужни са базово копие, по-старо от него, и архивиран WAL след целта: при нова инсталация създайте базово копие и изчакайте следващия архивен сегмент (най-много минута при наличие на записи), преди да изберете момент след копието. За пробната операция на хоста са нужни Docker и bash и нищо друго.

Всяка от следните проверки може да провали пробата:

1. **Възможност за възстановяване**: временният клъстер възпроизвежда WAL до целта и се стартира.
2. **Пълнота**: броят редове във всяка таблица се сравнява с активната база (`tooling/restore-drill`). Активната база се е променила след целта, затова разликата в таблицата може да е по-голямата стойност измежду 500 реда и една десета от размера ѝ, в която и да е посока (записите я карат да изостава, изтриванията оставят повече във възстановеното копие); дял, създаден след целта, не се счита за загубена таблица. Разширете допустимата разлика в по-натоварена инсталация чрез `QUIRE_DRILL_MAX_BEHIND` и `QUIRE_DRILL_MAX_DRIFT_RATIO`. Липсваща или изпразнена таблица води до неуспех.
3. **Цялост**: хеш верига за одит се проверява във възстановеното копие.
4. **Използваемост**: ролята на приложението чете данните чрез row-level security.
5. **Време**: време от стартиране до успешен резултат спрямо `QUIRE_DRILL_RTO_SECONDS`
   (по подразбиране 3600).

Пробата никога не записва в активната база или нейните томове: томовете за архиви и WAL са монтирани само за четене, а временният клъстер се премахва в края — независимо от резултата.

Задайте `QUIRE_DRILL_REPORT` на път за запис на JSON отчета, успешен или не, и планирайте изпълнение от Docker хоста:

```cron
30 3 1 * * cd /srv/quire && QUIRE_DRILL_REPORT=/var/log/quire-drill.json docker/scripts/restore-drill.sh >> /var/log/quire-drill.log 2>&1
```

Изпълнявайте я всеки месец и преди всяко надстройване. Веднъж на тримесечие помолете човек, който не е написал ръководството, да извърши реално възстановяване към определен момент на резервен хост, като използва само този документ.

## Файлове <!--quire:files-->

Локалните файлове са в тома `files`. Архивирайте го едновременно с базата данни и възстановявайте и двете заедно:

```sh
docker run --rm -v quire_files:/files:ro -v "$PWD":/out alpine tar -czf /out/files-$(date -u +%Y%m%d).tar.gz -C /files .
```

При използване на обектно хранилище включете версиониране на кофата и пазете старите версии 35 дни; тогава възстановяването на файлове към определен момент се осигурява от самата кофа.

Source: https://docs.quirelms.com/bg/ops/backup-restore/index.mdx
