# 📦 Livrer un APK à l'utilisateur

> Comment je (l'assistant) livre un nouveau build APK. L'APK et les fichiers passent
> par l'**agent du Pi en HTTP**, qui a le NAS monté en CIFS — écrire sur l'agent =
> écrire sur le NAS. Le **code de l'agent** (`server.js`), lui, se déploie **par SSH
> avec une clé** (sans mot de passe) — voir « Déployer l'agent » plus bas. Donc je
> peux livrer **en solo** : build → NAS → métadonnée → (si besoin) déploiement agent.

## ⚡ L'ESSENTIEL — l'utilisateur installe DEPUIS L'APP, pas par QR

**L'utilisateur télécharge la nouvelle version via la MISE À JOUR IN-APP.** L'app
compare son `versionCode` installé à `GET /apk-latest` et propose le téléchargement
**uniquement si la métadonnée annonce un `versionCode` SUPÉRIEUR**.

➡️ **Ne propose JAMAIS de QR code / lien à scanner** — ça l'agace (le QR de la section
optionnelle n'est qu'un dépannage si l'app ne peut pas être lancée du tout).

➡️ Conséquence : **chaque build destiné à l'app DOIT incrémenter `versionCode`.** Si tu
oublies, l'app ne verra jamais la mise à jour, même si l'APK est sur le NAS.

## ✅ Checklist (dans l'ordre — sauter une étape = échec)

1. **Bumper la version AVANT de builder** dans `mobile/app.json` :
   - `expo.version` (ex. `1.4.2` → `1.4.3`)
   - `expo.android.versionCode` (ex. `12` → `13`) ← **c'est CE champ qui déclenche l'update**
2. **Builder l'APK en local** (section 1).
3. **Uploader sur le NAS** dans `Taaazzz/_apk/` (section 2), nom `PhotoSync-<version>.apk`.
4. **Publier la métadonnée `POST /apk-latest`** avec le NOUVEAU `versionCode` (section 3)
   ← **sans ça, rien ne se passe côté app.**
5. Vérifier que `GET /apk-latest` renvoie bien le nouveau `versionCode`.
6. **Si `pi-agent/src/server.js` a changé** : déployer l'agent par SSH (section « Déployer l'agent »).
7. (option) Mettre l'ancien APK à la corbeille (section 4).

## 🔑 Token (obligatoire — l'agent refuse tout sans lui : `401 token requis`)

`PHOTOSYNC_TOKEN` est défini sur le Pi → **toutes** les requêtes (sauf `GET /`) exigent
`-H "X-PhotoSync-Token: <token>"` (ou `&token=<token>` dans l'URL pour `/file`).

Le token n'est **jamais committé** (ni ici, ni sur le NAS). Pour le récupérer sur la
machine de dev (WSL2), **sans demander à l'utilisateur** :
```bash
TOKEN=$(grep -hoE "PHOTOSYNC_TOKEN='[^']+'" ~/.bash_history | tail -1 | sed "s/.*='//;s/'//")
# Sinon il est aussi noté dans la mémoire privée photosync-token.md (~/.claude/.../memory/).
```
Il est aussi présent dans l'unité systemd du Pi (`Environment=PHOTOSYNC_TOKEN=…`) et dans
l'AsyncStorage de l'app (clé `token`). Voir la mémoire `photosync-token`.

---

## Repères réseau

- **Agent (local, WiFi maison)** : `http://192.168.1.136:8080`
- **Agent (à distance, Tailscale)** : `https://raspberrypi.tail8e8ec4.ts.net`
- **Racine NAS de l'agent** : `/mnt/nas/homes` (partage `homes` : `admin/`, `Alexia/`, `Taaazzz/`)
- **Dossier des APK** : `Taaazzz/_apk/`

L'agent expose `POST /upload` (écrit sur le NAS), `GET /file` (le sert),
`POST /apk-latest` (métadonnée de mise à jour in-app).

## 🔐 Déployer l'agent (code `server.js`) — par SSH avec clé, EN SOLO

Le code de l'agent vit sur le **Pi** (`~/photosync-agent/server.js`, PAS sur le NAS),
servi par le service systemd **`photosync-agent`**. Il se déploie **par SSH avec une
clé** (`~/.ssh/id_ed25519`, sans passphrase, déjà dans le `authorized_keys` du Pi) —
**aucun mot de passe, jamais** (ne JAMAIS le redemander). Faire CE déploiement à chaque
fois que `pi-agent/src/server.js` change (nouvel endpoint, correctif…).

```bash
# 1) backup horodaté sur le Pi
ssh -o BatchMode=yes taaazzz@192.168.1.136 'cp ~/photosync-agent/server.js ~/photosync-agent/server.js.bak-$(date +%Y%m%d-%H%M%S)'
# 2) copie du nouveau code
scp -o BatchMode=yes ~/projects/photo/pi-agent/src/server.js taaazzz@192.168.1.136:~/photosync-agent/server.js
# 3) vérif syntaxe + restart + état (doit afficher "active")
ssh -o BatchMode=yes taaazzz@192.168.1.136 'node --check ~/photosync-agent/server.js && sudo systemctl restart photosync-agent && systemctl is-active photosync-agent'
```
> Si la clé est rejetée (Pi réinstallé), l'utilisateur la repose une fois :
> `ssh-copy-id -i ~/.ssh/id_ed25519.pub taaazzz@192.168.1.136` (il tape SON mot de passe
> Pi, je ne le vois pas). Voir la mémoire `pi-ssh-deploy`.

Vérifier ensuite qu'un endpoint touché répond (ex. `curl` sur `/journal-remove`, etc.).

## 1) Construire l'APK en local

Quota EAS cloud épuisé → build **local** (ne compte pas dans le quota). JDK 17 requis
(pas 21). Voir la mémoire `local-apk-build` pour les pièges (OOM Gradle, keystore).
```bash
cd ~/projects/photo/mobile
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 ANDROID_HOME=~/Android/Sdk EAS_LOCAL_BUILD_SKIP_CLEANUP=1
npx eas-cli build --local --profile preview --platform android --non-interactive
# -> ~/projects/photo/mobile/build-<timestamp>.apk (~94 MB, signé avec le keystore EAS)
# NB : c'est `npx eas-cli`, le binaire `eas` n'est PAS dans le PATH.
```

## 2) Envoyer l'APK sur le NAS (via l'agent)

```bash
TOKEN=$(grep -hoE "PHOTOSYNC_TOKEN='[^']+'" ~/.bash_history | tail -1 | sed "s/.*='//;s/'//")
APK=~/projects/photo/mobile/build-<timestamp>.apk
NAME=PhotoSync-1.4.3.apk
enc(){ python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$1"; }

curl -s -m 600 --data-binary @"$APK" -X POST -H "X-PhotoSync-Token: $TOKEN" \
  "http://192.168.1.136:8080/upload?name=$NAME&dest=$(enc 'Taaazzz/_apk')"
# -> {"ok":true,...}
curl -s -H "X-PhotoSync-Token: $TOKEN" "http://192.168.1.136:8080/files?path=$(enc 'Taaazzz/_apk')"
```

## 3) Publier la mise à jour in-app (`POST /apk-latest`) — ÉTAPE QUI DÉCLENCHE L'UPDATE

```bash
SIZE=$(stat -c%s "$APK")
curl -s -X POST -H "X-PhotoSync-Token: $TOKEN" "http://192.168.1.136:8080/apk-latest" \
  -H "Content-Type: application/json" -d "{
  \"version\": \"1.4.3\", \"versionCode\": 13,
  \"name\": \"PhotoSync-1.4.3.apk\",
  \"rel\": \"Taaazzz/_apk/PhotoSync-1.4.3.apk\",
  \"size\": $SIZE,
  \"notes\": \"Lecture des vidéos en plein écran\"
}"
# Vérifier :
curl -s -H "X-PhotoSync-Token: $TOKEN" "http://192.168.1.136:8080/apk-latest"
# -> le versionCode renvoyé DOIT être 13 (> celui installé) sinon l'app ne propose rien.
```
> Le `versionCode` annoncé ici DOIT correspondre à celui réellement buildé dans
> `app.json` (sinon, après install, l'app croira encore qu'une mise à jour existe).

## 4) (option) Nettoyer une ancienne version

```bash
curl -s -X POST -H "X-PhotoSync-Token: $TOKEN" \
  "http://192.168.1.136:8080/trash?path=$(enc 'Taaazzz/_apk/ancien.apk')"
# -> corbeille (.photosync-corbeille), récupérable, visible dans Maintenance > Corbeille
```

## (dépannage seulement) QR / lien direct

> ⚠️ À utiliser SEULEMENT si l'app installée ne peut pas faire l'update elle-même.
> En usage normal, l'utilisateur passe par la mise à jour in-app (voir tout en haut).

`GET /file` sert l'APK avec `Content-Disposition` (nom forcé) → le téléphone propose
« Installer » :
```
http://192.168.1.136:8080/file?path=Taaazzz/_apk/PhotoSync-1.4.3.apk&token=<token>
```
> `/file` répond seulement au **GET** (un `curl -I`/HEAD renvoie un faux 404).

## Diagnostic

- `GET /logs?n=120` : journal des requêtes reçues (méthode, chemin, statut, durée, IP).
- `GET /` : santé (`PhotoSync agent OK`).
