Add serve-webapp.sh to manage the webapp stack on the lightweight machine
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.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,6 +42,7 @@ htmlcov/
|
||||
# Fichiers de configuration locaux
|
||||
.env
|
||||
.env.local
|
||||
webapp.env
|
||||
|
||||
# Données et artefacts internes (jamais dans le dépôt)
|
||||
data/
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
- build: `docker build -t lidar-lidar .`
|
||||
- build webapp légère (Raspberry Pi, déploiement 2 machines — cf. `docs/DEPLOY_WEBAPP.md`): `docker compose -f docker-compose.webapp.yml up -d --build` (image `Dockerfile.webapp`, sans PDAL/GPU)
|
||||
- build générateur de tuiles (machine de traitement): `docker compose -f docker-compose.worker.yml up -d --build` (service `worker`, API pour les webapp distantes)
|
||||
- stack webapp (machine légère): `./serve-webapp.sh [start|stop|restart|status|sync|logs|update]`, config dans `webapp.env` (modèle `webapp.env.example`, ignoré par git)
|
||||
- test all: `./run.sh --test` (rebuild automatique de l'image avant les tests ; en `docker run` direct, rebuild manuellement d'abord)
|
||||
- test file: `docker run --rm lidar-lidar python3 -m pytest -v --pyargs lidar_pipeline.tests.<module>`
|
||||
- test case: `docker run --rm lidar-lidar python3 -m pytest -v --pyargs lidar_pipeline.tests.<module>::<TestClass>::<test_method>`
|
||||
|
||||
@ -88,17 +88,32 @@ 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).
|
||||
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
|
||||
|
||||
Deux façons, au choix :
|
||||
Trois 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`
|
||||
**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
|
||||
@ -124,7 +139,7 @@ DNAT Docker pour les clients du LAN) ; derrière un reverse proxy local
|
||||
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` :
|
||||
**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
|
||||
@ -158,8 +173,9 @@ 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).
|
||||
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
|
||||
|
||||
@ -174,7 +190,14 @@ 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) :
|
||||
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
|
||||
@ -203,10 +226,13 @@ chaque lancement ; il suffit de relancer la commande. Le cache de tuiles
|
||||
`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).
|
||||
`"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
|
||||
|
||||
@ -244,3 +270,6 @@ chaque lancement ; il suffit de relancer la commande. Le cache de tuiles
|
||||
- `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).
|
||||
|
||||
180
serve-webapp.sh
Executable file
180
serve-webapp.sh
Executable file
@ -0,0 +1,180 @@
|
||||
#!/bin/bash
|
||||
# Stack webapp LiDAR — gestion du conteneur carte interactive sur la machine
|
||||
# légère (Raspberry Pi). Cf. docs/DEPLOY_WEBAPP.md.
|
||||
#
|
||||
# Configuration : variables dans webapp.env (modèle : webapp.env.example,
|
||||
# non versionné) ou exportées dans l'environnement :
|
||||
# WEBAPP_PORT port hôte (défaut 8973)
|
||||
# LIDAR_GENERATION_URL machine de traitement (ex. http://192.168.1.50:8973)
|
||||
# LIDAR_REMOTE_TOKEN jeton si la machine exige LIDAR_API_TOKEN
|
||||
# LIDAR_SYNC_CMD rsync qui remplit le cache local de tuiles
|
||||
# LIDAR_AUTO_SYNC_SECONDS resynchronisation périodique (défaut 0 = off)
|
||||
# LIDAR_REGEN_CIDR réseau autorisé à lancer les générations
|
||||
# (défaut 192.168.1.0/24, vide = restriction levée)
|
||||
#
|
||||
# Usage :
|
||||
# ./serve-webapp.sh [start] démarrer (arrière-plan, redémarrage auto)
|
||||
# ./serve-webapp.sh stop arrêter et supprimer le conteneur
|
||||
# ./serve-webapp.sh restart redémarrer (relit webapp.env)
|
||||
# ./serve-webapp.sh status état du conteneur et de la dernière sync
|
||||
# ./serve-webapp.sh sync forcer sync rsync + régénération des vignettes
|
||||
# ./serve-webapp.sh logs suivre les logs du conteneur
|
||||
# ./serve-webapp.sh update git pull, rebuild de l'image, redémarrer
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
IMAGE="lidar-webapp"
|
||||
CONTAINER="lidar-webapp"
|
||||
WEBAPP_PORT="${WEBAPP_PORT:-8973}"
|
||||
BASE_URL="http://127.0.0.1:${WEBAPP_PORT}"
|
||||
|
||||
# Configuration locale (non versionnée) : webapp.env surcharge l'environnement
|
||||
if [ -f "${SCRIPT_DIR}/webapp.env" ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
. "${SCRIPT_DIR}/webapp.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
CMD="${1:-start}"
|
||||
|
||||
build_image() {
|
||||
echo "Build de l'image webapp (cache Docker)..."
|
||||
if ! docker build -f "${SCRIPT_DIR}/Dockerfile.webapp" -t "$IMAGE" "$SCRIPT_DIR" > /tmp/lidar_webapp_build.log 2>&1; then
|
||||
cat /tmp/lidar_webapp_build.log >&2
|
||||
echo "Échec du build (journal : /tmp/lidar_webapp_build.log)" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
lan_ip() {
|
||||
hostname -I 2> /dev/null | awk '{print $1}'
|
||||
}
|
||||
|
||||
cmd_start() {
|
||||
mkdir -p "${SCRIPT_DIR}/output"
|
||||
if curl -fsS "${BASE_URL}/api/status" > /dev/null 2>&1; then
|
||||
echo "Le port ${WEBAPP_PORT} répond déjà (webapp déjà lancée ?)." >&2
|
||||
echo "Arrêter l'existant, ou relancer avec un autre port : WEBAPP_PORT=9000 $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! docker image inspect "$IMAGE" > /dev/null 2>&1; then
|
||||
build_image
|
||||
fi
|
||||
ENV_ARGS=()
|
||||
if [ -n "${LIDAR_GENERATION_URL:-}" ]; then
|
||||
ENV_ARGS+=(-e LIDAR_GENERATION_URL="$LIDAR_GENERATION_URL")
|
||||
fi
|
||||
if [ -n "${LIDAR_REMOTE_TOKEN:-}" ]; then
|
||||
ENV_ARGS+=(-e LIDAR_REMOTE_TOKEN="$LIDAR_REMOTE_TOKEN")
|
||||
fi
|
||||
if [ -n "${LIDAR_SYNC_CMD:-}" ]; then
|
||||
ENV_ARGS+=(-e LIDAR_SYNC_CMD="$LIDAR_SYNC_CMD")
|
||||
fi
|
||||
if [ -n "${LIDAR_AUTO_SYNC_SECONDS:-}" ]; then
|
||||
ENV_ARGS+=(-e LIDAR_AUTO_SYNC_SECONDS="$LIDAR_AUTO_SYNC_SECONDS")
|
||||
fi
|
||||
# Définie même vide (= restriction levée) ; absente = défaut webapp.py
|
||||
if [ -n "${LIDAR_REGEN_CIDR+x}" ]; then
|
||||
ENV_ARGS+=(-e LIDAR_REGEN_CIDR="${LIDAR_REGEN_CIDR:-}")
|
||||
fi
|
||||
# Clé SSH du rsync (LIDAR_SYNC_CMD) : montée en lecture seule si présente
|
||||
SSH_ARGS=()
|
||||
if [ -d "${HOME}/.ssh" ]; then
|
||||
SSH_ARGS+=(-v "${HOME}/.ssh:/home/lidar/.ssh:ro")
|
||||
fi
|
||||
docker rm -f "$CONTAINER" > /dev/null 2>&1 || true
|
||||
docker run -d --init \
|
||||
--name "$CONTAINER" \
|
||||
--restart unless-stopped \
|
||||
--user 1000:1000 \
|
||||
-p "${WEBAPP_PORT}:8973" \
|
||||
-v "${SCRIPT_DIR}/output:/data/output" \
|
||||
"${SSH_ARGS[@]}" \
|
||||
-e LIDAR_OUTPUT_DIR=/data/output \
|
||||
"${ENV_ARGS[@]}" \
|
||||
"$IMAGE" > /dev/null
|
||||
echo -n "Attente du serveur sur le port ${WEBAPP_PORT}..."
|
||||
UP=0
|
||||
for _ in $(seq 1 30); do
|
||||
if curl -fsS "${BASE_URL}/api/status" > /dev/null 2>&1; then
|
||||
UP=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$UP" != "1" ]; then
|
||||
echo " KO (voir ./serve-webapp.sh logs)" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " OK"
|
||||
echo "============================================"
|
||||
echo " Carte LiDAR — webapp (${CONTAINER})"
|
||||
echo "============================================"
|
||||
echo " URL : ${BASE_URL}/"
|
||||
if [ -n "$(lan_ip)" ]; then
|
||||
echo " URL (LAN) : http://$(lan_ip):${WEBAPP_PORT}/"
|
||||
fi
|
||||
echo " Génération distante: ${LIDAR_GENERATION_URL:-non configurée}"
|
||||
if [ -n "${LIDAR_SYNC_CMD:-}" ]; then
|
||||
if [ -n "${LIDAR_AUTO_SYNC_SECONDS:-}" ] && [ "${LIDAR_AUTO_SYNC_SECONDS}" != "0" ]; then
|
||||
echo " Cache local tuiles : rsync (auto toutes les ${LIDAR_AUTO_SYNC_SECONDS}s)"
|
||||
else
|
||||
echo " Cache local tuiles : rsync (à la demande)"
|
||||
fi
|
||||
else
|
||||
echo " Cache local tuiles : désactivé (LIDAR_SYNC_CMD)"
|
||||
fi
|
||||
echo "============================================"
|
||||
}
|
||||
|
||||
cmd_stop() {
|
||||
docker rm -f "$CONTAINER" > /dev/null 2>&1 || true
|
||||
echo "Conteneur ${CONTAINER} arrêté."
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
if docker ps --format '{{.Names}}' 2> /dev/null | grep -qx "$CONTAINER"; then
|
||||
echo "Conteneur : ${CONTAINER} en cours d'exécution ($(docker ps --filter name="^${CONTAINER}$" --format '{{.Status}}'))"
|
||||
else
|
||||
echo "Conteneur : ${CONTAINER} arrêté"
|
||||
fi
|
||||
if curl -fsS "${BASE_URL}/api/sync" 2> /dev/null; then
|
||||
echo ""
|
||||
else
|
||||
echo "Webapp : injoignable sur ${BASE_URL}"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_sync() {
|
||||
CODE="$(curl -s -o /dev/null -w '%{http_code}' -X POST "${BASE_URL}/api/sync" || true)"
|
||||
case "$CODE" in
|
||||
200) echo "Sync lancée (suivi : ./serve-webapp.sh status, logs : ./serve-webapp.sh logs)" ;;
|
||||
409) echo "Une sync est déjà en cours (suivi : ./serve-webapp.sh status)" ;;
|
||||
*)
|
||||
echo "Échec : HTTP ${CODE:-pas de réponse} (webapp injoignable sur ${BASE_URL} ?)" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
cmd_update() {
|
||||
echo "Mise à jour du code (git pull)..."
|
||||
git -C "$SCRIPT_DIR" pull --ff-only
|
||||
build_image
|
||||
cmd_start
|
||||
}
|
||||
|
||||
case "$CMD" in
|
||||
start) cmd_start ;;
|
||||
stop) cmd_stop ;;
|
||||
restart) cmd_stop; cmd_start ;;
|
||||
status) cmd_status ;;
|
||||
sync) cmd_sync ;;
|
||||
logs) exec docker logs -f "$CONTAINER" ;;
|
||||
update) cmd_update ;;
|
||||
*)
|
||||
echo "Usage : $0 [start|stop|restart|status|sync|logs|update]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
25
webapp.env.example
Normal file
25
webapp.env.example
Normal file
@ -0,0 +1,25 @@
|
||||
# Configuration de la stack webapp — copier en webapp.env et adapter.
|
||||
# webapp.env est ignoré par git (jetons, chemins locaux).
|
||||
# Syntaxe shell simple : KEY=valeur (les valeurs avec espaces entre guillemets).
|
||||
|
||||
# Port hôte de la carte (défaut 8973)
|
||||
#WEBAPP_PORT=8973
|
||||
|
||||
# Machine de traitement (service worker de docker-compose.worker.yml).
|
||||
# Commenté : consultation autonome du cache local, aucune génération.
|
||||
#LIDAR_GENERATION_URL=http://192.168.1.50:8973
|
||||
|
||||
# Jeton partagé si la machine de traitement définit LIDAR_API_TOKEN
|
||||
#LIDAR_REMOTE_TOKEN=un-secret-à-partager
|
||||
|
||||
# rsync qui ramène les tuiles traitées vers le cache local.
|
||||
# Les vignettes (index_thumbs/, index_subtiles/) sont régénérées localement,
|
||||
# ne pas les synchroniser.
|
||||
#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/"
|
||||
|
||||
# Cache auto-entretenu : resync + vignettes toutes les N secondes (0 = off)
|
||||
#LIDAR_AUTO_SYNC_SECONDS=600
|
||||
|
||||
# Réseau autorisé à lancer les générations depuis la carte
|
||||
# (défaut 192.168.1.0/24 ; vide pour lever la restriction)
|
||||
#LIDAR_REGEN_CIDR=192.168.1.0/24
|
||||
Reference in New Issue
Block a user