Files
lidar_rendu/docs/DEPLOY_WEBAPP.md
Antoine Jacquin 68272cabf0 Tuiles visibles en direct après régénération et déploiement webapp distante
- URLs images versionnées (?v=mtime) : une tuile recalculée change d'URL et
  force le rechargement navigateur (cache heuristique contourné), y compris
  en plein run via /api/tiles ; veille permanente 15 s sur la carte
- simulation locale du mode deux machines (docker-compose.local-2m.yml) :
  worker GPU :8974 + webapp légère :8973 au cache séparé output-webapp/
- override webapp pour le Pi 5 (192.168.3.3) : volume /srv/lidar/output,
  rsync vers le worker, labels Traefik (proxy/websecure/myresolver)
- purge 0,5 m : worker/process et politique générale passés à 0,2 m seul
- intègre le travail parallèle non commité : export mosaïque multi-dalles
  (export.py + /api/export), sous-tuilage intégral des couches, légendes
  VIZ_LEGENDS, docs et tests associés (213 tests verts)
2026-09-12 17:48:37 +02:00

280 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Déploiement webapp légère + machine de traitement
Architecture deux machines : la **webapp** (carte interactive, vignettes,
API) tourne sur un Raspberry Pi ; la **génération de tuiles** (téléchargement
IGN, PDAL, GPU) tourne sur une machine puissante. Les deux exécutent le même
code (`lidar_pipeline.webapp`), dans deux images Docker différentes.
```
Navigateur ──HTTP──▶ Raspberry Pi (image légère, Dockerfile.webapp)
│ sert carte + vignettes (régénérées localement)
│ /api/generate, /api/preview, /api/status
│ └─ transmis à ──▶ machine de traitement
│ /api/sync : rsync output/ ──◀── machine puissante
▼
Machine de traitement (image complète,
docker-compose.worker.yml service `worker`) :
téléchargement IGN + pipeline GPU (générateur de tuiles)
```
## Machine de traitement (générateur de tuiles)
Le service `worker` joue le rôle de générateur : il accepte les demandes
de génération envoyées par les webapp distantes (téléchargement IGN +
traitement PDAL/GPU) et expose les tuiles produites pour le rsync.
```bash
docker compose -f docker-compose.worker.yml up -d --build # API sur http://<ip-machine>:8973
docker compose -f docker-compose.worker.yml logs -f worker
```
Traitement batch ponctuel des dalles déjà présentes dans `input/` :
```bash
docker compose -f docker-compose.worker.yml run --rm --build process [-r 0.5,0.2 | --force]
```
Sur une machine sans GPU : retirer les lignes `gpus: all` (traitement CPU,
plus lent). Optionnel mais recommandé si le réseau n'est pas de confiance :
protéger l'API avec un jeton partagé — décommenter dans
`docker-compose.worker.yml` :
```yaml
environment:
- LIDAR_API_TOKEN=un-secret-à-partager
```
## Raspberry Pi (webapp légère)
### 0. Prérequis sur le Pi
- **Architecture** : image construite nativement sur la machine (ARM64 ou
x86_64, vérifier avec `uname -m`). Le build se fait sur le Pi lui-même
(`Dockerfile.webapp` : base `python:3.12-slim`, ~200 Mo, sans PDAL/GDAL).
- **Docker + plugin compose** : installation officielle
[docs.docker.com/engine/install](https://docs.docker.com/engine/install/)
(tester avec `docker compose version`).
- **rsync/ssh côté client** : inutile sur l'hôte — ils sont dans l'image —
mais la **clé SSH** de l'hôte est montée dans le conteneur.
- **Espace disque** : prévoir la taille du cache de tuiles (compter la
taille de `output/` sur la machine de traitement, ~quelques dizaines de
Mo par dalle et par résolution).
### 1. Copier le code sur le Pi (git clone)
```bash
git clone ssh://git@git.example.fr:2222/code_public/lidar_rendu.git lidar
cd lidar
```
Le clone SSH utilise la même clé que le rsync (`~/.ssh`, voir ci-dessous) ;
si le serveur git n'est pas encore connu, faire une première connexion pour
accepter son empreinte. `output/` et `input/` sont ignorés par git : le
dépôt ne contient que le code, le cache de tuiles se remplit ensuite par
rsync (premier chargement, section 4).
### 2. Accès SSH pour le rsync (la Pi tire les tuiles traitées)
```bash
ssh-keygen -t ed25519 # si pas encore de clé
ssh-copy-id lidar@<ip-machine> # compte lecture sur output/
ssh lidar@<ip-machine> exit # 1re connexion : enregistre known_hosts
```
La dernière commande évite le prompt « authenticity of host » pendant le
rsync (le conteneur ne peut pas répondre interactivement).
Sur la machine puissante, le dossier `output/` doit être lisible par ce
compte (ex. `/srv/lidar/output` si vous préférez un chemin dédié — adaptez
LIDAR_SYNC_CMD).
Avec `serve-webapp.sh` et `run.sh`, `~/.ssh` (clé + known_hosts) est monté
automatiquement en lecture seule dans le conteneur ; avec `docker compose`,
le montage équivalent est à décommenter dans `docker-compose.webapp.yml`
(voir l'option c ci-dessous).
### 3. Configurer et lancer
Trois façons, au choix :
**a. `serve-webapp.sh` (recommandé)** — script de gestion de la stack
(arrière-plan, redémarrage automatique au reboot, sous-commandes). La
configuration vit dans `webapp.env` (copie du modèle, non versionné) :
```bash
cp webapp.env.example webapp.env
nano webapp.env # LIDAR_GENERATION_URL, LIDAR_SYNC_CMD, jeton...
./serve-webapp.sh # démarre (build au premier lancement) et attend le serveur
```
Sous-commandes : `stop`, `restart` (relit `webapp.env`), `status` (conteneur
+ état de la sync), `sync` (force rsync + vignettes), `logs`, `update`
(`git pull` + rebuild + redémarrage). Port hôte via `WEBAPP_PORT` dans
`webapp.env` ou l'environnement (`WEBAPP_PORT=9000 ./serve-webapp.sh`).
**b. `run.sh`** — conteneur au premier plan (Ctrl-C arrête tout) : pratique
pour tester, les variables se passent depuis l'environnement hôte, `~/.ssh`
est monté automatiquement :
```bash
LIDAR_GENERATION_URL=http://192.168.1.50:8973 \
LIDAR_REMOTE_TOKEN=un-secret-à-partager \
LIDAR_SYNC_CMD="rsync -a --delete --exclude=*.tif --exclude=.generation* --exclude=index_thumbs --exclude=index_subtiles lidar@192.168.1.50:/srv/lidar/output/ /data/output/" \
LIDAR_AUTO_SYNC_SECONDS=600 \
./run.sh --serve-webapp # port 8973, ou --serve-webapp 9000
```
`LIDAR_AUTO_SYNC_SECONDS` entretient le cache tout seul : toutes les N
secondes, rsync ramène les nouvelles tuiles et les vignettes manquantes
sont régénérées (les mtimes évitent tout recalcul inutile). Sans cette
variable, le cache se rafraîchit à la demande : bouton ↻, fin d'un run,
ou `POST /api/sync`.
`LIDAR_REGEN_CIDR` restreint le lancement des générations (`POST
/api/generate` : zones, sélection, complétion, régénération) aux clients
dont l'IP est dans un des réseaux indiqués — liste de CIDR séparés par
virgules, chaîne vide pour lever la restriction. Défaut :
`127.0.0.0/8,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16` (boucle locale
+ plages privées RFC1918 : LAN, hôte Docker via la passerelle 172.x ; les
IP publiques restent refusées). L'IP est celle de la connexion (conservée
par le DNAT Docker pour les clients du LAN) ; derrière un reverse proxy
local (dans le réseau autorisé), `X-Forwarded-For` désigne le client réel.
Les autres clients consultent la carte normalement : les boutons de
génération sont masqués et l'API répond 403.
**c. `docker compose`** — éditer `docker-compose.webapp.yml` :
- `LIDAR_GENERATION_URL` : `http://<ip-machine>:8973`
- `LIDAR_REMOTE_TOKEN` : la valeur de `LIDAR_API_TOKEN` de la machine
(inutile si aucun token là-bas)
- `LIDAR_SYNC_CMD` : la commande rsync qui copie `output/` distant vers
`/data/output/` local, ex :
```yaml
- LIDAR_SYNC_CMD=rsync -a --delete --exclude=*.tif --exclude=.generation* --exclude=index_thumbs --exclude=index_subtiles lidar@192.168.1.50:/srv/lidar/output/ /data/output/
```
Décommenter aussi le montage de la clé SSH (rsync) :
```yaml
volumes:
- ./output:/data/output
- ~/.ssh:/home/lidar/.ssh:ro
```
Les vignettes (`index_thumbs/`, `index_subtiles/`) ne se synchronisent pas :
elles sont régénérées sur place par le Pi (`/api/sync` → `build_index`),
c'est le seul travail lourd qu'il fait. Exclure `*.tif` évite de copier des
intermédiaires éventuels ; les sidecars `output/DTM/*_method.txt` servent au
panneau d'infos et sont synchronisés.
```bash
mkdir -p output
docker compose -f docker-compose.webapp.yml up -d --build
```
La carte est sur `http://<ip-pi>:8973/`.
`run.sh` lance le conteneur au premier plan (Ctrl-C arrête tout) : pratique
pour tester. Pour une installation permanente, `serve-webapp.sh` (option a)
ou `docker compose` (option c) redémarrent la carte après un reboot du Pi
(`--restart unless-stopped`).
### 4. Premier chargement
Si aucune tuile n'a encore été synchronisée, déclencher une première fois :
```bash
curl -X POST http://<ip-pi>:8973/api/sync
```
(puis attendre la fin : `curl http://<ip-pi>:8973/api/sync` → `"running":
false`.) Le bouton ↻ de la carte fait la même chose (sync + vignettes).
## Mise à jour
Avec `serve-webapp.sh`, une seule commande (`git pull`, rebuild de l'image,
redémarrage) :
```bash
./serve-webapp.sh update
```
Sinon, sur le Pi, après un `git pull` (ou rsync du code) :
```bash
docker compose -f docker-compose.webapp.yml up -d --build # rebuild + redémarrage
```
Avec `run.sh --serve-webapp`, l'image est reconstruite automatiquement à
chaque lancement ; il suffit de relancer la commande. Le cache de tuiles
(`output/`) n'est pas affecté par les mises à jour d'image.
## Dépannage
- **État d'une sync** : `curl http://<ip-pi>:8973/api/sync` → `running`,
`phase` (`sync` puis `index`) et `error` (dernière erreur, ex. fin de
journal rsync en cas d'échec). Les logs du conteneur :
`docker logs lidar-webapp` (ou `docker compose -f docker-compose.webapp.yml
logs -f webapp`).
- **`Permission denied (publickey)` pendant la sync** : refaire
`ssh-copy-id lidar@<ip-machine>` et une connexion manuelle pour
renseigner `known_hosts` ; vérifier que `~/.ssh` est monté dans le
conteneur (automatique avec `run.sh`, à décommenter en compose).
- **Boutons de génération masqués / `403` sur `/api/generate`** : l'IP du
client n'est dans aucun des réseaux de `LIDAR_REGEN_CIDR` (défaut :
localhost + plages privées). Adapter la variable ou la vider pour lever
la restriction.
- **« Machine de traitement injoignable »** : vérifier le service `worker`
(`docker compose -f docker-compose.worker.yml logs -f worker`) et
`LIDAR_GENERATION_URL` (IP + port 8973). La consultation de la carte
reste possible avec le cache local.
- **Carte vide après sync** : attendre la fin du rebuild (`GET /api/sync` →
`"running": false`, ou `./serve-webapp.sh status`) puis recharger la
page ; le bouton ↻ de la carte enchaîne sync + vignettes
automatiquement.
- **Port 8973 déjà pris** : `WEBAPP_PORT=9000 ./serve-webapp.sh` (ou la
variable dans `webapp.env`), `--serve-webapp 9000` avec `run.sh`, ou
changer le mapping `ports:` en compose (le conteneur écoute toujours sur
8973).
## Fonctionnement
- Dessiner une zone (bouton « + Zone ») sur la carte du Pi envoie la demande
à la machine de traitement, qui télécharge les dalles IGN puis les traite.
La file de génération (progression tuile par tuile) est lue depuis la
machine distante en direct.
- À la fin du run, le navigateur appelle `/api/sync` : rsync ramène les
images, le Pi régénère vignettes/sous-tuiles/index.html, puis recharge la
carte. Le bouton ↻ relance le même cycle à tout moment. Avec
`LIDAR_AUTO_SYNC_SECONDS`, le conteneur webapp seul maintient aussi son
cache périodiquement (utile quand personne ne consulte la carte).
- Sans machine de traitement configurée, `./run.sh --serve-webapp` sert la
carte en lecture seule depuis `output/` (cache figé, aucun traitement
possible depuis l'interface).
- **Autonomie** : une fois les tuiles chargées dans le cache local, la
webapp fonctionne seule — Leaflet est vendorisé dans `assets/vendor/`
(aucun CDN), tuiles/vignettes/infos sont lues dans `output/`, et un
backend injoignable masque simplement les boutons de génération sans
affecter la consultation.
- Machine de traitement seule (sans Pi) : rien ne change, `--serve` /
`docker compose up -d --build serve` fonctionne comme avant en local (tout-en-un), et `docker-compose.worker.yml` installe le générateur seul.
- Si la machine de traitement est éteinte, la carte du Pi reste consultable
(données synchronisées) ; seuls les nouveaux traitements sont indisponibles
(message « machine de traitement injoignable »).
## Références
- `lidar_pipeline/webapp.py` : proxy distant (`LIDAR_GENERATION_URL`),
`/api/sync`, cache local périodique (`LIDAR_AUTO_SYNC_SECONDS`), jeton
`LIDAR_API_TOKEN`/`LIDAR_REMOTE_TOKEN`, restriction des générations au
réseau local (`LIDAR_REGEN_CIDR`).
- `lidar_pipeline/index.py` : vignettes/index sans GDAL (pyproj ou repli
affine), recharge après sync.
- `docker-compose.worker.yml` : générateur de tuiles (machine puissante).
- `Dockerfile.webapp`, `docker-compose.webapp.yml` : webapp légère ARM64
(FastAPI + Pillow AVIF natif + pyproj, ~200 Mo).
- `serve-webapp.sh`, `webapp.env.example` : gestion de la stack webapp sur
le Pi (démarrage/arrêt/sync/logs/update), configuration locale dans
`webapp.env` (ignoré par git).