Migration G9 — Kit d'exécution
Objectif : tout le matériel (scripts + configs + compose) prêt à exécuter la migration
xpenology → G9, phase par phase, quand le G9 sera branché (IP temporaire .24).
Règle d'or : chaque script est idempotent et sécurisé. Les opérations destructives (partitionnement, LUKS, wipe) sont bloquées sans le drapeau
--i-understandet la validation explicite de l'utilisateur (garde-fou G3).
Charte d'autonomie (garde-fous G1–G10)
| # | Garde-fou |
|---|---|
| G1 | Lecture seule libre ; toute modif délibérée et documentée. |
| G2 | Aucune modif de données sans backup vérifié en place. |
| G3 | Op. destructives (rm -rf, mkfs, wipe, dd, partition, RAID/LUKS, suppression conteneur) = validation explicite avant exécution. |
| G4 | Double-run : xpenology = source de vérité ; aucune suppression dessus tant que le contrepartie G9 n'est pas validé. |
| G5 | IP G9 = .24 tant que le xpenology n'est pas décommissionné. |
| G6 | Aucun changement réseau impactant d'autres appareils sans validation. |
| G7 | Chaque modif a un rollback documenté. |
| G8 | Journalisation de chaque action (quoi, pourquoi, résultat). |
| G9 | Aucun secret loggué ou commité. |
| G10 | Périmètre = G9 (+ lecture xpenology) ; autres machines sur validation. |
Correspondance garde-fou → point d'application
| Garde-fou | Point d'application |
|---|---|
| G1 | Scripts idempotents, en-têtes documentant chaque modification. |
| G2 | 06-restic-backup.sh : backup + test de restauration avant toute migration. |
| G3 | 06b-usb-media.sh : drapeau --i-understand + saisie du nom du disque. 08-decommission.sh : saisie ETEINDRE XPENOLOGY — aucun wipe exécuté par le kit (décision 12/10 : débrancher, disques intacts, récupération RESTORE-NAS-USB.md). |
| G4 | 07a–07d (+ 07-migrate-stateful.sh générique) : lecture seule du xpenology + intégrité sha256 ; 08-decommission.sh exige la validation de toutes les briques avant wipe. |
| G5 | README (ordre d'exécution) + 08-decommission.sh : IP .24→.25 uniquement au décommissionnement. |
| G6 | 01-hardening.sh : ufw LAN-only (192.168.1.0/24), pas d'exposition WAN. |
| G7 | Chaque script : section Rollback en en-tête + colonne Rollback du journal. |
| G8 | journal() de chaque script → journal.md (6 colonnes : date, phase, action, raison, résultat, rollback). |
| G9 | .gitignore (*.env, secrets/, configs remplis) + 03b-secrets.sh (chmod 600, non loggué). |
| G10 | Scripts ne modifient que le G9 ; xpenology en lecture seule (SSH Master@192.168.1.25:222, clé SSH — compte effi bloqué par Synology 10/2026). |
| G11 | Autonomie de l'assistante IA par paliers (décision 12/10) : L0 lecture seule → L1 propose, l'humain exécute → L2 exécute les actions stateless documentées des RUNBOOKS/ → L3 actions stateful uniquement sur GO explicite de l'utilisateur. Portes : L1→L2 = 20 propositions journalisées dont ≥ 90 % pertinentes sur 30 jours ; L2→L3 = sinistre simulé sur une brique stateful sans perte de données + rollback vérifié. Interdits permanents : opérations destructives (G3), suppression de données, modification réseau impactant d'autres appareils (G6), wipe, création de compte ou de privilège, lecture de secrets hors coffre. Traçabilité : chaque action de l'IA = ligne de journal (quoi, pourquoi, résultat, rollback) + notification dans le salon ops. Aucune exécution L2/L3 sans validation utilisateur tracée. |
Arborescence
g9-migration/
├── README.md ← ce fichier (runbook)
├── journal.md ← journal d'exécution (G8) — à remplir pendant la migration
├── scripts/
│ ├── 01-hardening.sh ← T2.1 : SSH keys, ufw (LAN-only), unattended-upgrades, fail2ban
│ ├── 02-data.sh ← T2.2 : espace /data (NON destructif — LUKS abandonné 10/10/2026)
│ ├── 03-docker.sh ← T2.3 : Docker + compose + groupe docker
│ ├── 03b-secrets.sh ← T2.3b : bootstrap des fichiers secrets des briques (chmod 600)
│ ├── 04-smb.sh ← T2.4 : partage SMB (données famille)
│ ├── 05-beszel.sh ← T2.5 : agent Beszel
│ ├── 06-restic-backup.sh ← T3.1 : restic G9 → Freebox + test restauration
│ ├── 06b-usb-media.sh ← T6.1 : montage des disques USB média → /mnt/usb-media
│ ├── 06c-postboot-check.sh ← contrôles post-reboot (lecture seule, 15+ checks)
│ ├── 07-migrate-stateful.sh← T4.x : générique (rsync) — INOPÉRANT sur le DSM : sshd
│ │ refuse `rsync --server` et scp → utiliser 07a–07d
│ ├── 07a-migrate-gitea.sh ← T4.4 : Gitea + backend Postgres (transport tar sur SSH)
│ ├── 07b-migrate-vaultwarden.sh ← T4.1 : Vaultwarden (coffre + 2FA + ADMIN_TOKEN)
│ ├── 07c-migrate-nextcloud.sh ← T4.2 : Nextcloud + MariaDB + Collabora
│ ├── 07d-migrate-photoprism.sh ← T4.3 : PhotoPrism (copie 530 Go + indexation)
│ ├── 07e-deploy-edge.sh ← T4.6 : edge Caddy (validate → up → ufw 443 → vérifs HTTPS/SNI)
│ ├── restic-notify.sh ← alerte Matrix des jobs restic (posée par 06)
│ ├── 09-j5040-dhcp.sh ← E3 : DHCP dnsmasq sur J5040 (pré-bascule bridge)
│ ├── 10-verify-restore-photo.sh ← critère 3 : preuve restore photo (sha256 vs .25)
│ ├── 11-registry.sh ← T-A.1/T-A.5 : génère REGISTRE-BRIQUES.md + --check --alert (dérive)
│ ├── registry_parse.py ← helper lecture seule de 11-registry.sh (docker ps → lignes + constats)
│ ├── registry_inputs.py ← helper : compose du kit + Caddyfile → declarations & endpoints
│ ├── 12-probes.sh ← T-C.1/C.2/C.5 : sondes APPLICATIVES + alertes JSON + digest (cron G9)
│ ├── g9-notify.py ← poste un payload JSON dans le salon ops (install /usr/local/bin)
│ ├── g9-verify-alert.py ← vérifie la conformité des payloads JSON reçus (C-AP2)
│ ├── lib-notify.sh ← côté pilot : notifie via g9-notify.py (aucun secret dans le kit)
│ └── 08-decommission.sh ← T6.4 : arrêt + débranchement xpenology [0 wipe — disques intacts]
├── compose/ ← docker-compose.yml versionnés (stockés aussi dans Gitea)
│ ├── vaultwarden/ ← T4.1
│ ├── nextcloud/ ← T4.2
│ ├── photoprism/ ← T4.3
│ ├── gitea/ ← T4.4
│ ├── n8n/ ← T4.5
│ ├── jellyfin/ ← T4.5 (média par référence, /media:ro)
│ ├── edge/ ← T4.6 : Caddy G9 (CA interne, edge transitoire)
│ ├── edge-j5040/ ← E4 : Caddy J5040 + plugin OVH (LE DNS-01, Caddyfile versionné)
│ ├── adguard/ ← E2 : AdGuard Home (DNS filtré, image épinglée digest)
│ └── watchtower/ ← S11.4 : Watchtower sélectif
└── configs/
├── fail2ban.local.conf ← config fail2ban (T2.1)
└── restic.env.example ← template restic (T3.1) — REMPLIR les secrets
Docs d'identité, d'observabilité et d'études (Phase 4.5+)
| Fichier | Rôle | Nature |
|---|---|---|
REGISTRE-BRIQUES.md |
Moitié technique du registre : briques, images épinglées, labels Watchtower, endpoints, état, backup. | Générée par scripts/11-registry.sh — ne pas éditer |
REGISTRE-IDENTITES.md |
Moitié humaine : quel compte sur quelle brique, type, 2FA, référence d'entrée du coffre, comment réinitialiser, responsable. | Versionnée, 0 secret |
RUNBOOKS/ |
nextcloud · matrix · jellyfin · photoprism · vaultwarden · registre · digest : symptôme → diagnostic → action → rollback |
matière première de l'assistante IA (G11) |
PARTAGE-SECRET-MAITRE.md |
Qui peut déverrouiller le coffre si je suis injoignable : détenteurs, contenu de l'enveloppe, procédure, test annuel | 0 secret |
RESTORE-NAS-USB.md |
Lire les disques DSM hors boîtier (mdadm/LVM, lecture seule), procédure + contrôles + interdits | test = critère Phase 6 |
EXPOSITION-INTERNET.md |
Étude : exposition mesurée, grille par brique, lien avec l'IdP, playbook classé | 0 règle posée |
BRIDGE-WHATSAPP.md |
Étude mautrix-whatsapp : apports, limites, risque de bannissement, interdits, pilote borné |
0 installation |
Régler le registre et les sondes (une fois, puis ça tourne)
# machine de pilotage — lecture seule sur .24/.27
./scripts/11-registry.sh # génère REGISTRE-BRIQUES.md
./scripts/11-registry.sh --check --alert # dérive -> payload JSON dans le salon ops (rc=1)
# G9 (root) — sondes applicatives + digest
sudo ./scripts/12-probes.sh --install # /usr/local/bin/g9-probes.sh + cron */15 + digest 08:30
sudo ./scripts/12-probes.sh --selftest # provocation d'un KO -> NOTIFY_OK 200
sudo ./scripts/g9-verify-alert.py 20 # payloads JSON conformes ? -> VERIFY_OK
sudo ./scripts/12-probes.sh --uninstall # rollback complet (cron + script + état)
Ordre d'exécution (phase par phase)
| Phase | Script / action | Garde-fou |
|---|---|---|
| 1 | (xpenology) exclure stateful de Watchtower + inventaire | G4 |
| 2 | 01-hardening.sh → 02-data.sh → 03-docker.sh → 03b-secrets.sh → 04-smb.sh → 05-beszel.sh |
— |
| 3 | 06-restic-backup.sh (backup + test restauration) |
G2 |
| 4 | par brique, dans l'ordre — après 03b-secrets.sh : 07b-migrate-vaultwarden.sh (T4.1) → 07c-migrate-nextcloud.sh (T4.2) → 07d-migrate-photoprism.sh (T4.3) ✅ 12/10 : 433 941 fichiers / 567 Go copiés, manifeste 0 écart + sha256 200/200, stack up (port 2342), index progressif, snapshot restic + restore prouvée (10-verify-restore-photo.sh) → 07a-migrate-gitea.sh (T4.4) → Jellyfin (T4.5, frais) → 07e-deploy-edge.sh (T4.6, Caddy TLS interne) → Watchtower (T4.7, scope stateless). 07-migrate-stateful.sh = script générique rsync, inopérant sur le DSM (sshd refuse rsync --server/scp) ; transport réel des 07a–07d = tar sur SSH |
G4 |
| 5 | restic 3-2-1 complet (G9 + PC IA → Freebox → 218play) | G2 |
| E (J5040, parallèle) | E1 durcissement + Docker (01/03 paramétrés, .27) ✅ → E2 AdGuard (VM Freebox .1 → .27) ✅ bascule DNS faite (résolvants clients → .27, filtres testés) → E3 DHCP (09-j5040-dhcp.sh, baux 35 appareils prêts) + Freebox mode bridge = GO utilisateur (G6) → E4 Caddy OVH (compose/edge-j5040) ✅ 7/7 certs LE (DNS-01) + WireGuard prêt ; forward WAN Freebox→.27 + démontage edge G9 = GO utilisateur |
G6, G7 |
| 6 | 06b-usb-media.sh (T6.1) → Jellyfin (T6.2) → IP .24→.25 → 08-decommission.sh |
G3, G4, G5 |
| 7 | (G7) wipe DSM + Ubuntu neuf + boot test | G3, G10 |
Note disque (décision 10/10/2026 — Option 0) : le G9 est mono-disque (Samsung 990 Pro 2 To, seul disque interne, aucun RAID prévu).
02-data.shcrée l'espace/dataen simple dossier sur le LV racine existant : ni partitionnement, ni LUKS, ni réinstallation (LUKS abandonné — données familiales non critiques ; protection = ufw LAN-only + backups restic chiffrés, voirDOSSIER-SOFTWARE.mdS9/S10 changelog 10/10/2026). La tolérance panne disque est assurée par le 3-2-1 (Phases 3/5) et le spare G7 (Phase 7).
Phase 3 — backup restic : état au 10/10/2026
Repo rclone:freeboxftp:Freebox/backup/g9 (Freebox Delta, partage Save/Freebox,
1.79 To, 1.785 To libres). Snapshot initial 21de0787 : 4 564 fichiers,
6.956 GiB lus / 4.178 GiB stockés (sources /data, /home/folzo, /etc).
- Transport & append-only : backend rclone de restic, remote FTP local
freeboxftp:= 192.168.1.254:21 (SMBfreebox:en secours), aucun montage ; append-only prouvé. Transports testés/écartés et limitations : source unique →RESTORE-G9.md§6. - Test de restauration : fichier restauré depuis la Freebox, sha256 identique
(
9352a905d9bb…, 470 637 o, restore < 2 s) — voirRESTORE-G9.md§2/§5 etjournal.md. - Automatisation (cron root, logs
/var/log/restic-*.log) : 03:30 backup (conteneurs stateful stoppés ~1–3 min : valheim, n8n, synapse, couchdb, obsidian), 04:00 prune (7 quotidiens / 4 hebdo / 12 mensuels / 5 annuels), 09:15 health (alerte si snapshot > 26 h). Les 2 jobs ont été lancés à la main (rc=0, snapshotcccdb2c7). - Alerte Matrix : directe via Synapse (
/etc/restic/matrix.env, token ops-bot, salon ops) — testée, messages réellement reçus. Voie de secours webhook n8n conservée (RESTIC_NOTIFY_WEBHOOK) ; accès n8n à recréer (clé API à générer dans l'UI). - Persistance post-reboot : validée par un vrai reboot (21:36) —
scripts/06c-postboot-check.sh= 15 contrôles OK / 0 KO (binaires, env/clé/config en 600, aucun montage requis, cron, jobs, repo lisible +checkOK, conteneurs relancés). À rejouer après tout changement. - Secrets :
/etc/restic/repo.key(clé du repo, jamais affichée — à consigner dans Vaultwarden, item « G9 – clé repo restic »),/etc/restic/cifs.cred,/etc/restic/matrix.env,/root/.config/rclone/{rclone,ftp}.conf,/etc/restic.env→ tous600root ; aucun secret dans le repo git (.gitignore:*.env,*.cred,*.key).
Convention
- Chaque script affiche ce qu'il fait, est idempotent, et écrit dans
journal.md. - Les secrets (mots de passe, clés restic) vont dans
configs/*.envhors git (G9). - Un script échoue → il s'arrête (
set -euo pipefail) → on lit le journal → on corrige.