Documentatie

Hoe backup.noaber.ai werkt

Centrale monitoring voor backup-jobs over Noaber.ai-eigen systemen én klant-stacks. Doel: 's nachts schrijven alle scripts hun status hierheen, 's ochtends één Telegram-samenvatting. Verder via deze pagina kunnen we per klant een eigen dashboard tonen met alleen hun eigen scope.

Het idee in één plaatje

backup-job (Cloudflare cron, plex VPS, LaunchAgent, NUC LXC, …)
    │
    │  HTTPS POST  →  https://backup.noaber.ai/api/report
    │  Header: X-Backup-Token: bh_xxxxxxxxxx
    │  Body  : { system_key, status, duration_ms, rows_count, size_bytes, … }
    ▼
backup.noaber.ai  (Cloudflare Worker + D1 + KV)
    │
    ├─ Live dashboard (passkey-gated): per systeem, status, history
    ├─ /api/events, /api/systems, /api/summary (JSON voor automatisering)
    └─ Cron 06:00 → 1 Telegram-samenvatting (failures + ok-overzicht + missing)

Wat is een "systeem"?

Een systeem is één discreet backup-mechanisme. Voorbeelden in de Noaber-stack:

system_keyCategorieLocatieFrequentie
topviewcf-cronCloudflare Workerelke 24u
lurvinkcf-cronCloudflare Workerelke 24u
bubbelbreincf-cronCloudflare Workerelke 24u
padelclippycf-cronCloudflare Workerelke 24u
cashspotcf-cronCloudflare Workerelke 24u
paperless-r2plex-cronPlex VPS (LXC)elke 24u
topview-nucnuc-cronNUC LXC 103elke 24u
restic-mbp-minilaunchdMBP → Mac mini SFTPelke 24u
restic-mbp-r2launchdMBP → R2 (weekly)elke 168u
vaultwarden-r2launchdMBP → R2elke 24u

Het verwachte payload-formaat

POST naar /api/report met header X-Backup-Token: bh_xxxxxxxxxxxxxxxx (of Authorization: Bearer bh_...). Body:

{
  "system_key": "bubbelbrein",
  "status": "ok",
  "category": "cf-cron",
  "started_at": 1747094400000,
  "completed_at": 1747094403127,
  "duration_ms": 3127,
  "rows_count": 22057,
  "size_bytes": 7093500,
  "details": { "tables": 21, "files_present": "1/1" },
  "source": "cf-cron"
}

Status moet ok, failed, warning, of in_progress zijn. Andere velden zijn optioneel maar verhogen wel de waarde van het dashboard. details mag JSON-object of string zijn.

Onboarden van een nieuwe klant of systeem

1. Token uitgeven

Ga naar /admin/tokens → maak een token aan met scope (bv. noaber, bubbelbal, lurvink) en omschrijving. Token wordt eenmalig getoond → bewaar in Vaultwarden + in de script-secrets.

2. Backup-script aanpassen

Vervang bestaande Telegram-notify door een POST naar /api/report. Failures mogen ook nog direct naar Telegram (urgent), succes hoeft niet meer (komt 's ochtends in samenvatting).

Voorbeeld bash-snippet:

BACKUP_TOKEN="bh_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
COLLECTOR="https://backup.noaber.ai/api/report"

curl -fsS -X POST "$COLLECTOR" \
  -H "X-Backup-Token: $BACKUP_TOKEN" \
  -H "Content-Type: application/json" \
  --data "{
    \"system_key\": \"paperless-r2\",
    \"status\": \"ok\",
    \"duration_ms\": $DURATION,
    \"size_bytes\": $SIZE_BYTES,
    \"details\": { \"file\": \"$BASENAME\" }
  }" >/dev/null

Cloudflare Worker (cf-cron) snippet:

await fetch('https://backup.noaber.ai/api/report', {
  method: 'POST',
  headers: {
    'X-Backup-Token': env.BACKUP_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    system_key: 'bubbelbrein',
    status: 'ok',
    duration_ms: Date.now() - startedAt,
    rows_count: totalRows,
    size_bytes: totalBytes,
    details: { tables: tableCount },
    source: 'cf-cron',
  }),
})

3. Systeem registreren (optioneel)

Voor het dashboard kun je een systeem vooraf registreren zodat het ook zichtbaar is voordat het eerste event binnenkomt. Eerste event registreert het systeem automatisch (met display_name = system_key); via POST /admin/systems kun je naam/beschrijving/frequentie opgeven.

Beveiliging

  • Dashboard achter passkey-login (WebAuthn). Geen wachtwoorden. Sessies via opaque cookie + KV-store, 30 dagen TTL.
  • Ingestion via shared secret per scope. Token wordt SHA-256 + pepper gehasht in D1; nooit plain opgeslagen. Per token een eigen scope — backup-script kan niet over scope-grens heen schrijven.
  • Token revoken via /admin/tokens. Backup-script raakt onmiddellijk 401.
  • Bootstrap-token voor eerste admin staat als secret in de Worker — gebruikt voor enrollment van de eerste passkey, daarna kan deze worden gerouleerd (wrangler secret put BOOTSTRAP_TOKEN) of gewoon nooit meer gebruikt.

Daily summary

Cron-trigger: 0 4 * * * (04:00 UTC = 06:00 CEST). Output: 1 Telegram-bericht aan SpotBot (chat 467689725) met:

  • Failures (events in 24h met status failed of warning)
  • Missing (systemen met verwachte frequentie ≤ 24u die geen event hadden)
  • OK (laatste succes per systeem, met rows/size/duur)
  • Link naar dit dashboard voor diepere details

Failures kunnen daarnaast direct doorkomen vanuit de scripts zelf (snellere reactie); het ochtend-rapport is voor totaal-overzicht.

LXC / backup-mechanisme definitie

Voor systemen die op een LXC of VPS draaien, hanteren we deze conventie zodat de scripts onderling consistent zijn:

  1. Source folder: /mnt/docker/<app>/ op de host (bv. /mnt/docker/paperless/).
  2. Backup folder: /mnt/docker/backups/<app>/, retention 30 dagen lokaal.
  3. Backup-scripts: /mnt/docker/scripts/backup-<app>.sh (lokale tar), sync-<app>-nas.sh (Synology rsync), backup-<app>-r2.sh (offsite encrypted).
  4. Encryption voor offsite: GPG symmetric AES256, passphrase in /root/.config/<app>-r2-passphrase (chmod 600), mirror in Vaultwarden.
  5. R2 buckets: noaber-<app>-backups (WEUR), scoped S3-tokens met alleen Object Read & Write op die bucket.
  6. Cron-schedule: lokaal 03:30 · NAS 03:45 · R2 04:15. Drie windows zodat eerst-fase moet slagen voor twee-fase begint.
  7. Reporting: na de R2-upload (laag 3) doet het script één POST /api/report met succes-status + size + duur. Failure-pad: meteen POST met status failed + details + nog steeds Telegram-alert direct.

Klant-specifiek dashboard (later)

Het schema ondersteunt al per-scope filtering. Voor een klant-dashboard kan op subdomein backup.<klant>.nl dezelfde Worker draaien met een query-default ?scope=<klant> en alleen klant-tokens accepteren. Niet nu nodig, schema is klaar.