Files
lidar_rendu/docs/DEPLOY_WEBAPP.md
Antoine Jacquin 23969c9e14 Add Leaflet interactive map, tile generator compose, auto-sync cache, deploy docs
index.py rewritten as a continuous Leaflet map: rotated L93 tiles, stackable
visualization layers (per-layer opacity, drag-reorder persisted in
localStorage), tile info panel, live rebuild after each tile during a run.
Leaflet is vendored in assets/vendor/ so the map works fully offline;
georeferencing falls back rasterio -> pyproj -> affine so the lightweight
webapp (no GDAL) is supported.

docker-compose.worker.yml adds the tile generator service (full image + GPU)
that remote webapps call via LIDAR_GENERATION_URL, plus a one-shot process
profile. webapp.py gains LIDAR_AUTO_SYNC_SECONDS periodic cache refresh and
LIDAR_REGEN_CIDR restricting generation to the local network. run.sh
--serve-webapp now mounts ~/.ssh read-only so the rsync sync works.

docs/DEPLOY_WEBAPP.md completed for Raspberry Pi deployment: prerequisites,
git clone install, SSH key setup, first sync, update procedure and
troubleshooting.
2026-09-04 21:36:14 +02:00

247 lines
11 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 `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 b ci-dessous).
### 3. Configurer et lancer
Deux façons, au choix :
**a. `run.sh` (le plus simple)** — conteneur webapp seul, cache local des
tuiles ; 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, complétion, régénération) aux clients dont l'IP
est dans le réseau indiqué — défaut `192.168.1.0/24`, chaîne vide pour
lever la restriction. 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.
**b. `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, préférer `docker compose`
(`restart: unless-stopped` : la carte revient après un reboot du Pi).
### 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
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 pas dans `LIDAR_REGEN_CIDR` (défaut `192.168.1.0/24`).
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`) puis recharger la page ; le bouton ↻ de la carte
enchaîne sync + vignettes automatiquement.
- **Port 8973 déjà pris** : `--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).