Проектът е описан в раздел 8 на docs/architecture/23-ops.md. Това е оперативното ръководство за продукта Docker Compose. То е написано така, че да го изпълнява човек, който не го е съставил; ако някоя стъпка не е ясна, това е дефект в документа.
Какво се защитава и как
Актив
Начин
Местоположение
Базата данни
Непрекъснато архивиране на 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), това включва и оттеглените ключове. И двете са част от резервното копие.
Създаване на резервни копия
Базово резервно копие на целия клъстер:
docker compose -f docker/compose.yaml --profile backup run --rm backup
Съхраняват се най-новите базови копия в брой QUIRE_BACKUP_KEEP (по подразбиране 5), а WAL, който вече не е нужен на най-старото копие, се изчиства, така че архивът да не расте безкрайно.
Настройте ежедневно изпълнение с cron или systemd timer на хост:
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, описан по-долу.
docker compose -f docker/compose.yaml --profile backup up -d
Криптирани копия извън хоста
И двата тома са на същия хост като базата данни, а резервно копие на
счупената машина не е резервно копие. 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). Прехвърлянето е идемпотентно: вече съхраненото се пропуска, а базовото копие се счита за съхранено едва след последния запис на манифеста.
Същите команди могат да се изпълнят ръчно:
docker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts shipdocker compose -f docker/compose.yaml run --rm backup-offsite bun apps/worker/src/backups/main.ts verify
За възстановяване на нов хост първо върнете набор, след това изпълнете стъпките по-долу, като използвате извлечената директория вместо тома pgbackup, а извлечения wal-archive вместо pgwal:
bun apps/worker/src/backups/main.ts fetch base-20260924T021500Z /srv/restore
Възстановяване към определен момент
Използвайте това след загуба на данни: неуспешен импорт, изтрит курс или миграция за премахване, която трябва да отмените. Тя заменя активната база данни, затова първо упражнете процедурата с пробното възстановяване по-долу.
Изберете целевия момент в UTC, точно преди повредата:
2026-09-24 09:30:00+00. Журналът за одит (/admin/audit) обикновено показва момента.
Спрете всичко, което записва:
docker compose -f docker/compose.yaml stop web content worker scheduler collab
Запазете повредения клъстер, докато не бъде проверено възстановяването:
docker compose -f docker/compose.yaml stop postgresdocker run --rm -v quire_postgres18-data:/from -v quire_postgres-damaged:/to alpine cp -a /from/. /to/
Разархивирайте най-новото базово копие, по-старо от целевия момент, в тома с данни и поискайте възстановяване до целта:
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'
Възстановете: стартирайте Postgres веднъж с настройките за възстановяване от Compose override, така че нормалният файл да остане непроменен:
Override заменя цялата команда, затова включва двете настройки, от които зависи възстановяването: max_connections не трябва да е по-ниско от стойността на основния сървър (иначе възстановяването спира с „insufficient parameter settings“) и монтирания файл pg_hba.conf.
docker compose -f docker/compose.yaml -f docker/compose.recover.yaml up -d postgresdocker compose -f docker/compose.yaml logs -f postgres # wait for "database system is ready"
Проверете преди да допуснете потребители: веригата за одит
(docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts)
и че изгубените данни са възстановени.
Върнете се към нормална работа: docker compose -f docker/compose.yaml up -d. Така Postgres се рестартира с включено архивиране и започва нова WAL времева линия. Веднага създайте ново базово резервно копие.
Името на проекта quire е префикс за всеки том; точните имена се виждат чрез docker volume ls.
Планирана проверка
backup-offsite изпълнява пробно възстановяване през QUIRE_BACKUP_DRILL_INTERVAL_HOURS
(по подразбиране 168, седмично), както и при следващото изпълнение след неуспешна проверка. То извлича най-новото базово копие извън хоста и всички WAL сегменти след него, декриптира ги (доказвайки, че ключът все още ги отваря и че нищо не е променяно), сравнява всеки файл с манифеста му, проверява, че архивът е директория с данни на Postgres, и проверява, че във WAL след резервното копие няма пропуски. Отчетът се записва в хранилището като reports/drill-<time>.json и в журнала на услугата; неуспешната проверка посочва файла или първия липсващ сегмент.
Пробно възстановяване
Резервно копие, което никога не е възстановявано, не е резервно копие. Пробната операция възстановява най-новото пълно базово копие, направено преди целевия момент, заедно с WAL архива, във временен Postgres, който не споделя нищо с активната база, и проверява резултата:
docker/scripts/restore-drill.sh # to ninety minutes agodocker/scripts/restore-drill.sh --target "2026-09-24 09:30:00+00"
Целевият час е UTC в точно този формат. Нужни са базово копие, по-старо от него, и архивиран WAL след целта: при нова инсталация създайте базово копие и изчакайте следващия архивен сегмент (най-много минута при наличие на записи), преди да изберете момент след копието. За пробната операция на хоста са нужни Docker и bash и нищо друго.
Всяка от следните проверки може да провали пробата:
Възможност за възстановяване: временният клъстер възпроизвежда WAL до целта и се стартира.
Пълнота: броят редове във всяка таблица се сравнява с активната база (tooling/restore-drill). Активната база се е променила след целта, затова разликата в таблицата може да е по-голямата стойност измежду 500 реда и една десета от размера ѝ, в която и да е посока (записите я карат да изостава, изтриванията оставят повече във възстановеното копие); дял, създаден след целта, не се счита за загубена таблица. Разширете допустимата разлика в по-натоварена инсталация чрез QUIRE_DRILL_MAX_BEHIND и QUIRE_DRILL_MAX_DRIFT_RATIO. Липсваща или изпразнена таблица води до неуспех.
Цялост: хеш верига за одит се проверява във възстановеното копие.
Използваемост: ролята на приложението чете данните чрез row-level security.
Време: време от стартиране до успешен резултат спрямо QUIRE_DRILL_RTO_SECONDS
(по подразбиране 3600).
Пробата никога не записва в активната база или нейните томове: томовете за архиви и WAL са монтирани само за четене, а временният клъстер се премахва в края — независимо от резултата.
Задайте QUIRE_DRILL_REPORT на път за запис на JSON отчета, успешен или не, и планирайте изпълнение от Docker хоста:
Изпълнявайте я всеки месец и преди всяко надстройване. Веднъж на тримесечие помолете човек, който не е написал ръководството, да извърши реално възстановяване към определен момент на резервен хост, като използва само този документ.
Файлове
Локалните файлове са в тома files. Архивирайте го едновременно с базата данни и възстановявайте и двете заедно:
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 дни; тогава възстановяването на файлове към определен момент се осигурява от самата кофа.