server-up/docs/backups.md
Ramon 87fb9d19d1
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m22s
v0.7.50-beta - meldingen, schijfruimte, backups controleren en extern wegzetten
Het onbewaakte deel was de zwakke plek: geplande taken draaiden 's nachts en
een storing kwam alleen in het auditlog terecht.

Meldingen (core/notify.py):
- ntfy, webhook (Discord/Slack/Gotify) en e-mail, alle drie met de
  standaardbibliotheek.
- Gebeurtenissen: mislukte backup, onleesbaar archief, vastgelopen taak,
  weinig schijfruimte, beschikbare update, gestopte container.
- Eén bericht per ronde; bij schijfruimte en gestopte containers alleen bij
  de overgang, zodat je niet elk uur hetzelfde krijgt.
- Testknop die eerst opslaat, zodat je test wat je net hebt ingevuld.
- send() gooit nooit: het kanaal mag de taak die de melding veroorzaakte niet
  alsnog laten omvallen.

Schijfruimte (core/diskspace.py):
- Controle vóór elke backup; past het niet, dan weigeren in plaats van
  halverwege afbreken.
- DISK_MIN_FREE_GB blijft gereserveerd, DISK_WARN_PCT kleurt de balk rood.
- Per filesystem één regel in de backuplijst.

Backups controleren:
- Elk nieuw archief wordt helemaal uitgelezen (tar-structuur plus
  gzip-checksum, die aan het eind staat).
- Diepe variant pakt echt uit naar een tijdelijke map, met dezelfde
  beperkingen als een echt herstel.
- Resultaat staat in de metadata en als schildje in de lijst.

Backups de deur uit:
- Downloadknop.
- BACKUP_OFFSITE_DIR kopieert elke nieuwe backup naar een gemounte schijf,
  NFS- of SMB-share, via .part zodat een afgebroken kopie herkenbaar onaf is.
- Ruimtecontrole op de bestemming, want een weggevallen mount laat vaak een
  lege map op de systeemschijf achter.

NOTIFY_TOKEN en NOTIFY_EMAIL_PASSWORD zijn write-only.

Nieuw: docs/meldingen.md; docs/backups.md uitgebreid. Getest tegen een
draaiende server met een echte ontvanger. 1064 tests groen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-28 19:41:35 +02:00

213 lines
8 KiB
Markdown

# Backups
Server Up maakt backups van je stacks: handmatig, automatisch vóór risicovolle
acties, of op een schema. Terugzetten kan met één klik.
---
## ⚠️ Lees dit eerst: wat zit er wél en niet in
Een backup is een `.tar.gz` van de **stackmap** in je library. Daar zitten in:
- het compose-bestand
- het `.env`-bestand (met je wachtwoorden en tokens)
- eventuele configuratiebestanden die naast de compose staan
- `.serverup.json` met de metadata van de installatie
Wat er **niet** in zit: de Docker-volumes met de eigenlijke gegevens van de app.
| Voorbeeld | In de backup? |
|---|---|
| De compose van Vaultwarden | ✅ |
| Je `ADMIN_TOKEN` uit `.env` | ✅ |
| De wachtwoordkluis van Vaultwarden zelf | ❌ |
| De database van Paperless | ❌ |
| Je foto's in Immich | ❌ |
Je kunt hiermee dus een stack terugzetten zoals hij geconfigureerd stond, maar
niet de inhoud ervan herstellen. Wil je dat ook, dan zijn er twee routes:
1. **Bind mounts in plaats van named volumes.** Staat je data in
`/opt/serverup/appdata/<app>` en zit die map binnen je stackmap, dan gaat hij
wél mee. De meeste templates in `apps/` gebruiken `data_dir` daarvoor.
2. **Een aparte volume-backup** met bijvoorbeeld `restic` of `borg` naast Server
Up. Voor grote datasets is dat sowieso beter dan een tar per keer.
> Omdat een backup je `.env` bevat, staan er wachtwoorden in. `BACKUP_DIR` hoort
> dus dezelfde bescherming te hebben als de rest van `/data`.
---
## Een backup maken
**Per stack** — de archiefknop op de stackkaart maakt er direct een. De
geschiedenisknop ernaast toont alle backups van die stack, met de mogelijkheid
om terug te zetten of te verwijderen.
**Automatisch vóór risicovolle acties** — standaard aan voor:
- **bijwerken** van een stack (`compose pull` + `up`), want een nieuwe
image-versie kan een compose-wijziging nodig hebben;
- **verwijderen** van een stack, maar alleen als je "verwijder ook de bestanden"
kiest — bij het enkel stoppen van containers valt er niets te verliezen.
Uit te zetten onder **Instellingen → Backups**. Mislukt zo'n automatische
backup, dan gaat de actie alsnog door: anders zou je een kapotte stack niet meer
kunnen opruimen. De melding komt wel in het joblog.
**Gepland** — dagelijks of wekelijks, rond een uur dat je zelf kiest. De
planner draait in de applicatie zelf (geen cron nodig) en onthoudt in
`config.json` wanneer hij voor het laatst gelopen heeft. Stond de server uit op
het geplande moment, dan loopt de backup bij de eerstvolgende gelegenheid alsnog.
---
## Bewaarbeleid
Twee regels, die allebei tegelijk gelden:
| Instelling | Betekenis |
|---|---|
| `BACKUP_KEEP` | Hoeveel backups per stack je bewaart. `0` = onbeperkt. |
| `BACKUP_MAX_AGE_DAYS` | Alles ouder dan dit aantal dagen gaat weg. `0` = geen limiet. |
**De nieuwste backup van een stack blijft altijd staan**, ook als hij ouder is
dan de maximale leeftijd. Anders sta je na een lange vakantie met lege handen.
Opruimen gebeurt na elke nieuwe backup van die stack, en na een geplande ronde.
---
## Terugzetten
De geschiedenisknop op de stackkaart → **Terugzetten**. Wat er dan gebeurt:
```
1. docker compose down stack stoppen
2. huidige map opzij zetten als <stack>.restore-<tijdstempel>
3. archief uitpakken
4. opzijgezette map weggooien
5. docker compose up -d stack starten
```
Gaat stap 3 mis — een beschadigd archief, of een archief dat buiten de stackmap
probeert te schrijven — dan wordt de opzijgezette map teruggeplaatst. Je raakt
je stack dus niet kwijt aan een kapotte backup.
Een backup van stack A kun je niet over stack B heen zetten; dat wordt geweigerd
op basis van de metadata.
### Veiligheid
Archieven worden uitgepakt met Pythons `tarfile` en `filter="data"`. Dat weigert
absolute paden, `..`-ingangen, symlinks die buiten de map wijzen en
apparaatbestanden. Met een kaal `tar xzf` zou een geprepareerd archief bestanden
elders op de host kunnen neerzetten.
---
## Instellingen
| Sleutel | Standaard | Betekenis |
|---|---|---|
| `BACKUP_DIR` | `/opt/serverup/backups` | Waar de archieven komen |
| `BACKUP_KEEP` | `5` | Aantal per stack |
| `BACKUP_MAX_AGE_DAYS` | `0` | Maximale leeftijd in dagen |
| `BACKUP_SCHEDULE` | `off` | `off` / `daily` / `weekly` |
| `BACKUP_SCHEDULE_HOUR` | `3` | Rond welk uur de geplande ronde draait |
| `BACKUP_BEFORE_UPDATE` | `true` | Backup vóór het bijwerken van een stack |
| `BACKUP_BEFORE_REMOVE` | `true` | Backup vóór het verwijderen van bestanden |
| `BACKUP_VERIFY` | `true` | Elk nieuw archief meteen controleren |
| `BACKUP_OFFSITE_DIR` | leeg | Tweede bestemming, bijvoorbeeld een gemounte schijf |
| `DISK_MIN_FREE_GB` | `2` | Reserve die vrij moet blijven |
| `DISK_WARN_PCT` | `10` | Onder dit percentage waarschuwt de interface |
---
## Controleren
Een backup die je nooit leest is een aanname. Server Up leest daarom elk nieuw
archief meteen helemaal uit: dat controleert de tar-structuur én de
gzip-checksum. Die checksum staat aan het eind, dus een archief dat halverwege
is afgebroken valt hier door de mand terwijl het in een lijst compleet lijkt.
Mislukt dat, dan meldt de backup zich als fout — het archief blijft wel staan,
zodat je kunt zien wat er mis is.
In de backuplijst staat achter elk archief een schildje: groen als het gelezen
is, rood met de foutmelding als het beschadigd is.
Met de knop **Controleren** doe je het handmatig over. Wil je zekerder weten dat
terugzetten écht werkt, gebruik dan de diepe variant (`{"deep": true}` op
`/api/backups/<bestand>/verify`): die pakt het archief uit naar een tijdelijke
map met dezelfde beperkingen als een echt herstel, en ruimt die daarna op.
Uitzetten kan met `BACKUP_VERIFY`; bij hele grote stacks kost het merkbaar tijd.
---
## Schijfruimte
Vóór elke backup wordt gekeken of het er redelijkerwijs in past. De schatting is
de ongecomprimeerde grootte van de stackmap — bewust aan de veilige kant, want
gzip maakt het kleiner. Past het niet, dan wordt de backup **geweigerd** in
plaats van halverwege afgebroken: een half archief ziet er in een lijst compleet
uit.
Daarbovenop blijft `DISK_MIN_FREE_GB` gereserveerd. Een volle schijf op een
Docker-host breekt alles, niet alleen Server Up.
Zakt een schijf onder `DISK_WARN_PCT`, dan waarschuwt de interface en — als je
meldingen hebt ingesteld — krijg je bericht. Dat gebeurt eenmalig bij de
overgang, niet elke ronde opnieuw.
---
## Een tweede bestemming
Backups staan standaard op dezelfde schijf als de data die ze moeten
beschermen. Gaat die schijf stuk, dan ben je allebei kwijt. Zet daarom
`BACKUP_OFFSITE_DIR` op een absoluut pad naar een **gemounte schijf, NFS- of
SMB-share**:
```bash
BACKUP_OFFSITE_DIR=/mnt/nas/serverup-backups
```
Elke nieuwe backup wordt daar naartoe gekopieerd, archief én metadata. Een
bestaande backup kopieer je alsnog met de wolkknop in de lijst.
Het kopiëren gaat eerst onder de naam `.part` en wordt daarna hernoemd, zodat
een afgebroken kopie herkenbaar onaf blijft. Valt de mount weg, dan bestaat het
pad vaak nog als lege map op je systeemschijf — daarom wordt eerst gecontroleerd
of er ruimte is, zodat je niet ongemerkt de verkeerde schijf vult.
> Dit is bewust een gewone mapkopie: dat werkt met USB, NAS en netwerkschijven
> zonder extra software en zonder dat wij een wachtwoord hoeven te bewaren.
> Wil je het echt buiten de deur, mount dan een externe opslag of laat een
> `rsync`-taak van je NAS de map ophalen.
---
## Downloaden
Met de downloadknop haal je een archief naar je eigen apparaat. Dat is de
snelste manier om een kopie te hebben die niets met deze server te maken heeft.
---
## Handmatig
De archieven zijn gewone tarballs — je hebt Server Up niet nodig om erbij te
kunnen:
```bash
# Bekijken wat erin zit
tar tzf /opt/serverup/backups/vaultwarden_20260726_030000.tar.gz
# Ergens anders uitpakken
tar xzf /opt/serverup/backups/vaultwarden_20260726_030000.tar.gz -C /tmp/herstel
```
Naast elk archief staat een `.json` met de stacknaam, het tijdstip, de grootte
en de reden (handmatig, gepland, voor-update, voor-remove). Ontbreekt dat
bestand, dan leidt Server Up de gegevens af uit de bestandsnaam.