364 lines
15 KiB
Markdown
364 lines
15 KiB
Markdown
---
|
||
title: Stratégie de backup DAEMON
|
||
type: infra
|
||
created: 2026-04-18
|
||
updated: 2026-04-19
|
||
owner: jerem
|
||
status: ✅ déployé et opérationnel
|
||
tags:
|
||
- infra
|
||
- backup
|
||
- kopia
|
||
- security
|
||
related:
|
||
- "[[_adn/infra/vps-config-2026-04-17]]"
|
||
- "[[_adn/soul]]"
|
||
---
|
||
|
||
# Stratégie de backup DAEMON — 2026-04-18
|
||
|
||
> **État (2026-04-19)** : ✅ Déployé. 3 destinations actives (local VPS + Google Drive + NAS Ugreen). Premier run automatique via systemd timer prévu lundi 20 avril 03h00 Paris.
|
||
|
||
---
|
||
|
||
## 🎯 Objectif
|
||
|
||
Protéger les données critiques de l'écosystème DAEMON (vault Obsidian, data Notion, configs infrastructure) contre :
|
||
- Panne matérielle VPS
|
||
- Corruption / suppression accidentelle
|
||
- Compromission sécurité (ransomware, intrusion)
|
||
- Perte compte iCloud / Google
|
||
- Faillite / coupure d'un fournisseur
|
||
|
||
Règle appliquée : **3-2-1** (3 copies, 2 médias différents, 1 offsite minimum). En réalité on vise **3-3-2** : 3 destinations de backup, 3 médias distincts, 2 offsites.
|
||
|
||
---
|
||
|
||
## 📊 Ce qui est backupé (par criticité)
|
||
|
||
### Tier 1 — catastrophique si perdu (backup quotidien)
|
||
|
||
| Source | Contenu | Pourquoi critique |
|
||
|---|---|---|
|
||
| `/home/jerem/vault/` | Vault Obsidian (cerveau DAEMON) | Contient toute la mémoire, les règles, l'historique |
|
||
| `/home/jerem/notion-backup/` | Export Notion quotidien en Markdown | Clients, projets, habitudes, journaling |
|
||
|
||
### Tier 2 — reinstallable mais douloureux (backup hebdomadaire le dimanche)
|
||
|
||
| Source | Contenu |
|
||
|---|---|
|
||
| `/home/jerem/.openclaw/` | Config OpenClaw, credentials Google/Gmail |
|
||
| `/home/jerem/daemon-infra/` | Scripts, systemd units, config nginx/wireguard |
|
||
| `/etc/systemd/system/` | Services systemd |
|
||
| `/etc/nginx/` | Config reverse proxy |
|
||
| `/etc/wireguard/` | Tunnel VPN |
|
||
|
||
### Tier 3 — pas backupé
|
||
|
||
- Logs OpenClaw
|
||
- Caches, sessions
|
||
- Dossiers `/tmp/`, `/var/cache/`
|
||
- Anything ephemeral
|
||
|
||
### Non backupé volontairement
|
||
|
||
- **Doppler** : MFA activé, régénération possible en 4h. Risque accepté.
|
||
- **Bot tokens Slack/WhatsApp** : stockés dans Doppler, idem ci-dessus.
|
||
|
||
---
|
||
|
||
## 🗺️ Topologie
|
||
|
||
```
|
||
/home/jerem/vault (live VPS)
|
||
│
|
||
┌──────────────────────┼──────────────────────┐
|
||
│ │ │
|
||
▼ ▼ ▼
|
||
Kopia local Kopia NAS ami (WebDAV) Kopia Google Drive
|
||
/var/backups/ nasprodfab.duckdns gdrive jerem
|
||
7 jours rétention 5 ans rétention 5 ans rétention
|
||
│ │ │
|
||
└── restore rapide └── offsite physique └── offsite cloud
|
||
```
|
||
|
||
**Pas d'offsite** : Kopia local (même machine que la source).
|
||
**Offsite physique** : NAS Ugreen de Fab.
|
||
**Offsite cloud** : Google Drive (compte jerem, 5TB).
|
||
|
||
---
|
||
|
||
## 🔒 Sécurité
|
||
|
||
### Chiffrement
|
||
|
||
- **Côté client, avant transit** : Kopia AES-256-GCM
|
||
- Le NAS de Fab voit uniquement des blobs chiffrés illisibles
|
||
- Google Drive idem
|
||
|
||
### Gestion de la passphrase Kopia
|
||
|
||
La passphrase de chiffrement est stockée à **3 endroits** :
|
||
|
||
1. **VPS** : `/root/.kopia-password` (mode `600`, root-only)
|
||
2. **Bitwarden** : entrée "Kopia DAEMON backup passphrase"
|
||
3. **Papier** : dans le coffre physique de Jerem
|
||
|
||
**Si VPS détruit + Bitwarden perdu → backups restaurables via la copie papier.**
|
||
**Si tous les 3 sont perdus → backups illisibles. Risque accepté : probabilité extrêmement faible.**
|
||
|
||
### Secrets NAS (WebDAV)
|
||
|
||
Stockés dans **Doppler** `daemon-infra/prd` :
|
||
|
||
- `NAS_WEBDAV_URL`
|
||
- `NAS_WEBDAV_USER`
|
||
- `NAS_WEBDAV_PASS`
|
||
|
||
Injectés dans l'environnement Kopia au moment du backup par le script de démarrage, jamais écrits en clair sur disque.
|
||
|
||
### Secrets Google Drive
|
||
|
||
Utilise l'OAuth Google déjà configuré pour Calendar/Gmail. Refresh token dans Doppler : `GOOGLE_REFRESH_TOKEN`.
|
||
|
||
---
|
||
|
||
## ⏰ Planning
|
||
|
||
| Quand | Quoi | Où |
|
||
|---|---|---|
|
||
| Chaque nuit à **3h00 Paris** (heure VPS fixe, ne change jamais en voyage) | Snapshot Kopia Tier 1 + 2 | Local VPS + NAS + gdrive |
|
||
| Chaque dimanche **3h30** | Snapshot Tier 2 seul si rien de Tier 1 n'a tourné | Idem |
|
||
| Chaque lundi **4h00** | **Test de restore automatique** (extraction d'un snapshot random vers `/tmp`, comparaison hash) | Local |
|
||
|
||
**Pourquoi 3h Paris fixe** : le backup tourne sur le VPS (pas sur le Mac), donc l'heure physique de Jerem importe peu. 3h Paris = toujours pendant une période de faible activité chez lui, peu importe le fuseau.
|
||
|
||
---
|
||
|
||
## 📦 Rétention GFS (Grandfather-Father-Son)
|
||
|
||
Kopia applique automatiquement :
|
||
|
||
| Horizon | Snapshots gardés |
|
||
|---|---|
|
||
| 7 derniers jours | Tous (1 par jour) |
|
||
| 4 dernières semaines | 1 par semaine |
|
||
| 12 derniers mois | 1 par mois |
|
||
| 5 dernières années | 1 par an |
|
||
|
||
**Total théorique** : ~28 snapshots actifs à n'importe quel instant.
|
||
**Taille réelle** : grâce à la déduplication Kopia (chunks unique stockés une seule fois), ~150-300 MB pour le repo complet, pas 28× la taille du vault.
|
||
|
||
---
|
||
|
||
## 🔄 Pipeline de backup (nightly 3h)
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────────┐
|
||
│ 0. Pull secrets Doppler (NAS_*, GOOGLE_*) │
|
||
│ │
|
||
│ 1. Notion export │
|
||
│ ├─ notion-export → /home/jerem/notion-backup/*.md │
|
||
│ ├─ git commit + push (repo Gitea dédié) │
|
||
│ ├─ SI ÉCHEC : alerte Slack + continue (export J-1 dispo) │
|
||
│ └─ ping Kuma push monitor "notion-export-ok" │
|
||
│ │
|
||
│ 2. Kopia snapshot local │
|
||
│ └─ sources : vault, notion-backup, daemon-infra, │
|
||
│ .openclaw, /etc/* (hebdo seulement) │
|
||
│ │
|
||
│ 3. Kopia sync vers NAS ami (WebDAV) │
|
||
│ └─ SI ÉCHEC (NAS offline) : alerte + continue │
|
||
│ │
|
||
│ 4. Kopia sync vers Google Drive │
|
||
│ └─ SI ÉCHEC : alerte + continue │
|
||
│ │
|
||
│ 5. Kopia maintenance (GFS prune, compaction) │
|
||
│ │
|
||
│ 6. Ping Kuma "daemon-backup-ok" │
|
||
└──────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**Philosophie** : jamais d'arrêt brutal. Une étape qui rate alerte et laisse les autres continuer.
|
||
|
||
---
|
||
|
||
## 🚨 Monitoring et alertes
|
||
|
||
### Uptime Kuma Push Monitors
|
||
|
||
| Monitor | Seuil | Action si KO |
|
||
|---|---|---|
|
||
| `daemon-backup-ok` | Pas de ping en 25h | Alerte Slack (normal) |
|
||
| `notion-export-ok` | Pas de ping en 25h | J1 Slack normal · J2 Slack urgent + email · J3 appel téléphonique |
|
||
| `kopia-local-integrity` | Check intégrité hebdo | Slack urgent |
|
||
| `restore-test-ok` | Test restore hebdo | Slack normal si échec, urgent si 2× consécutif |
|
||
|
||
### Cascade d'escalade (conforme [[_adn/soul]] section 2)
|
||
|
||
| Palier | Canal | Niveau Kuma |
|
||
|---|---|---|
|
||
| Échec J1 | Slack DM | Normal |
|
||
| Échec J2 | Slack all devices ring + email | Urgent |
|
||
| Échec J3 | Appel téléphonique + tout ce qui précède | Très urgent |
|
||
|
||
---
|
||
|
||
## 🖥️ Interface Kopia UI
|
||
|
||
- **URL** : `https://kopia.daemon.jeremunlimited.com`
|
||
- **Auth** : basic auth + token header (pattern identique à Control UI OpenClaw)
|
||
- **Headers** : `X-Robots-Tag: noindex`
|
||
- **Accès** : public HTTPS ou LAN via WireGuard
|
||
- **Permissions** : lecture + restore uniquement (création snapshots bloquée en UI, seul le cron peut créer)
|
||
|
||
Utilisations :
|
||
- Voir les snapshots passés et leur taille
|
||
- Restaurer un fichier en 2 clics
|
||
- Vérifier l'intégrité d'un snapshot
|
||
- Monitorer l'espace disque des repos
|
||
|
||
---
|
||
|
||
## 🛠️ Commandes utiles (CLI)
|
||
|
||
```bash
|
||
# Lister les snapshots locaux
|
||
kopia snapshot list
|
||
|
||
# Restaurer la dernière version du vault
|
||
kopia snapshot restore latest /tmp/restore-vault
|
||
|
||
# Restaurer un fichier spécifique d'un snapshot donné
|
||
kopia snapshot restore <snapshot-id>/path/to/file /tmp/restored-file
|
||
|
||
# Vérifier l'intégrité
|
||
kopia snapshot verify
|
||
|
||
# Voir l'espace utilisé
|
||
kopia content stats
|
||
|
||
# Purger manuellement (suit la policy GFS)
|
||
kopia policy set --keep-latest 7 --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --keep-annual 5
|
||
|
||
# Forcer un backup immédiat
|
||
sudo systemctl start daemon-backup.service
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Plan de restauration (runbook)
|
||
|
||
### Scénario 1 : j'ai supprimé un fichier du vault
|
||
|
||
1. Ouvrir l'UI Kopia
|
||
2. Naviguer dans le dernier snapshot → `vault/`
|
||
3. Clic droit sur le fichier → Restore → `/home/jerem/vault/`
|
||
4. `git add` + commit dans le vault
|
||
|
||
### Scénario 2 : le VPS est HS, il faut tout reconstruire
|
||
|
||
1. Déployer un nouveau VPS (Hetzner, même config)
|
||
2. Installer Kopia
|
||
3. Restaurer la passphrase depuis Bitwarden (ou papier)
|
||
4. Connecter Kopia au repo Google Drive (plus rapide que NAS distant)
|
||
```bash
|
||
kopia repository connect gdrive \
|
||
--folder-id $FOLDER_ID \
|
||
--credentials-file /path/to/oauth.json
|
||
```
|
||
5. `kopia snapshot restore latest --target /`
|
||
6. Reconfigurer secrets (régénérer OAuth Google, Doppler tokens)
|
||
7. Relancer systemd : `systemctl start openclaw`
|
||
|
||
**Temps cible de restauration complète** : < 4h (avec config nginx + wireguard + openclaw à reconfigurer).
|
||
|
||
### Scénario 3 : compromission sécurité, besoin de rollback
|
||
|
||
1. Identifier la date de compromission depuis les logs
|
||
2. Restaurer le snapshot de la veille (avant compromission)
|
||
3. `kopia snapshot restore <snapshot-pre-compromission> --target /home/jerem/`
|
||
4. Rotation de tous les secrets (Doppler → regenerate)
|
||
5. Invalider tous les OAuth Google / Slack / Notion tokens
|
||
6. Recréer depuis zéro les tokens
|
||
|
||
---
|
||
|
||
## 📐 Dimensionnement estimé
|
||
|
||
| Donnée | Taille actuelle | Taille dans 5 ans (×10) |
|
||
|---|---|---|
|
||
| Vault Obsidian | ~10 Mo | ~100 Mo |
|
||
| Notion export mirror | ~50 Mo | ~500 Mo |
|
||
| daemon-infra | ~5 Mo | ~50 Mo |
|
||
| .openclaw config | ~2 Mo | ~20 Mo |
|
||
| /etc/* (tier 2) | ~10 Mo | ~10 Mo (stable) |
|
||
| **Total source** | **~77 Mo** | **~680 Mo** |
|
||
| **Total repo Kopia (28 snapshots + dedup + compression)** | **~200 Mo** | **~1.5 Go** |
|
||
|
||
**Quota à demander à Fab** : 10 Go (avec marge confortable).
|
||
**Usage Google Drive** : 10 Go sur 5 TB (0.2 %).
|
||
**Usage VPS local** : ~200 Mo (7 jours de rétention, largement acceptable).
|
||
|
||
---
|
||
|
||
## ✅ Déploiement effectué (2026-04-19)
|
||
|
||
- [x] **Validation Fab** : dossier `Jerem Unlimited Création de contenus/DAEMON-Backup` créé
|
||
- [x] **Passphrase Kopia** : générée + stockée Doppler (`KOPIA_PASSPHRASE`) + Bitwarden + papier
|
||
- [x] **Secrets NAS Doppler** : `NAS_WEBDAV_URL`, `NAS_WEBDAV_USER`, `NAS_WEBDAV_PASS` (rclone-obscured)
|
||
- [x] **OAuth Google unifié** : nouveau client Web (Calendar + Gmail + Drive scopes)
|
||
- [x] **Repo Kopia local** : `/var/backups/kopia-local` actif
|
||
- [x] **Sync Kopia → gdrive** : `gdrive:DAEMON-Backups/kopia` actif
|
||
- [x] **Sync Kopia → NAS** : `nas:Jerem Unlimited Création de contenus/DAEMON-Backup/kopia` actif
|
||
- [x] **Script** : `/home/jerem/daemon-infra/scripts/daemon-backup.sh`
|
||
- [x] **Systemd timer** : `daemon-backup.timer` (cron 03:00 Europe/Paris, persistent=true)
|
||
- [x] **Kopia UI** : `https://kopia.jeremunlimited.com` (basicAuth + noindex via Traefik/Coolify)
|
||
- [x] **Service systemd Kopia** : `kopia-server.service` (port 51515 sur 0.0.0.0, UFW restreint à 10.0.0.0/8)
|
||
- [x] **Monitoring Kuma** : Push Monitor `DAEMON Backup`, URL dans Doppler `KUMA_BACKUP_PUSH_URL`
|
||
- [x] **Sudoers rsync** : `/etc/sudoers.d/daemon-backup-rsync` (jerem peut rsync /etc/* sans password)
|
||
|
||
## 🔜 À faire ensuite
|
||
|
||
- [ ] **Test de restore réel** sur VPS blank (exercice trimestriel — premier exo Q3 2026)
|
||
- [ ] **Notion export** (Phase 2.6) : intégration script Python notion-sdk-py + commit Gitea `daemon-notion-mirror`
|
||
- [ ] **Monitoring sync vault Obsidian** (vérifier Git push toutes les minutes ne rate pas)
|
||
|
||
## 🔧 Notes techniques de déploiement
|
||
|
||
**TLS NAS** : le certificat NAS est auto-signé (duckdns). On utilise `--rclone-args=--no-check-certificate` côté Kopia + `no_check_certificate = true` dans `~/.config/rclone/rclone.conf`. Acceptable car Kopia chiffre AES-256-GCM côté client AVANT transit — TLS n'est qu'une couche additionnelle de protection contre la modification.
|
||
|
||
**Préservation rclone.conf** : le script `daemon-backup.sh` reconstruit la section `[gdrive]` à chaque run (avec un access_token frais), mais préserve les autres sections (notamment `[nas]`) via un parser awk dans le script.
|
||
|
||
**Permissions /etc/*** : impossible de snapshoter `/etc/{systemd,nginx,wireguard}` directement comme `jerem` (root-owned + secrets wireguard `chmod 600`). Solution : staging via `rsync --chown=jerem:jerem` vers `/var/backups/etc-staging/` avant snapshot Kopia. Sudoers granulaire pour autoriser uniquement ce rsync sans password.
|
||
|
||
**Docker networking** : Coolify-proxy (Traefik) tourne dans Docker sur le réseau `coolify` (10.0.1.0/24). Pour qu'il puisse joindre Kopia server bare-metal, on bind sur `0.0.0.0:51515` puis on filtre via UFW : `allow from 10.0.0.0/8 to any port 51515` (même pattern que OpenClaw 18789).
|
||
|
||
**Mot de passe NAS exposé** : durant le debug, j'ai dû lire `~/.config/rclone/rclone.conf` du Mac de Jerem pour identifier un mismatch avec Doppler. Le mot de passe en clair (`Jerem2026`) a été vu. Décision Jerem : pas de rotation (mot de passe partagé Fab existant, accessible via iCloud Mac).
|
||
|
||
---
|
||
|
||
## 📚 Alternatives considérées (historique)
|
||
|
||
| Option | Rejetée car |
|
||
|---|---|
|
||
| **Obsidian Sync officiel** | Payant, vendor lock-in, ne couvre pas Notion |
|
||
| **Backblaze B2** | Jerem a le NAS ami gratuit, pas besoin de cloud payant |
|
||
| **iCloud pour backup serveur** | Pas d'API serveur native, besoin d'un Mac allumé |
|
||
| **Restic** (vs Kopia) | Pas d'UI web — Jerem préfère avoir le visuel |
|
||
| **Borg** (vs Kopia) | Pas de backend WebDAV natif, moins polyvalent |
|
||
| **Notion → incremental seulement** | Aucun outil mature, complexité de code non justifiée vu que Kopia dedupe déjà |
|
||
|
||
---
|
||
|
||
## 🔗 Références
|
||
|
||
- [Kopia documentation](https://kopia.io/docs/)
|
||
- [Règle 3-2-1 (US-CERT)](https://www.cisa.gov/sites/default/files/publications/data_backup_options.pdf)
|
||
- [SOUL.md section alertes](/_adn/soul.md#2-règles-cardinales-non-negociables)
|
||
- Doc config VPS : [[_adn/infra/vps-config-2026-04-17]]
|
||
|
||
---
|
||
|
||
*Document vivant, à mettre à jour à chaque évolution de la stratégie.*
|