---
title: "Copia de seguridade, recuperación a un punto no tempo e proba de restauración"
description: "Fai copias de seguridade de Quire, restáuraas a un punto no tempo e comproba o proceso coa proba de restauración."
image: "https://docs.quirelms.com/og.png"
---

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

# Copia de seguridade, recuperación a un punto no tempo e proba de restauración

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

O deseño descríbese na sección 8 de `docs/architecture/23-ops.md`. Este é o manual de operacións do produto Docker Compose. Está redactado para que o poida seguir alguén que non o escribiu; se algún paso non queda claro, é un defecto deste documento.

## Que se protexe e como <!--quire:what-is-protected-and-how-->

| Recurso | Método | Lugar |
| --- | --- | --- |
| Base de datos | Arquivo continuo de WAL, como máximo cada 60 segundos, desde o primeiro inicio | Volume `pgwal` |
| Base de datos | Copias base con `pg_basebackup`, unha vez ao día de xeito predeterminado (`backup-scheduler`) | Volume `pgbackup` |
| Base de datos | Copias cifradas das copias base e do WAL cada cinco minutos (`backup-offsite`) | Almacén independente que indiques |
| Ficheiros | Volume `files`. Copia coas ferramentas do host ou utiliza almacenamento de obxectos con versións | Volume `files` |
| Segredos | `docker/.env`, sobre todo `QUIRE_MASTER_KEY` (e calquera `QUIRE_MASTER_KEY_RETIRED` que aínda se use), `QUIRE_BACKUP_ENCRYPTION_KEY` e `docker/secrets/audit-signing-key.pem` | Garda unha copia fóra deste host |
| Índices de busca, cachés e versións derivadas | Non se gardan nas copias; recréanse | |

Obxectivos: perder como máximo os cambios dos últimos 60 segundos e completar a restauración en 60 minutos para unha base de datos de 500 GB.

Dous erros son habituais. Se restauras unha base de datos **sen os ficheiros**, as páxinas amosaranse danadas. Se a restauras **sen `QUIRE_MASTER_KEY`**, non poderás descifrar as credenciais SSO, de webhook e de integración que contén; ata completar unha rotación da clave mestra sen valores sen resolver ([key-rotation.md](/gl/ops/key-rotation/)), isto inclúe as claves retiradas. Ambas as cousas forman parte da copia de seguridade.

## Facer copias de seguridade <!--quire:taking-backups-->

Copia base de todo o clúster:

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

Conserva as copias base máis recentes indicadas por `QUIRE_BACKUP_KEEP` (5 de xeito predeterminado) e elimina o WAL que xa non precise a copia máis antiga, para que o arquivo non medre sen límite.
Programa unha copia diaria cun cron ou temporizador systemd no 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
```

Ou deixa que a pila a programe: o perfil `backup` inicia `backup-scheduler`, que crea unha copia base cada `QUIRE_BACKUP_INTERVAL_HOURS` (24 de xeito predeterminado), e tamén o servizo `backup-offsite`, que se describe a continuación.

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

## Copias cifradas fóra do host <!--quire:encrypted-off-host-copies-->

Ambos os volumes están no mesmo host que a base de datos; unha copia na máquina que fallou non serve de nada. `backup-offsite` copia cada copia base e cada segmento WAL arquivado a un almacén diferente mediante o porto de almacenamento; cifra os datos e aplícalles a política de conservación:

- **Cifrado.** AES-256-GCM mediante `QUIRE_BACKUP_ENCRYPTION_KEY` (ou o ficheiro indicado por `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): 32 bytes, xerados con `openssl rand -hex 32`. Cada ficheiro ten un nonce propio e unha etiqueta de autenticación, polo que non se pode ler a copia sen a clave e detéctase calquera modificación. Garda a clave xunto con `QUIRE_MASTER_KEY`, lonxe deste host e do almacén das copias. Sen a clave non se pode restaurar.
- **Destino.** `QUIRE_BACKUP_STORAGE_DRIVER` é `s3`, `azure` ou `local` (un disco remoto montado en `QUIRE_BACKUP_STORAGE_ROOT`). Os axustes son os mesmos que para o almacenamento de ficheiros, co prefixo `QUIRE_BACKUP_`: `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`, `QUIRE_BACKUP_S3_ACCESS_KEY_ID` e demais. Utiliza un bucket distinto e, se é posible, unha conta diferente da dos ficheiros, con credenciais que poidan escribir pero non eliminar, se o provedor o permite.
- **Conservación.** Garda as copias base máis recentes indicadas por `QUIRE_BACKUP_OFFSITE_KEEP` (o valor de `QUIRE_BACKUP_KEEP` ou, se non está establecido, 7) e o WAL que precise a máis antiga; as copias e os segmentos anteriores elimínanse do almacén.
- **Frecuencia.** Cada `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (300 segundos de xeito predeterminado). O envío é idempotente: omite o que xa está no almacén e só considera gardada unha copia base cando se escribiu o seu manifesto, como último paso.

Tamén podes executar o mesmo comando manualmente:

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

Para restaurar nun host novo, recupera primeiro un conxunto e, a continuación, segue os pasos máis abaixo utilizando o directorio descargado no lugar do volume `pgbackup` e o arquivo `wal-archive` descargado no lugar de `pgwal`:

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

## Restaurar a un punto no tempo <!--quire:restoring-to-a-point-in-time-->

Utiliza este procedemento tras unha perda de datos: unha importación incorrecta, un curso eliminado ou unha migración de eliminación que haxa que desfacer. Substitúe a base de datos activa, polo que antes debes ensaiar a operación coa proba máis abaixo.

1. **Escolle a hora de destino**, en UTC e xusto antes do dano: `2026-09-24 09:30:00+00`. O rexistro de auditoría (`/admin/audit`) adoita indicar o momento.
2. **Detén todo o que escriba datos**: `docker compose -f docker/compose.yaml stop web content worker scheduler collab`.
3. **Conserva o clúster danado** ata verificar a restauración:
   ```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. **Extrae no volume de datos a copia base máis recente anterior ao destino** e activa a recuperación para o punto seleccionado:
   ```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. **Recupera**: inicia Postgres unha vez cos axustes de recuperación, mediante un ficheiro de substitución de Compose para non modificar o ficheiro 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]
   ```
   A substitución reemplaza o comando completo, polo que repite os dous
   axustes dos que depende a recuperación: `max_connections` debe ser polo
   menos igual ao da instancia principal (se non, a recuperación detense cun
   erro «insufficient parameter settings») e tamén hai que indicar o ficheiro
   montado `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. **Comproba a restauración** antes de permitir o acceso: valida a cadea de auditoría (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`) e confirma que se recuperaron os datos perdidos.
7. **Volve á execución normal**: `docker compose -f docker/compose.yaml up -d`. Postgres reiníciase co arquivo activado e comeza unha nova liña temporal de WAL. Fai inmediatamente unha nova copia base.

O nome de proxecto `quire` úsase como prefixo de cada volume; `docker volume ls` amosa os nomes exactos.

## Proba de verificación programada <!--quire:the-scheduled-verification-drill-->

`backup-offsite` tamén executa unha proba cada `QUIRE_BACKUP_DRILL_INTERVAL_HOURS` (168 horas, é dicir, cada semana, de xeito predeterminado), e repítea na seguinte execución se unha proba falla. Descarga a copia base externa máis recente e todos os segmentos WAL posteriores, descifra cada ficheiro (o que demostra que a clave segue abríndoos e que non se modificaron), compara cada ficheiro co seu manifesto, comproba que o arquivo é un directorio de datos Postgres e verifica que non haxa ocos no WAL desde a copia base. O informe gárdase no almacén como `reports/drill-<time>.json` e no rexistro do servizo; se a proba falla, indícase o ficheiro ou o primeiro segmento que falta.

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

Unha copia de seguridade que nunca se restaurou non está probada. A proba restaura a copia base completa máis recente anterior ao obxectivo, xunto co arquivo WAL, nunha instancia Postgres temporal que non comparte nada coa instancia activa, e comproba o resultado:

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

O destino debe estar en UTC e seguir exactamente ese formato. Precisa unha copia base anterior e segmentos WAL posteriores: nunha instalación nova, fai unha copia base e agarda ao seguinte segmento arquivado (como máximo un minuto se hai escrituras) antes de escoller un destino posterior á copia. Para executala só fan falta Docker e bash no host.

A proba falla se falla calquera dos seguintes pasos:

1. **Recuperabilidade**: o clúster temporal volve ao destino e ábrese correctamente.
2. **Integridade**: compárase o número de filas de cada táboa coa base activa (`tooling/restore-drill`). Como a base activa avanzou desde o momento de destino, unha táboa pode diferir nunha cantidade igual ao maior destes valores: 500 filas ou unha décima parte do seu tamaño, en calquera dirección (as escrituras reducen o número restaurado e as eliminacións fan que a copia teña máis); unha partición creada despois do destino non se considera unha táboa perdida. Podes ampliar a tolerancia nunha instalación con máis actividade mediante `QUIRE_DRILL_MAX_BEHIND` e `QUIRE_DRILL_MAX_DRIFT_RATIO`. A ausencia dunha táboa ou que estea baleira fan fallar a proba.
3. **Integridade**: a cadea hash de auditoría verifícase na copia restaurada.
4. **Usabilidade**: o rol da aplicación pode ler aplicando a seguridade a nivel de fila.
5. **Tempo**: mide o tempo desde o inicio ata o resultado correcto e compárao con `QUIRE_DRILL_RTO_SECONDS` (3600 de xeito predeterminado).

A proba nunca escribe na base de datos activa nin nos seus volumes: os volumes da copia base e do WAL móntanse en modo de só lectura, e o clúster temporal elimínase ao rematar, tanto se a proba pasa como se falla.

Establece `QUIRE_DRILL_REPORT` cunha ruta para escribir un informe JSON tanto se a proba pasa como se falla, e prográmaa desde o 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
```

Execútaa cada mes e antes de cada actualización. Se falla, non actualices. Unha vez por trimestre, pide a alguén que non escribise este manual que faga unha restauración real a un punto no tempo nun host de proba utilizando só este documento.

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

Os ficheiros locais están no volume `files`. Fai a copia ao mesmo tempo que a da base de datos e restáuraas xuntas:

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

Se utilizas almacenamento de obxectos, activa o versionado do bucket e conserva durante 35 días as versións que deixaron de estar vixentes; a recuperación a un punto no tempo dos ficheiros depende entón do propio bucket.

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