Files
sans.pub/plan-migration.md
T
Pierre MartinandClaude Opus 5.5 3fc9ae52c9 fix(mail): certificat TLS de poste.io synchronisé depuis CapRover
Le certificat de mail.sans.pub servi par poste.io avait expiré le
2021-02-06 : son renouvellement Let's Encrypt intégré ne peut pas
fonctionner derrière le nginx de CapRover. Le script mail/mail-cert-sync.sh
(cron quotidien) copie désormais le certificat renouvelé par CapRover
et recharge Dovecot/Haraka. L'alerte du backup n'a plus besoin de -k.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 13:43:22 +02:00

14 KiB

Plan de migration du serveur sans.pub

  • Créé le : 2026-10-03
  • Statut : brouillon — décisions D1 à D4 à trancher avant de commencer
  • Remplace : la chaîne « Debian 9 → 12, puis Docker 27+, puis CapRover 1.14 » de l'ancien todo.txt

1. État des lieux (relevé du 2026-10-03)

Élément État Problème
OS Debian 9, noyau 4.9, uptime ~2300 jours Fin de support, plus de correctifs de sécurité
Docker 19.03.12 (Swarm, 1 nœud) Trop ancien pour CapRover ≥ 1.14 (API 1.43 requise)
CapRover 1.11.1 Bloqué par Docker
Disque 1 seul disque de 931 Go, LVM, sans RAID Panne disque = perte totale
Backup Duplicity (/root/backup/, nov. 2020) vers OVH — ne tourne plus : conteneur absent, image supprimée Voir D4
Résidus YunoHost (LDAP, postfix/dovecot installés, /etc/yunohost), PostgreSQL 9.6 sur l'hôte (vide), /home/admin/Kaamelott (31 Go) Hôte encombré, surface d'attaque
Matériel 4 cœurs, 23 Go RAM, IP 109.238.11.111 (Ikoula) —

Données à sauvegarder (~140 Go utiles)

Données Taille
captain--nextcloud-data 50 Go
captain--photoprism-data 23 Go
captain--une-espace-data 16 Go
captain--etherpad-db-data 3 Go
captain--gitea-data 2,1 Go
captain--peertube-peertube-data 1,7 Go
captain--mail-data 1,4 Go
Autres volumes (bases, cryptpad, bitwarden, privatebin…) ~3 Go
/captain/data hors registry (certificats, config CapRover) ~1 Go
/home/cloud/apps_data (configs writefreely, rss-bridge) < 10 Mo
/home/admin/Kaamelott 31 Go (à garder ?)

À ne pas sauvegarder (reconstructible) : registres Docker (/captain/data/registry 17 Go, volumes anonymes de docker-registry 28 + 4,5 Go), overlay2, logs.

Services (47) et images obsolètes

Bases en fin de vie : mysql:5.7 (gitea-db), postgres:10 (peertube-db), mariadb:10.5.3 (nextcloud-db, une-espace-db), mariadb:10.7 (writefreely-db), redis:6 (peertube-redis).

Applications très en retard : PeerTube 3.4.1, PhotoPrism 2021, LimeSurvey 5.2.4, CryptPad 5.2.1 (image non officielle), Scenari 5.0, Miniflux 2.0.44, Vaultwarden 1.32, poste.io 2.4.11, WriteFreely 0.12.

Images personnalisées construites par CapRover (registre interne :996) : domi-marche, ethercalc-db, etherpad, jeu-laser, limesurvey-mariadb-db, lmo, monica, monica-db, nextcloud-cron, ots-db, pierre-martin, sans-pub-site, une-espace-cron, une-espace-redis, zoe-cm1-site. Il faut retrouver leurs sources (dépôts Gitea + webhooks) avant de quitter CapRover.

2. Décisions à prendre

D1 — Mise à jour sur place ou nouveau serveur ? → Recommandé : nouveau serveur

Le scénario « tout éteindre, backup, tout mettre à jour, tout relancer » sur la machine actuelle implique quatre montées de version Debian enchaînées (9 → 10 → 11 → 12 → 13) sur un système qui n'a pas redémarré depuis six ans, avec des résidus YunoHost. Il implique aussi une longue coupure de tous les services, et aucun retour arrière si une étape casse.

Avec un nouveau serveur (Debian 13 neuf, idéalement avec 2 disques en RAID 1) :

  • l'ancien serveur reste allumé et sert de retour arrière jusqu'à la fin ;
  • on migre service par service, chacun avec une courte coupure ;
  • on part d'un hôte propre, ce qui est cohérent avec l'incident de septembre (on ne garde rien de potentiellement touché).

Coût : un second serveur pendant quelques semaines. Point d'attention : la nouvelle IP impacte le mail (PTR, SPF, réputation) → voir phase 5.

D2 — Garder CapRover ? → Recommandé : non, Docker Compose + Caddy, versionné dans ce dépôt

CapRover Compose + Caddy
Config Dans la base CapRover, modifiable via UI/API Fichiers texte versionnés dans git, relisibles, diffables
Pièges connus appDefinitions/update qui remplace au lieu de fusionner (incident du 2026-09-23), modifs docker service invisibles Aucun intermédiaire : ce qui est dans le fichier est ce qui tourne
Swarm Obligatoire (inutile sur 1 nœud) Inutile
TLS certbot intégré Automatique avec Caddy
Déploiement depuis Gitea Webhooks intégrés À remplacer (Gitea Actions + docker compose pull && up -d, ou build local)
Backup/restauration Indirecte (volumes + config CapRover) Directe : dépôt git + volumes
Interface web Oui Non (option : Dockge ou Portainer si besoin d'une UI)

Si une interface de type PaaS reste indispensable, les alternatives modernes à CapRover sont Coolify et Dokploy. Elles réintroduisent cependant la même couche opaque.

D3 — Quels services garder ?

À passer en revue (garder / supprimer / archiver) avant la migration — chaque service supprimé, c'est une migration en moins. Candidats à l'examen : ethercalc, reqbin, scenari, monica, photoprism, jeu-laser, lmo, une-fibre (redirection), limesurvey, cryptpad, /home/admin/Kaamelott.

D4 — Le backup OVH existant (retrouvé le 2026-10-03)

Configuration dans /root/backup/ sur le serveur, créée en novembre 2020 :

  • docker-compose.yaml : image tecnativa/duplicity (Duplicity + cron interne), sauvegarde de /var/lib/docker/volumes, /captain/data et /home/cloud/apps_data, chiffrée (PASSPHRASE), rapport par mail à pierre@sans.pub
  • config.json : double destination OVH (projet Public Cloud, région GRA) — données dans le conteneur Public Cloud Archive backup-principal (pca://), manifestes/signatures dans le conteneur Swift backup-principal-metadata

État constaté :

  • Aucun conteneur de backup ne tourne et l'image tecnativa/duplicity n'est plus présente sur le serveur → les backups sont arrêtés depuis une date inconnue
  • restored-data/ vide : aucune restauration n'a jamais été testée
  • config.json contient les identifiants OVH en clair et est lisible par tous (644) → passer en 600
  • La passphrase Duplicity est dans docker-compose.yaml : sans elle, les archives existantes sont illisibles — à mettre dans Vaultwarden
  • Public Cloud Archive (stockage froid) : la restauration impose un délai de désarchivage (plusieurs heures) — peu adapté à une migration
  • Vérifier côté console OVH que l'offre Public Cloud Archive (Swift) est toujours active et facturée — OVH pousse désormais ses offres S3 (Object Storage, Cold Archive)

À faire : lister les deux conteneurs OVH pour connaître la date du dernier backup réussi et son volume.

3. Phases

Phase 0 — Backup fiable (à faire tout de suite, indépendamment de la migration)

Objectif : un backup chiffré, automatique, restauration testée, sur OVH Object Storage.

  • (2026-10-03) restic 0.19.1 installé, dépôt swift:sans-pub-restic:/ créé avec les identifiants OVH existants (Swift standard, pas archive)
  • (2026-10-03) Script backup/restic-backup.sh : dumps de toutes les bases + volumes + /captain/data + /etc, rétention 7/4/6, prune/check hebdo, mail si échec — détails dans CLAUDE.md § Backup
  • (2026-10-03) Cron quotidien 03:30 ; premier backup complet (~53 Gio) réussi, restauration de fichiers testée depuis OVH
  • Copier /root/restic/password dans Vaultwarden (+ la passphrase Duplicity et les identifiants OVH)
  • Alerte « homme mort » externe (ex : healthchecks.io) : l'alerte mail actuelle passe par le serveur lui-même
  • Régénérer les identifiants OVH (exposés en clair dans une session Claude le 2026-10-03) puis mettre à jour /root/restic/env ; chmod 600 /root/backup/config.json
  • (Optionnel) Lister les conteneurs OVH de l'ancien backup Duplicity pour connaître la date du dernier backup, puis les supprimer pour ne plus les payer
  • (Optionnel) Inclure /home/admin/Kaamelott (31 Go, exclu pour l'instant)
  • Test de restauration : restaurer un volume et un dump de base sur une autre machine, et vérifier que le service démarre avec
  • Conserver le mot de passe restic hors du serveur (Vaultwarden + copie papier) — sans lui le backup est inutilisable

Les sauvegardes « à chaud » (bases dumpées, fichiers copiés en marche) suffisent pour le quotidien. Pour la bascule de chaque service, on arrêtera le service avant la sauvegarde finale (phase 3).

Phase 1 — Préparer la nouvelle infrastructure

  • Commander le serveur (2 disques RAID 1 si possible, ≥ 1 To, ≥ 32 Go RAM vu la croissance)
  • Debian 13, durcissement SSH (clé seule, pas de root), pare-feu (nftables), mises à jour automatiques de sécurité (unattended-upgrades)
  • Docker Engine récent (dépôt officiel) + plugin Compose
  • Arborescence dans ce dépôt : stacks/<service>/compose.yml + .env.example ; secrets réels dans .env non versionnés (ou chiffrés avec sops/age)
  • Caddy en reverse proxy (un réseau Docker par stack, seul Caddy est connecté à tous)
  • Backup restic installé dès le départ sur le nouveau serveur
  • Supervision minimale (uptime externe + espace disque + échec de backup)

Phase 2 — Inventaire détaillé des services

Pour chaque service conservé (D3) : image cible, volumes, variables d'environnement (via GET /user/apps/appDefinitions), domaines, dépendances (base, redis), source des images personnalisées, procédure de montée de version.

  • Exporter les définitions CapRover complètes (appDefinitions) dans ce dépôt (sans les secrets)
  • Retrouver le dépôt source de chaque image img-captain-*

Phase 3 — Migration service par service

Pour chaque service :

  1. Baisser le TTL DNS du domaine (300 s) quelques jours avant
  2. Déployer la stack sur le nouveau serveur, à vide, et tester
  3. Arrêter le service sur l'ancien serveur (via CapRover : instances → 0)
  4. Dump final de la base + copie des volumes (rsync de l'ancien vers le nouveau)
  5. Restaurer ; pour les bases EOL, restaurer le dump dans la nouvelle version plutôt que réutiliser les fichiers bruts
  6. Basculer le DNS, tester, surveiller 24-48 h
  7. Laisser l'ancien service arrêté mais intact (retour arrière = rallumer + remettre le DNS)

Ordre proposé (du moins risqué au plus critique) :

  1. Sites statiques et outils sans données : sans-pub-site, pierre-martin, zoe-cm1-site, it-tools, cyberchef, adminer, une-fibre
  2. Petits services avec données : privatebin, ots, rss + rss-bridge, writefreely, ethercalc, etherpad, cryptpad
  3. Gitea (+ remplacement des webhooks de déploiement CapRover)
  4. Services lourds avec montée de version importante : peertube (3.4 → 8 : suivre les notes de version majeure par majeure), limesurvey, photoprism, scenari, monica
  5. Nextcloud et une-espace (gros volumes)
  6. Vaultwarden (critique : tester export/import avant)
  7. Mail en dernier (voir phase 5)

Phase 4 — Montée de version des bases

Base Actuelle Cible Méthode
gitea-db MySQL 5.7 MySQL 8.4 LTS (ou MariaDB 11.4) dump/restore
etherpad-db MySQL 8.0.33 MySQL 8.4 dump/restore
nextcloud-db, une-espace-db MariaDB 10.5.3 MariaDB 11.4 LTS dump/restore
writefreely-db MariaDB 10.7 MariaDB 11.4 dump/restore
peertube-db PostgreSQL 10 PostgreSQL 17 pg_dump / restore
rss-db PostgreSQL 15.2 PostgreSQL 17 pg_dump / restore
peertube-redis Redis 6 Valkey 8 / Redis 7 données jetables (cache/queue), mot de passe à ajouter

Phase 5 — Mail (poste.io)

Le changement d'IP est le principal risque :

  • Demander le PTR (reverse DNS) de la nouvelle IP vers sans.pub avant la bascule
  • Vérifier que la nouvelle IP n'est pas sur liste noire (Spamhaus, etc.)
  • Mettre à jour SPF (ip4:), garder la même clé DKIM
  • Bascule : arrêter mail sur l'ancien serveur → rsync final de captain--mail-data → démarrer → changer A/MX
  • Les mails entrants pendant la bascule sont remis plus tard par les serveurs expéditeurs (file d'attente SMTP de plusieurs jours)

Phase 6 — Décommissionnement

  • Toutes les stacks migrées et stables depuis ≥ 2 semaines
  • Backup final complet de l'ancien serveur (archive froide sur OVH)
  • Résiliation de l'ancien serveur
  • Mise à jour de CLAUDE.md (nouvelle archi, plus de CapRover, nouvelles commandes)

4. Problèmes découverts en route (2026-10-03)

  • Certificat TLS de poste.io expiré depuis le 2021-02-06 — corrigé le 2026-10-03 en réutilisant le certificat CapRover, synchronisation quotidienne (voir CLAUDE.md § Mail). Dans la nouvelle archi, prévoir le certificat du mail dès le départ (Caddy ne sert pas les ports mail)
  • writefreely-db (MariaDB 10.7) : table système mysql.proc incompatible → lancer mariadb-upgrade (ou corriger lors de la migration par dump/restore)
  • limesurvey-mariadb-db : root@localhost et root@% ont des mots de passe différents → à harmoniser (ou ignorer : réglé à la migration)
  • Cron Nextcloud en panne du 2026-08-09 au 2026-10-03 (DNS overlay perdu) — corrigé, voir CLAUDE.md

5. Risques

Risque Parade
Pas de backup avant de commencer Phase 0 obligatoire, restauration testée
Source d'une image personnalisée introuvable Exporter l'image depuis le registre interne (docker save) en dernier recours
PeerTube 3.4 → 8 casse Garder l'ancien en lecture seule, migrer par étapes de version majeure
Mail en spam après changement d'IP PTR + liste noire vérifiés avant, bascule mail en dernier
Secrets compromis lors de l'incident de septembre recopiés tels quels Profiter de la migration pour régénérer les secrets applicatifs