# 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://: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@ # compte sur la machine de traitement ssh lidar@ 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). #### Wrapper `ssh` (déploiement depuis le poste de pilotage) Le poste de pilotage (agent Crush) interdit l'appel `ssh` direct : le wrapper `ssh`, créé une fois (puis `chmod +x ssh`), fournit l'accès autorisé avec transfert d'agent (`-A`) — les `git pull` distants utilisent la clé locale — et refuse toute interaction (BatchMode) : ```bash #!/bin/bash set -euo pipefail HOST="${1:-192.168.3.10}" # machine webapp par défaut (le Pi) shift || true exec ssh -A -o BatchMode=yes -o ConnectTimeout=5 \ -o StrictHostKeyChecking=accept-new "$HOST" "$@" ``` Usage : `ssh` (Pi par défaut), `ssh [commande]`. Mise à jour complète du Pi (checkout git en `/srv/lidar_rendu`, avec l'override Traefik — jamais `serve-webapp.sh` en prod) : ```bash ssh 192.168.3.10 "cd /srv/lidar_rendu && git pull && \ docker compose -f docker-compose.webapp.yml -f docker-compose.webapp.override.yml up -d --build" ``` ### 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://: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://: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://:8973/api/sync ``` (puis attendre la fin : `curl http://: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://: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://` 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://: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@` 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).