# 🧰 Migration du Pi : Bullseye → Bookworm (carte SD neuve)

> ✅ **FAIT le 2026-06-20.** Écarts vs ce plan : Pi Imager a flashé **Debian 13
> (trixie)**, pas bookworm (mieux : support ~2030) ; arch **aarch64 (64-bit)** ✅ ;
> **Node 24.17** via nodesource (repo `nodistro`, OK sur trixie) en `/usr/bin/node` ;
> NAS monté en **SMB3 (vers=3.0)** — le QNAP l'accepte (l'ancien « SMB2 only » était
> pessimiste) ; **10 partages** reproduits dans le fstab (pas juste `homes`) ;
> nouvelle **IP Tailscale `100.69.14.115`** (reportée dans l'app + OTA preview) ;
> origines unattended-upgrades = `trixie`/`trixie-security` (Node+Tailscale exclus,
> volontaire). Ancienne carte bullseye gardée intacte (rollback).

> **Pourquoi / échéance** : Raspbian/Debian 11 **bullseye = fin de support LTS le
> 2026-08-31**. Après, plus aucun correctif de sécurité. Le Pi étant **la passerelle
> qui porte toute la sécurité** (agent + SSH + creds NAS), il doit passer sous
> **Debian 12 (bookworm)** avant cette date. Support bookworm jusqu'en ~2028.

## Stratégie : install neuve sur une AUTRE carte SD (rollback gratuit)

On **ne fait PAS** de `dist-upgrade` en place (risque de lock-out à distance, pas de
console). On flashe **une carte SD neuve** avec Pi OS bookworm et on re-provisionne.
La **carte actuelle est conservée intacte** : si quoi que ce soit cloche, on la
réinsère et tout remarche comme avant.

**La config et les données vivent sur le NAS** (`/mnt/nas/homes/.photosync` : config,
journal, métadonnée APK) → **rien à migrer côté données**. Le Pi n'est qu'une
passerelle à reconstruire. Le NAS n'est pas touché par cette opération.

⚠️ **À faire avec accès physique au Pi** (clavier/écran ou au moins la carte en main),
pas en aveugle à distance.

---

## 🗓️ Déroulé Jour J — pas à pas (on le fait ensemble)
> 🧑 = toi (physique / clics) · 🤖 = moi (en SSH). On avance étape par étape : tu me dis
> quand chaque action 🧑 est faite, je fais les 🤖, et on vérifie au fur et à mesure.
> **À tout moment en cas de blocage** → on remet l'ancienne carte (§Rollback) et on
> diagnostique à froid, sans stress.

1. 🧑 **Console admin Tailscale** → supprimer le nœud `raspberrypi` (cf. Pré-vol).
2. 🧑 **Flasher** la carte neuve avec Pi Imager (réglages exacts au §1), l'insérer dans
   le Pi, brancher l'**Ethernet**, allumer.
3. 🧑 me dire « **c'est booté** » → 🤖 je teste l'accès SSH par clé sur `192.168.1.136`.
4. 🤖 **Paquets de base + Node 24** (§2).
5. 🧑 me donner le **mot de passe NAS** (une fois) → 🤖 **montage CIFS** `vers=3.0` (§3).
6. 🤖 **Agent + service** (token en `EnvironmentFile`) (§4).
7. 🤖 **Tailscale** install + `up` → 🧑 valider l'auth dans le navigateur si demandé ;
   🤖 je note la **nouvelle IP `100.x`**.
8. 🤖 **Durcissement** SSH / ufw / auto-updates (§6) + 🤖 **vérifications** (§7).
9. 🧑 **mettre la nouvelle IP Tailscale dans l'app**, puis tester une synchro **Wi-Fi**
   maison **et** en **4G/5G**.
10. ✅ Tout passe → terminé. **L'ancienne carte reste ton filet** : la garder telle quelle.

---

## État de référence (carte actuelle, relevé le 2026-06-15)
- **Matériel** : Raspberry Pi 3 Model B — actuellement **armhf (32-bit)**. Bookworm
  propose le **64-bit (arm64)** sur Pi 3 → recommandé (mieux supporté, pérenne).
- **Node** : **v22.22.3** (LTS Jod) dans `/opt/node22/bin/node` — passé de Node 20 (EOL
  fin avril 2026) à Node 22 le 2026-06-15 ; c'est la **dernière LTS encore dispo en
  32-bit (armv7l)**. Node 20 conservé à `/usr/local/bin/node` (rollback). Sur le **Pi
  64-bit (arm64)** cible, viser **Node 24 LTS** (le 24 n'a pas de binaire 32-bit).
- **Agent** : `/home/taaazzz/photosync-agent/server.js`, service systemd `photosync-agent`.
- **IP locale** : `192.168.1.136` (réservation DHCP sur la box à reconduire).
- **Tailscale** : hostname `raspberrypi`, tailnet `tail8e8ec4.ts.net`, IP `100.105.226.90`,
  `tailscale serve` 443→127.0.0.1:8080.

---

## Pré-vol (avant de toucher au Pi)
- [ ] **Carte SD neuve** : SanDisk **High Endurance 64 Go** (gamme dashcam/surveillance,
  écriture 24/7) + un **lecteur SD** sur le PC.
- [ ] **Mot de passe du compte NAS** `taaazzz` (pour le fichier creds CIFS).
- [ ] **`PHOTOSYNC_TOKEN`** : je le récupère depuis l'ancienne carte **avant le swap**
  (`/etc/photosync-agent.env`), aussi en mémoire `photosync-token`.
- [ ] **Clé publique** du poste de dev (à coller dans Pi Imager) :
  ```
  ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDY07pQHE83oB67zUOZkLsZNEY3W6bAE8VXwhwx4bu6f bruno@taaazzz-prog
  ```
- [ ] **Tailscale — l'IP VA changer (cas nominal, pas un secours)** : une install neuve
  = nouvelle clé/identité de nœud, donc Tailscale la voit comme une **autre machine** et
  lui donne une **nouvelle IP `100.x`**, quel que soit le hostname. Et comme l'ancien
  nœud `raspberrypi` existe encore (ancienne carte gardée pour rollback), le nouveau
  s'enregistrerait en **`raspberrypi-1`** → ça casserait `tailscale serve` et MagicDNS.
  → **Avant le ré-`up`** : supprimer/expirer l'ancien nœud `raspberrypi` dans la console
  admin Tailscale (pour récupérer le nom sans suffixe `-1`). → Prévoir de **mettre à jour
  l'adresse serveur dans l'app** avec la nouvelle IP (c'est attendu, pas exceptionnel).
  ⚠️ Si un jour tu rebootes l'ancienne carte pour un diag, elle se **réenregistrera** dans
  le tailnet.

## 1) Flasher Pi OS bookworm (Lite, 64-bit)
- [ ] **OS** : Raspberry Pi Imager → **Raspberry Pi OS Lite (64-bit)** (bookworm).
- [ ] **Roue crantée / « Modifier les réglages »** :
  - Hostname : `raspberrypi`
  - **Activer SSH** → *« Autoriser l'authentification par clé publique uniquement »* →
    coller la clé du Pré-vol (`ssh-ed25519 …AAAAIDY07pQ… bruno@taaazzz-prog`)
  - Nom d'utilisateur : `taaazzz` + **un mot de passe local** (console physique de
    secours ; il ne sert PAS au SSH qui reste en clé)
  - Locale : Europe/Paris, clavier `fr`. **Pas de Wi-Fi** (le Pi est en **Ethernet**).
- [ ] Booter la carte neuve dans le Pi. **L'IP locale `192.168.1.136` revient toute
  seule** : c'est le **même Pi physique → même MAC → la réservation DHCP de la box
  s'applique automatiquement** (rien à refaire, sauf si tu avais figé l'IP côté OS — ce
  qui n'est pas le cas ici, pas de statique dans `dhcpcd`).

## 2) Paquets de base
```bash
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y ffmpeg cifs-utils ufw unattended-upgrades
# Node 24 LTS (arm64 dispo sur bookworm 64-bit ; le 24 n'existe PAS en 32-bit) :
curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs    # -> /usr/bin/node ; ajuster ExecStart si le chemin diffère
node -v                        # doit afficher v24.x
```

## 3) Montage CIFS du NAS
```bash
# Fichier creds (RECREER avec le vrai mot de passe ; perms 600) :
sudo tee /etc/photosync-taaazzz.cred >/dev/null <<'EOF'
username=taaazzz
password=LE_MOT_DE_PASSE_NAS
EOF
sudo chmod 600 /etc/photosync-taaazzz.cred
# Ligne fstab — vers=3.0 (le QNAP supporte SMB3 : intégrité + chiffrement possibles ;
# gain gratuit vs l'ancien vers=2.0). En cas de souci de compat, retomber sur vers=2.0.
echo '//192.168.1.124/homes /mnt/nas/homes cifs credentials=/etc/photosync-taaazzz.cred,vers=3.0,uid=taaazzz,gid=taaazzz,iocharset=utf8,_netdev,nofail,x-systemd.automount 0 0' | sudo tee -a /etc/fstab
sudo mkdir -p /mnt/nas/homes && sudo systemctl daemon-reload
ls /mnt/nas/homes/Taaazzz/_apk/   # doit lister les APK -> montage OK
```
> 💡 C'est le bon moment, si on le décide, pour passer le montage sur un **compte NAS
> standard** (≠ admin) — voir mémoire `pi-hardening` (levier sécu « compte standard »).

## 4) Agent PhotoSync + service
> **Dépendances** : `server.js` n'utilise **que des modules natifs de Node** (vérifié
> 2026-06-15 : aucun `node_modules`, et `md5sum` identique entre l'ancien Pi et le dépôt).
> Donc **copier `server.js` suffit**, pas de `npm install`. *Si un jour des deps étaient
> ajoutées (un `package.json` avec express/multer…), il faudrait aussi copier
> `package.json` + `package-lock.json` puis `npm ci`.* Avant de migrer, **re-confirmer**
> que le `server.js` du dépôt == celui qui tourne (pas de correctif chaud resté seulement
> sur le Pi) : `md5sum ~/projects/photo/pi-agent/src/server.js` vs sur le Pi.
```bash
mkdir -p ~/photosync-agent
scp ~/projects/photo/pi-agent/src/server.js taaazzz@192.168.1.136:~/photosync-agent/server.js  # depuis le poste de dev
# Token HORS de l'unite (lisible par tous en 644) -> EnvironmentFile en 600 root :
sudo tee /etc/photosync-agent.env >/dev/null <<'EOF'
NAS_ROOT=/mnt/nas
PHOTOSYNC_META_ROOT=/mnt/nas/homes
PHOTOSYNC_TOKEN=LE_TOKEN
EOF
sudo chmod 600 /etc/photosync-agent.env && sudo chown root:root /etc/photosync-agent.env
sudo tee /etc/systemd/system/photosync-agent.service >/dev/null <<'EOF'
[Unit]
Description=PhotoSync agent (recoit les medias et les ecrit sur le NAS)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=taaazzz
WorkingDirectory=/home/taaazzz/photosync-agent
EnvironmentFile=/etc/photosync-agent.env
ExecStart=/usr/bin/node /home/taaazzz/photosync-agent/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now photosync-agent
systemctl is-active photosync-agent
```
> Vérifier `ExecStart` : avec nodesource le binaire est en `/usr/bin/node`. Adapter si
> besoin.
> 🛡️ **Filet fail-closed** : si on oublie `/etc/photosync-agent.env`, l'agent **refuse
> tout (401)** au lieu de s'ouvrir (il ne démarre PAS en grand ouvert). Le symptôme
> sera « 401 partout » au §7 → on saura tout de suite qu'il manque le token. (Mode
> ouvert volontaire = `PHOTOSYNC_ALLOW_OPEN=1`, à ne PAS mettre en prod.)

## 5) Tailscale
**D'abord** : dans la console admin Tailscale, **supprimer (ou expirer) l'ancien nœud
`raspberrypi`** — sinon le nouveau s'enregistre en `raspberrypi-1` (casse `serve` +
MagicDNS).
```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up                      # nouvelle identité de noeud (nouvelle IP attendue)
sudo tailscale serve --bg 8080         # recrée le proxy HTTPS 443 -> agent
tailscale ip -4                        # NOTER la nouvelle IP -> la reporter dans l'app (cas nominal)
sudo tailscale serve status            # confirmer 443 -> 127.0.0.1:8080
```

## 6) Durcissement (rejouer ce qui était en place — voir mémoire `pi-hardening`)
```bash
# SSH par cle uniquement
sudo tee /etc/ssh/sshd_config.d/99-photosync-hardening.conf >/dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
EOF
sudo sshd -t && sudo systemctl reload ssh

# Mises a jour de securite auto. Active le declenchement :
sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
EOF
# ⚠️ NE PAS recopier les origines du 32-bit ! Sur Raspberry Pi OS 64-bit il n'y a PLUS
# de depot "Raspbian" (cette ligne deviendrait un no-op silencieux -> rien patche, le
# piege qui mord). Les LABELS exacts different aussi. -> LIRE les vraies origines :
apt-get update
apt-cache policy | grep -Eo 'o=[^,]+,a=[^,]+|l=[^,]+' | sort -u   # note origin/label/suite reels
# Puis ecrire le pattern d'apres CE qui est affiche, p.ex. (a ADAPTER selon la sortie) :
sudo tee /etc/apt/apt.conf.d/51photosync-origins.conf >/dev/null <<'EOF'
Unattended-Upgrade::Origins-Pattern {
    "origin=Debian,codename=${distro_codename},label=Debian-Security";
    "origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
    "origin=Raspberry Pi Foundation,codename=${distro_codename},label=Raspberry Pi Foundation";
};
Unattended-Upgrade::Automatic-Reboot "false";
EOF
# VALIDER que des paquets de securite sont bien selectionnes (5 min qui sauvent la passerelle) :
sudo unattended-upgrade --dry-run -d 2>&1 | grep -i 'origines permises\|allowed origins'
sudo unattended-upgrade --dry-run -d 2>&1 | grep -c 'Marking not allowed'   # idealement 0 sur les depots utiles

# Firewall : tout bloque en entree sauf Tailscale + SSH/8080 depuis le LAN
sudo ufw default deny incoming && sudo ufw default allow outgoing
sudo ufw allow in on tailscale0 comment 'Tailscale : app a distance + admin'
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp comment 'SSH depuis LAN'
sudo ufw allow from 192.168.1.0/24 to any port 8080 proto tcp comment 'Agent depuis LAN'
sudo ufw allow 41641/udp comment 'Tailscale NAT traversal'
# filet anti-lockout puis activation :
sudo systemd-run --on-active=300 --unit=ufw-safety /usr/sbin/ufw --force disable
sudo ufw --force enable
# verifier l'acces sur une NOUVELLE connexion, puis : sudo systemctl stop ufw-safety.timer
```
> Pas de CUPS sur Pi OS Lite → rien à désactiver. Sinon `sudo systemctl disable --now cups*`.

## 7) Vérification finale
- [ ] `systemctl is-active photosync-agent` → `active`
- [ ] Depuis le poste de dev : `curl -s -o /dev/null -w '%{http_code}' http://192.168.1.136:8080/` → `200`
- [ ] `GET /apk-latest` **avec** token → `200` ; un endpoint protégé **sans** token → `401`
  (preuve que le token de l'`EnvironmentFile` est bien chargé)
- [ ] `sudo ls -l /etc/photosync-agent.env` → **`600 root`** (token jamais en 644)
- [ ] `unattended-upgrade --dry-run -d` sélectionne bien des paquets de **sécurité**
  (origines validées, pas de no-op silencieux)
- [ ] Nouvelle **IP Tailscale** reportée dans l'app ; `tailscale serve status` = 443→8080
- [ ] **App en Wi-Fi maison** : une synchro passe
- [ ] **App en 4G/5G** (chemin Tailscale) : une synchro passe
- [ ] SSH par clé OK, mot de passe refusé ; `sudo ufw status` actif

## Rollback
En cas de pépin : **éteindre, remettre l'ancienne carte SD bullseye, rebooter**. Tout
remarche comme avant (le NAS n'a pas bougé). Diagnostiquer la carte bookworm à froid.

---
Réfs : `APK-DELIVERY.md`, mémoires `pi-hardening`, `pi-ssh-deploy`, `photosync-token`,
`photosync-infra`.
