---
title: "Sikkerhetskopi, punkt-i-tid-gjenoppretting og gjenopprettingsøvelsen"
description: "Sikkerhetskopier Quire, gjenopprett det til et tidspunkt, og bevis det med gjenopprettingsøvelsen."
image: "https://docs.quirelms.com/og.png"
---

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

# Sikkerhetskopi, punkt-i-tid-gjenoppretting og gjenopprettingsøvelsen

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

Utformingen er `docs/architecture/23-ops.md` seksjon 8. Dette er prosedyren for Docker Compose-produktet. Den er skrevet for å følges av noen som ikke skrev den; hvis et steg er uklart, er det en feil i dette dokumentet.

## Hva som beskyttes, og hvordan <!--quire:what-is-protected-and-how-->

| Ressurs | Hvordan | Hvor |
| --- | --- | --- |
| Databasen | WAL arkiveres kontinuerlig, høyst hvert 60. sekund, fra første oppstart | `pgwal`-volum |
| Databasen | Basesikkerhetskopier med `pg_basebackup`, daglig som standard (`backup-scheduler`) | `pgbackup`-volum |
| Databasen | Krypterte kopier av basesikkerhetskopiene og WAL-en, hvert femte minutt (`backup-offsite`) | Et separat lager du navngir |
| Filer | `files`-volumet. Kopier det med vertens sikkerhetskopiverktøy, eller bruk versjonert objektlagring | `files`-volum |
| Hemmeligheter | `docker/.env`, fremfor alt `QUIRE_MASTER_KEY` (og eventuell `QUIRE_MASTER_KEY_RETIRED` fortsatt i bruk), `QUIRE_BACKUP_ENCRYPTION_KEY`, og `docker/secrets/audit-signing-key.pem` | Behold en kopi utenfor denne verten |
| Søkeindekser, mellomlagre, avspillinger | Sikkerhetskopieres ikke; bygges på nytt | |

Mål: et gjenopprettingspunkt innen 60 sekunder fra feilen, og en gjenoppretting innen 60 minutter for en 500 GB database.

To feil er vanlige. En database gjenopprettet **uten filene sine** gir ødelagte sider. En database gjenopprettet **uten `QUIRE_MASTER_KEY`** kan ikke dekryptere SSO-, webhook- og integrasjonslegitimasjonen den holder; til en hovednøkkelrotering fullføres uten noe uløst ([key-rotation.md](/nb/ops/key-rotation/)), inkluderer det de pensjonerte nøklene. Begge er en del av sikkerhetskopien.

## Ta sikkerhetskopier <!--quire:taking-backups-->

En basesikkerhetskopi av hele klyngen:

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

Den beholder de nyeste `QUIRE_BACKUP_KEEP`-basesikkerhetskopiene (standard 5) og beskjærer WAL den eldste ikke lenger trenger, slik at arkivet ikke kan vokse grenseløst. Planlegg den daglig med cron eller en systemd-timer på verten:

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

Eller la stabelen planlegge den: `backup`-profilen kjører `backup-scheduler`, som tar en basesikkerhetskopi hver `QUIRE_BACKUP_INTERVAL_HOURS` (standard 24), og `backup-offsite`, beskrevet nedenfor.

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

## Krypterte kopier utenfor verten <!--quire:encrypted-off-host-copies-->

Begge volumene ligger på samme vert som databasen, og en sikkerhetskopi på maskinen som feilet er ingen sikkerhetskopi. `backup-offsite` kopierer alle basesikkerhetskopier og alle arkiverte WAL-segmenter til et separat lager gjennom lagringsporten, kryptert, og beholder dem der under oppbevaring:

- **Kryptering.** AES-256-GCM med `QUIRE_BACKUP_ENCRYPTION_KEY` (eller filen navngitt av `QUIRE_BACKUP_ENCRYPTION_KEY_FILE`): 32 bytes, fra `openssl rand -hex 32`. Hver fil har sin egen nonce og en autentiseringstag, slik at en kopi er uleselig uten nøkkelen og enhver endring av den oppdages. Behold nøkkelen med `QUIRE_MASTER_KEY`, borte fra denne verten og borte fra sikkerhetskopilageret. Uten nøkkelen finnes ingen gjenoppretting.
- **Hvor.** `QUIRE_BACKUP_STORAGE_DRIVER` er `s3`, `azure` eller `local` (en montert fjernplate på `QUIRE_BACKUP_STORAGE_ROOT`). Innstillingene er fillagringens med et `QUIRE_BACKUP_`-prefiks: `QUIRE_BACKUP_S3_ENDPOINT`, `QUIRE_BACKUP_S3_BUCKET`, `QUIRE_BACKUP_S3_ACCESS_KEY_ID`, og så videre. Bruk en annen bøtte og, ideelt, en annen konto enn filene, med legitimasjon som kan skrive men ikke slette hvis leverandøren tillater det.
- **Oppbevaring.** De nyeste `QUIRE_BACKUP_OFFSITE_KEEP`-basesikkerhetskopiene (standard `QUIRE_BACKUP_KEEP`, ellers 7) og WAL-en den eldste av dem trenger; eldre sett og segmenter slettes fra lageret.
- **Når.** Hver `QUIRE_BACKUP_SHIP_INTERVAL_SECONDS` (standard 300). Forsending er idempotent: det som allerede er lagret hoppes over, og en basesikkerhetskopi teller som lagret først når manifestet dens skrives, sist.

Samme kommando kjøres for hånd:

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

For å gjenopprette på en ny vert, ta først et sett tilbake, og følg deretter stegene nedenfor med den hentede katalogen i stedet for `pgbackup`-volumet og den hentede `wal-archive` i stedet for `pgwal`:

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

## Gjenopprette til et tidspunkt <!--quire:restoring-to-a-point-in-time-->

Bruk dette etter datatap: en dårlig import, et slettet kurs, en kontraktsmigrering du må omgjøre. Det erstatter den levende databasen, så øv på det med øvelsen nedenfor først.

1. **Velg måltidspunktet**, i UTC, like før skaden: `2026-09-24 09:30:00+00`. Revisjonsloggen (`/admin/audit`) viser vanligvis øyeblikket.
2. **Stopp alt som skriver**: `docker compose -f docker/compose.yaml stop web content worker scheduler collab`
3. **Behold den skadede klyngen** til gjenopprettingen er verifisert:
   ```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. **Pakk ut den nyeste basesikkerhetskopien eldre enn målet** inn i datavolumet, og be om målrettet gjenoppretting:
   ```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. **Gjenopprett**: start Postgres én gang med gjenopprettingsinnstillingene, fra en Compose-overstyring slik at normalfilen er urørt:
   ```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]
   ```
   Overstyringen erstatter hele kommandoen, så den gjentar de to innstillingene gjenoppretting avhenger av: `max_connections` ikke lavere enn primærens (ellers avbrytes gjenoppretting med «insufficient parameter settings») og den monterte `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. **Sjekk den** før du slipper inn noen: revisjonskjeden (`docker compose -f docker/compose.yaml run --rm worker bun tooling/audit-verify/run.ts`), og at de tapte dataene er tilbake.
7. **Tilbake til normalen**: `docker compose -f docker/compose.yaml up -d`. Dette starter Postgres på nytt med arkivering på, og en ny WAL-tidslinje begynner. Ta en fersk basesikkerhetskopi med én gang.

Prosjektnavnet `quire` prefikser hvert volum; `docker volume ls` viser de eksakte navnene.

## Den planlagte verifiseringsøvelsen <!--quire:the-scheduled-verification-drill-->

`backup-offsite` kjører også en øvelse hver `QUIRE_BACKUP_DRILL_INTERVAL_HOURS` (standard 168, ukentlig), og igjen ved neste pass etter at en feiler. Den henter den nyeste basesikkerhetskopien utenfor verten og alle WAL-segmenter etter den, dekrypterer hver (som beviser at nøkkelen fortsatt åpner dem og at ingenting ble endret), sammenligner hver fil med manifestet dens, sjekker at arkivet er en Postgres-datakatalog, og sjekker at WAL-en fra sikkerhetskopien og fremover ikke har noe gap. Rapporten skrives til lageret som `reports/drill-<time>.json` og til tjenestens logg; en mislykket øvelse navngir filen eller det første manglende segmentet.

## Gjenopprettingsøvelsen <!--quire:the-restore-drill-->

En sikkerhetskopi som aldri er gjenopprettet er ingen sikkerhetskopi. Øvelsen gjenoppretter den nyeste fullstendige basesikkerhetskopien tatt før målet, pluss WAL-arkivet, inn i en midlertidig Postgres som ikke deler noe med den levende, og beviser resultatet:

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

Målet er UTC i nøyaktig den formen. Det trenger en basesikkerhetskopi eldre enn det og arkivert WAL bortenfor det: på en ny installasjon, ta en basesikkerhetskopi og vent på neste arkiverte segment (høyst et minutt med skriving) før du velger et mål etter sikkerhetskopien. Øvelsen trenger Docker og bash på verten, ingenting annet.

Hvert steg feiler øvelsen:

1. **Gjenopprettbarhet**: den midlertidige klyngen spiller av til målet og åpner.
2. **Fullstendighet**: hver tabells radtelling mot den levende databasen (`tooling/restore-drill`). Den levende databasen har flyttet seg siden målet, så en tabell kan avvike med det største av 500 rader og en tidel av størrelsen dens, i begge retninger (skriving får den til å henge etter, slettinger får gjenopprettingen til å holde mer); en partisjon opprettet etter målet er ikke en tapt tabell. Vid ut tillatelsen på en travlere installasjon med `QUIRE_DRILL_MAX_BEHIND` og `QUIRE_DRILL_MAX_DRIFT_RATIO`. En manglende eller tømt tabell feiler.
3. **Integritet**: revisjonshashkjeden verifiseres på den gjenopprettede kopien.
4. **Brukbarhet**: applikasjonsrollen leser gjennom sikkerhet på radnivå.
5. **Tid**: start til grønt, mot `QUIRE_DRILL_RTO_SECONDS` (standard 3600).

Den skriver aldri til den levende databasen eller volumene dens: sikkerhetskopi- og WAL-volumene monteres skrivebeskyttet og den midlertidige klyngen fjernes til slutt, bestått eller ikke.

Sett `QUIRE_DRILL_REPORT` til en sti for å få en JSON-rapport skrevet, bestått eller ikke, og kjør den på en plan fra Docker-verten:

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

Kjør den månedlig og før hver oppgradering. En mislykket øvelse blokkerer oppgraderingen. Én gang i kvartalet, la noen som ikke skrev denne prosedyren utføre en virkelig punkt-i-tid-gjenoppretting på en reservevert, kun med dette dokumentet.

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

Lokale filer ligger i `files`-volumet. Sikkerhetskopier det med databasen, samtidig, og gjenopprett begge sammen:

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

Med objektlagring, slå på bøtteversjonering og behold 35 dager med ikke-gjeldende versjoner; punkt-i-tid-gjenoppretting for filer er da bøttens egen.

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