Files
lidar_rendu/docs/DEPLOY_WEBAPP.md

325 lines
15 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 : rebuild local (tuiles servies à la demande)
▼
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, servies à la demande par les webapp.
```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`).
- **Clé SSH côté hôte** : montée dans le conteneur pour le déploiement et le
`git pull` distant (le rsync de tuiles, supprimé, n'en avait pas besoin).
- **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 clé de l'hôte (`~/.ssh`, section 2). 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 à la demande depuis la machine de
traitement (section 4).
### 2. Accès SSH (déploiement)
Le rsync ayant été retiré, le SSH ne sert plus à ramener les tuiles (elles
sont servies à la demande, section 4). Il sert au déploiement et au support
: se connecter à la machine de traitement pour y inspecter `output/` ou y
déposer des tuiles manuellement.
```bash
ssh-keygen -t ed25519 # si pas encore de clé
ssh-copy-id lidar@<ip-machine> # compte sur la machine de traitement
ssh lidar@<ip-machine> exit # 1re connexion : enregistre known_hosts
```
La dernière commande évite le prompt « authenticity of host » en connexion
non interactive (le conteneur ne peut pas répondre).
Le cache de tuiles du Pi se remplit à la demande depuis la machine de
traitement ; ce compte SSH sert à l'inspecter ou à y déposer des tuiles
manuellement si besoin.
Avec `serve-webapp.sh` et `run.sh`, la clé SSH de l'hôte (`~/.ssh`, clé +
known_hosts) est montée 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). Le wrapper local
`ssh` (agent forwarding) simplifie ces connexions.
### 3. Configurer et lancer
Trois façons, au choix :
**a. `serve-webapp.sh` (recommandé)** — script de gestion de la stack. Il
pilote `docker compose` et charge automatiquement
`docker-compose.webapp.override.yml` s'il existe : les labels Traefik et le
volume réel de l'override sont préservés. La configuration vit dans
`webapp.env` (copie du modèle, non versionné) :
```bash
cp webapp.env.example webapp.env
nano webapp.env # LIDAR_WEBAPP_HOST (labels), jeton...
./serve-webapp.sh # démarre (build + up -d) et attend le serveur
```
Sous-commandes : `stop` (compose down), `restart` (rebuild + recréation),
`status` (conteneur + état du rebuild), `sync` (rebuild + vignettes),
`logs`. Le port hôte vient du mapping compose ; `WEBAPP_PORT` ne sert plus
qu'aux sondes locales quand le conteneur est arrêté.
**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 \
./run.sh --serve-webapp # port 8973, ou --serve-webapp 9000
```
Les tuiles sont servies à la demande : une image absente du cache local est
téléchargée depuis la machine de traitement au premier affichage, le cache
se remplit ainsi progressivement. Le bouton ↻, la fin d'un run ou `POST
/api/sync` déclenchent un rebuild de l'index et régénèrent les vignettes.
`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)
- le cache local se remplit à la demande depuis `LIDAR_GENERATION_URL` (pas
de `LIDAR_SYNC_CMD`)
Décommenter aussi le montage de la clé SSH (déploiement) :
```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).
### 5. Centrer la carte sur la position GPS (téléphone)
Le bouton **⌖** (barre d'outils de la carte, à côté du recadrage) recentre la
carte sur la position GPS du téléphone et y dessine un marqueur vert.
Les navigateurs n'exposent l'API Geolocation qu'en **contexte sécurisé**
(HTTPS). Servie en `http://<ip-pi>:8973`, la carte ne peut donc pas obtenir la
position sur téléphone : le bouton affiche alors « connexion sécurisée (HTTPS)
requise ».
**TLS terminé par le proxy (cas d'usage ici)** — le certificat est porté par
**Traefik** : le webapp sert du HTTP, Traefik expose `https://<domaine>` et y
applique son certificat (valable, sans avertissement). Le téléphone se connecte
sur l'URL Traefik → contexte sécurisé → le bouton ⌖ fonctionne.
La config Traefik (routage HTTPS) vit dans le fichier **override** local
`docker-compose.webapp.override.yml` (non versionné, sur le Pi) — Compose le
merge par-dessus `docker-compose.webapp.yml`. Le modèle versionné
`docker-compose.webapp.override.yml.example` fournit les labels (provider
Docker) qui routent `Host(\`${LIDAR_WEBAPP_HOST}\`)` vers le service, en
`websecure` (443) + TLS avec le certificat porté par Traefik. Sur le Pi :
```bash
cp docker-compose.webapp.override.yml.example docker-compose.webapp.override.yml
# adapter le domaine : LIDAR_WEBAPP_HOST=carte.example.fr (dans un .env à
# côté du compose, l'environnement, ou en dur dans la règle Host())
# docker compose n'auto-charge PAS un override au nom custom (seulement
# docker-compose.override.yml) → passer -f explicitement :
docker compose -f docker-compose.webapp.yml \
-f docker-compose.webapp.override.yml up -d --build
```
Adapter si le proxy n'est pas en `websecure` ou que le provider n'est pas
Docker : changer `entrypoints` / ajouter
`traefik.http.routers.lidar-webapp.tls.certresolver=…`, ou déclarer le
service directement dans la config Traefik. Le webapp est transparent au
proxy (URLs relatives) et remonte l'IP réelle via `X-Forwarded-For` pour la
restriction `LIDAR_REGEN_CIDR` : l'IP du téléphone doit y figurer pour lancer
des générations.
> **Sans Traefik** — servir la carte en HTTPS avec un certificat auto-signé :
> `./make-tls-cert.sh` crée `tls/webapp.{crt,key}`, à monter dans le
> conteneur avec `LIDAR_SSL_CERTFILE` / `LIDAR_SSL_KEYFILE` (le chemin
> `serve-webapp.sh` le fait automatiquement). Le téléphone accepte alors
> l'avertissement « certificat non fiable » une fois.
## Mise à jour
Sur le Pi, après un `git pull` :
```bash
docker compose -f docker-compose.webapp.yml up -d --build # rebuild + redémarrage
```
Avec `serve-webapp.sh` (l'override Traefik est chargé automatiquement s'il
existe) :
```bash
./serve-webapp.sh restart # rebuild de l'image + recréation du conteneur
```
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'un rebuild** : `curl http://<ip-pi>:8973/api/sync` → `running`,
`phase` (`index`) et `error` (dernière erreur). Les logs du conteneur :
`docker logs lidar-webapp` (ou `docker compose -f docker-compose.webapp.yml
logs -f webapp`).
- **`Permission denied (publickey)` en SSH** : 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` : les images produites
sont servies à la demande depuis la machine de traitement, le Pi régénère
vignettes/sous-tuiles/index.html, puis recharge la carte. Le bouton ↻
relance le même cycle à tout moment.
- 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` (rebuild de l'index), cache local à la demande, 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), configuration locale dans
`webapp.env` (ignoré par git).