---
title: "Còpies de seguretat, recuperació en un moment concret i simulacre de restauració"
description: "Feu còpies de seguretat de Quire, restaureu-lo a un moment concret i comproveu-ho amb un simulacre."
image: "https://docs.quirelms.com/og.png"
---

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

# Còpies de seguretat, recuperació en un moment concret i simulacre de restauració

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

El disseny es descriu a la secció 8 de `docs/architecture/23-ops.md`. Aquest
és el manual operatiu del producte Docker Compose. Està escrit perquè el pugui
seguir algú que no l’hagi redactat; si un pas no és clar, és un defecte
d’aquest document.

## Què es protegeix i com <!--quire:what-is-protected-and-how-->

| Recurs | Mètode | Ubicació |
| --- | --- | --- |
| Base de dades | WAL arxivat contínuament, com a màxim cada 60 segons, des de la primera arrencada | Volum `pgwal` |
| Base de dades | Còpies base amb `pg_basebackup`, cada dia per defecte (`backup-scheduler`) | Volum `pgbackup` |
| Base de dades | Còpies xifrades de les còpies base i del WAL, cada cinc minuts (`backup-offsite`) | Magatzem separat que indiqueu |
| Fitxers | Volum `files`. Copieu-lo amb l’eina de còpia del vostre host o utilitzeu emmagatzematge d’objectes amb versions | Volum `files` |
| Secrets | `docker/.env`, sobretot `QUIRE_MASTER_KEY` (i qualsevol `QUIRE_MASTER_KEY_RETIRED` que encara s’utilitzi), `QUIRE_BACKUP_ENCRYPTION_KEY` i `docker/secrets/audit-signing-key.pem` | Conserveu-ne una còpia fora d’aquest host |
| Índexs de cerca, memòries cau i versions derivades | No se’n fa còpia; es tornen a crear | |

Objectius: recuperar-se fins a un punt situat a menys de 60 segons de l’error i
restaurar en menys de 60 minuts una base de dades de 500 GB.

Dos errors són habituals. Si restaureu la base de dades **sense els fitxers**,
les pàgines apareixen malmeses. Si la restaureu **sense `QUIRE_MASTER_KEY`**,
no podreu desxifrar les credencials SSO, webhook i integració que conté; fins
que una rotació de la clau mestra acabi sense res pendent
([key-rotation.md](/ca/ops/key-rotation/)), també hi calen les claus retirades. Totes
aquestes dades formen part de la còpia de seguretat.

## Fer còpies de seguretat <!--quire:taking-backups-->

Per fer una còpia base de tot el clúster:

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

Es conserven les `QUIRE_BACKUP_KEEP` còpies base més recents (5 per defecte) i
s’eliminen els arxius WAL que ja no necessita la còpia més antiga, perquè
l’arxiu no creixi sense límit. Programeu-ho cada dia amb cron o un temporitzador
systemd a l’host:

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

O deixeu que ho programi el stack: el perfil `backup` executa
`backup-scheduler`, que crea una còpia base cada `QUIRE_BACKUP_INTERVAL_HOURS`
(24 per defecte), i `backup-offsite`, que s’explica a continuació.

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

## Còpies xifrades fora de l’host <!--quire:encrypted-off-host-copies-->

Tots dos volums són al mateix host que la base de dades, i una còpia de
seguretat a la màquina que ha fallat no és cap còpia de seguretat. El servei
`backup-offsite` copia totes les còpies base i tots els segments WAL arxivats a
un magatzem separat a través del port d’emmagatzematge, els xifra i els conserva
segons la política de retenció:

- **Xifratge.** AES-256-GCM amb `QUIRE_BACKUP_ENCRYPTION_KEY` (o el fitxer
  indicat per `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): 32 bytes generats amb
  `openssl rand -hex 32`. Cada fitxer té un nonce i una etiqueta
  d’autenticació propis, de manera que no es pot llegir cap còpia sense la clau
  i qualsevol modificació es detecta. Conserveu la clau amb
  `QUIRE_MASTER_KEY`, lluny d’aquest host i del magatzem de còpies. Sense la
  clau no es pot restaurar.
- **Ubicació.** `QUIRE_BACKUP_STORAGE_DRIVER` pot ser `s3`, `azure` o `local`
  (disc remot muntat a `QUIRE_BACKUP_STORAGE_ROOT`). S’utilitzen les opcions
  d’emmagatzematge de fitxers amb el prefix `QUIRE_BACKUP_`:
  `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`,
  `QUIRE_BACKUP_S3_ACCESS_KEY_ID` i altres. Feu servir un bucket diferent i,
  idealment, un compte diferent del dels fitxers, amb credencials que puguin
  escriure però no suprimir, si el proveïdor ho permet.
- **Retenció.** Les còpies base més recents, `QUIRE_BACKUP_OFFSITE_KEEP`
  (per defecte, `QUIRE_BACKUP_KEEP` o 7), i el WAL que necessita la més antiga;
  els conjunts i segments anteriors se suprimeixen del magatzem.
- **Freqüència.** Cada `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (300 per defecte).
  L’enviament és idempotent: se salta el que ja hi ha desat i una còpia base
  només es considera desada quan el seu manifest s’ha escrit al final.

Les ordres es poden executar manualment:

```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
```

Per restaurar en un host nou, recupereu primer un conjunt i, després, seguiu
els passos següents fent servir el directori recuperat en lloc del volum
`pgbackup` i l’arxiu `wal-archive` recuperat en lloc de `pgwal`:

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

## Restaurar a un moment concret <!--quire:restoring-to-a-point-in-time-->

Feu-ho després d’una pèrdua de dades: una importació incorrecta, un curs
suprimit o una migració de retirada que cal desfer. Se substituirà la base de
dades activa; per tant, abans practiqueu el simulacre següent.

1. **Trieu l’hora de destinació**, en UTC, just abans del dany:
   `2026-09-24 09:30:00+00`. L’hora sol aparèixer al registre d’auditoria
   (`/admin/audit`).
2. **Atureu tots els serveis que escriuen**:
   `docker compose -f docker/compose.yaml stop web content worker scheduler collab`.
3. **Conserveu el clúster danyat** fins que s’hagi comprovat la restauració:
   ```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. **Descomprimiu la còpia base més recent anterior a l’hora de destinació**
   al volum de dades i demaneu una recuperació limitada:
   ```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. **Recupereu les dades**: inicieu Postgres una vegada amb les opcions de
   recuperació mitjançant una configuració alternativa de Compose, per no
   modificar el fitxer habitual:
   ```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]
   ```
   La configuració alternativa substitueix l’ordre sencera; per tant, cal
   repetir les dues opcions necessàries per a la recuperació: `max_connections`
   no pot ser inferior al valor del servidor principal (si no, la recuperació
   s’atura amb «insufficient parameter settings») i cal tornar a muntar
   `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. **Comproveu-ho** abans de permetre l’accés: verifiqueu la cadena
   d’auditoria (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`) i que les dades perdudes s’hagin recuperat.
7. **Torneu a l’operació habitual** amb `docker compose -f docker/compose.yaml up -d`. Això torna a iniciar Postgres amb l’arxivament activat i comença una nova línia de temps del WAL. Feu immediatament una còpia base nova.

El nom de projecte `quire` és el prefix dels volums; `docker volume ls` mostra
els noms exactes.

## Simulacre de verificació programat <!--quire:the-scheduled-verification-drill-->

`backup-offsite` també executa un simulacre cada `QUIRE_BACKUP_DRILL_INTERVAL_HOURS`
(168 hores, una vegada a la setmana per defecte) i, després d’un error, el
repeteix a la següent execució. Recupera del host extern la còpia base més
recent i tots els segments WAL posteriors, els desxifra (per comprovar que la
clau encara els pot obrir i que ningú no els ha modificat), compara cada fitxer
amb el seu manifest, comprova que l’arxiu és un directori de dades Postgres i
que no hi ha cap buit al WAL des de la còpia base. L’informe s’escriu al
magatzem com a `reports/drill-<time>.json` i al registre del servei; si el
simulacre falla, n’indica el fitxer o el primer segment absent.

## Simulacre de restauració <!--quire:the-restore-drill-->

Una còpia que no s’ha restaurat mai no és cap còpia. El simulacre restaura la
còpia base completa més recent anterior a l’hora de destinació i l’arxiu WAL en
un Postgres temporal, independent del servidor en ús, i en comprova el resultat:

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

L’hora de destinació ha d’estar en UTC i tenir exactament aquest format. Cal
una còpia base anterior i segments WAL arxivats posteriors: en una instal·lació
nova, feu una còpia base i espereu que s’arxivi el segment següent (com a
màxim un minut si hi ha escriptures) abans de triar una hora posterior a la
còpia. El simulacre només necessita Docker i bash a l’host.

Cada pas següent pot fer fallar el simulacre:

1. **Recuperabilitat**: el clúster temporal reprodueix les dades fins a l’hora
   de destinació i s’inicia.
2. **Integritat**: es comprova el nombre de files de cada taula en relació
   amb la base activa (`tooling/restore-drill`). La base activa ha avançat des
   de l’hora de destinació, així que cada taula pot diferir en el valor més
   gran entre 500 files i una dècima part de la seva mida, en totes dues
   direccions (les escriptures poden deixar-la endarrerida i les supressions
   poden fer que la còpia restaurada en tingui més). Una partició creada
   després de l’hora de destinació no compta com una taula perduda. En una
   instal·lació amb més activitat, augmenteu el marge amb
   `QUIRE_DRILL_MAX_BEHIND` i `QUIRE_DRILL_MAX_DRIFT_RATIO`. Si manca una
   taula o és buida, el simulacre falla.
3. **Integritat**: es verifica la cadena hash d’auditoria de la còpia
   restaurada.
4. **Usabilitat**: el rol d’aplicació pot llegir respectant la seguretat a
   nivell de fila.
5. **Durada**: temps fins al resultat correcte en relació amb
   `QUIRE_DRILL_RTO_SECONDS` (3600 per defecte).

No s’escriu mai a la base de dades en ús ni als seus volums: els volums de
còpia i WAL es munten només per a lectura, i el clúster temporal s’elimina al
final, tant si la prova s’aprova com si falla.

Establiu `QUIRE_DRILL_REPORT` amb el camí del fitxer per obtenir un informe
JSON, tant si s’aprova com si falla. Executeu periòdicament l’script des de
l’host 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
```

Executeu-lo cada mes i abans de cada actualització. Si falla, no es pot
actualitzar. Un cop cada trimestre, demaneu a algú que no hagi redactat
aquest manual que faci una restauració real a un host auxiliar utilitzant
només aquest document.

## Fitxers <!--quire:files-->

Els fitxers locals es troben al volum `files`. Feu-ne una còpia alhora que la
base de dades i restaureu-les totes dues juntes:

```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 .
```

Si utilitzeu emmagatzematge d’objectes, activeu el control de versions del
bucket i conserveu durant 35 dies les versions substituïdes. Així, la
recuperació de fitxers a un moment concret la proporciona el mateix bucket.

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