Single command to start the interactive map container in the background (auto-restart after reboot), with stop/restart/status/sync/logs/update subcommands. Configuration lives in webapp.env (template provided, git-ignored): remote generation URL, shared token, rsync cache command, periodic sync and allowed network. The SSH key is mounted automatically for rsync, port conflicts are detected before launch, and update pulls the repo, rebuilds the image and restarts. Deploy doc updated: the script is now the recommended install path on the Raspberry Pi, alongside run.sh (foreground) and docker compose.
276 lines
12 KiB
Markdown
276 lines
12 KiB
Markdown
# 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, 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.
|
||
|
||
**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 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`, 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).
|