From 9546173c0731b3aad2a651e92035a0f4daf2f03f Mon Sep 17 00:00:00 2001 From: Antoine Jacquin Date: Sat, 19 Sep 2026 11:05:16 +0200 Subject: [PATCH] =?UTF-8?q?Pr=C3=A9r=C3=A9glage=20carte=20par=20d=C3=A9fau?= =?UTF-8?q?t=20et=20serve-webapp.sh=20compatible=20Traefik?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 2 +- docs/DEPLOY_WEBAPP.md | 27 +++-- lidar_pipeline/index.py | 39 ++++-- lidar_pipeline/tests/test_index.py | 13 +- lidar_pipeline/webapp.py | 7 +- serve-webapp.sh | 188 ++++++++++++----------------- webapp.env.example | 13 +- 7 files changed, 144 insertions(+), 145 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7304721..f7057a9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ - 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) - simulation locale du mode deux machines : `docker compose -f docker-compose.local-2m.yml up -d --build` — worker GPU sur :8974 + webapp légère sur :8973 avec son PROPRE cache `output-webapp/` peuplé à la demande depuis le worker. Permet de rebuild l'interface sans toucher au worker, et réciproquement. Résolution 0,2 m uniquement (GENERATE_RESOLUTIONS). -- stack webapp (machine légère): `./serve-webapp.sh [start|stop|restart|status|sync|logs]`, config dans `webapp.env` (modèle `webapp.env.example`, ignoré par git) ; mise à jour = `git pull` puis rebuild image + `restart` (cf. `docs/DEPLOY_WEBAPP.md`). **ATTENTION Pi de prod (192.168.3.10) : la webapp y tourne via `docker compose -f docker-compose.webapp.yml -f docker-compose.webapp.override.yml up -d --build` (labels Traefik `lidar.example.fr` + volume réel `/srv/lidar/output`) — NE PAS utiliser `serve-webapp.sh` là-bas : il recrée le conteneur sans labels (route publique 404) et sur le mauvais volume `./output`.** +- stack webapp (machine légère): `./serve-webapp.sh [start|stop|restart|status|sync|logs]`, config dans `webapp.env` (modèle `webapp.env.example`, ignoré par git) ; mise à jour = `git pull` puis `restart` (rebuild inclus). Le script pilote `docker compose` et charge automatiquement `docker-compose.webapp.override.yml` s'il existe — utilisable sur le Pi de prod (192.168.3.10) : labels Traefik `lidar.example.fr` + volume réel `/srv/lidar/output` de l'override préservés. - 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.` - test case: `docker run --rm lidar-lidar python3 -m pytest -v --pyargs lidar_pipeline.tests.::::` diff --git a/docs/DEPLOY_WEBAPP.md b/docs/DEPLOY_WEBAPP.md index 298ca1d..f39e568 100644 --- a/docs/DEPLOY_WEBAPP.md +++ b/docs/DEPLOY_WEBAPP.md @@ -103,20 +103,22 @@ avec `docker compose`, le montage équivalent est à décommenter dans 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é) : +**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_GENERATION_URL, jeton... -./serve-webapp.sh # démarre (build au premier lancement) et attend le serveur +nano webapp.env # LIDAR_WEBAPP_HOST (labels), jeton... +./serve-webapp.sh # démarre (build + up -d) et attend le serveur ``` -Sous-commandes : `stop`, `restart` (relit `webapp.env`), `status` (conteneur -+ état du rebuild), `sync` (rebuild + vignettes), `logs`. Port hôte via -`WEBAPP_PORT` dans `webapp.env` ou l'environnement (`WEBAPP_PORT=9000 -./serve-webapp.sh`). +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` @@ -244,12 +246,11 @@ Sur le Pi, après un `git pull` : docker compose -f docker-compose.webapp.yml up -d --build # rebuild + redémarrage ``` -Avec `serve-webapp.sh`, l'image est à reconstruire explicitement avant de -redémarrer (le script ne reconstruit qu'au premier lancement) : +Avec `serve-webapp.sh` (l'override Traefik est chargé automatiquement s'il +existe) : ```bash -docker build -f Dockerfile.webapp -t lidar-webapp . -./serve-webapp.sh restart +./serve-webapp.sh restart # rebuild de l'image + recréation du conteneur ``` Avec `run.sh --serve-webapp`, l'image est reconstruite automatiquement à diff --git a/lidar_pipeline/index.py b/lidar_pipeline/index.py index 9a0c203..08f8534 100644 --- a/lidar_pipeline/index.py +++ b/lidar_pipeline/index.py @@ -205,19 +205,23 @@ VIZ_LEGENDS = { # Couches activées par défaut à l'ouverture de la carte : seules celles-ci # sont allumées, toutes les autres sont désactivées (mais restent accessibles -# via le panneau). Ordre = pile basse → haute. -DEFAULT_LAYERS = ('slope', 'aspect', 'positive_openness') +# via le panneau). Ordre = pile basse → haute : une couche opaque (opacité 1) +# doit rester en bas pour ne pas masquer les autres. +DEFAULT_LAYERS = ('positive_openness', 'aspect') + +# Opacité par défaut des couches allumées (0–1) ; les absentes restent à 1. +DEFAULT_OPACITY = {'aspect': 0.6} # Couche « principale » (mise en avant ; index_tiles.json + affichage en # direct de la webapp). DEFAULT_VIZ = DEFAULT_LAYERS[0] -# Restriction du panneau de couches : seule la sélection DEFAULT_LAYERS est +# Restriction du panneau de couches : seule la sélection ci-dessous est # proposée (couches conservées ; les autres ne sont plus ni affichées ni # référencées). None = toutes les visualisations présentes sur disque. # Le sélecteur de génération/régénération est piloté par le même registre -# (voir _render_html). -PANEL_VIZ = DEFAULT_LAYERS +# (voir _render_html). La pente y reste proposée mais éteinte par défaut. +PANEL_VIZ = ('slope', 'aspect', 'positive_openness') # Correspondance mot-clé de fichier de sortie → nom d'étape --only du pipeline # (les trois visualisations dont le nom de sortie diffère du nom d'étape, @@ -1118,6 +1122,7 @@ def build_index(output_dir, output_format='avif'): 'viz_meta': {k: {'label': VIZ_LABELS.get(k, k)} for k in ordered_viz}, 'stats': {'default_viz': default_viz, 'default_layers': default_layers_present(all_viz_keys), + 'default_opacity': dict(DEFAULT_OPACITY), 'n_tiles': len(display_tiles)}, }, ensure_ascii=False), encoding='utf-8') @@ -1233,6 +1238,7 @@ def _render_html(tiles, global_bbox, all_viz_keys, output_format): stats_json = _json_for_script( {'default_viz': default_viz, 'default_layers': default_layers_present(all_viz_keys), + 'default_opacity': dict(DEFAULT_OPACITY), 'n_tiles': len(tiles)}, ) @@ -1930,7 +1936,7 @@ _APP_JS = r"""'use strict'; // --- Données embarquées par index.py --- // TILES : tuiles affichables (corners WGS84, viz {thumb, full}, meta) // VIZ_META : {clé: {label}} des visualisations disponibles -// STATS : {default_viz, n_tiles} +// STATS : {default_viz, default_layers, default_opacity, n_tiles} const viewport = document.getElementById('viewport'); @@ -1998,9 +2004,26 @@ function defaultLayers() { ? STATS.default_layers : ((STATS.default_viz && VIZ_META[STATS.default_viz]) ? [STATS.default_viz] : [order[0]]); - const onSet = new Set(list.filter(k => VIZ_META[k])); + const defOrder = list.filter(k => VIZ_META[k]); + // La pile respecte l'ordre de default_layers (bas → haut) : une couche + // opaque (opacité 1) doit rester sous les semi-transparentes. + for (let i = 1; i < defOrder.length; i++) { + const bi = order.indexOf(defOrder[i - 1]), ai = order.indexOf(defOrder[i]); + if (bi >= 0 && ai >= 0 && bi > ai) { + order.splice(bi, 1); + order.splice(order.indexOf(defOrder[i]), 0, defOrder[i - 1]); + } + } + // Opacités par défaut (STATS.default_opacity) ; absentes → 1. + const defOp = (STATS.default_opacity && typeof STATS.default_opacity === 'object') + ? STATS.default_opacity : {}; + const onSet = new Set(defOrder); const on = {}, opacity = {}; - for (const k of order) { on[k] = onSet.has(k); opacity[k] = 1; } + for (const k of order) { + on[k] = onSet.has(k); + const op = Number(defOp[k]); + opacity[k] = Number.isNaN(op) ? 1 : Math.min(1, Math.max(0, op)); + } return { order, on, opacity, osm: { on: true, opacity: 0.85, dark: true } }; } diff --git a/lidar_pipeline/tests/test_index.py b/lidar_pipeline/tests/test_index.py index 5d66809..2305504 100644 --- a/lidar_pipeline/tests/test_index.py +++ b/lidar_pipeline/tests/test_index.py @@ -375,10 +375,17 @@ def test_subtiles_cover_all_layers(tmp_path): def test_panel_restricted_to_kept_layers(): - """Le panneau est restreint aux couches conservées (DEFAULT_LAYERS).""" - from lidar_pipeline.index import PANEL_VIZ, DEFAULT_LAYERS, KEYWORD_TO_STEP + """Le panneau est restreint aux couches conservées (PANEL_VIZ). + + Les couches allumées par défaut (DEFAULT_LAYERS) y figurent toutes — le + panneau peut en proposer davantage (ex. pente éteinte par défaut), et + chaque opacité par défaut vise une couche allumée. + """ + from lidar_pipeline.index import (PANEL_VIZ, DEFAULT_LAYERS, DEFAULT_OPACITY, + KEYWORD_TO_STEP) from lidar_pipeline.pipeline import VIZ_STEPS - assert PANEL_VIZ == DEFAULT_LAYERS + assert set(DEFAULT_LAYERS) <= set(PANEL_VIZ) + assert set(DEFAULT_OPACITY) <= set(DEFAULT_LAYERS) # Chaque couche conservée correspond à une étape --only valide steps = {name for name, _ in VIZ_STEPS} for key in PANEL_VIZ: diff --git a/lidar_pipeline/webapp.py b/lidar_pipeline/webapp.py index 1cd0c32..39f5497 100644 --- a/lidar_pipeline/webapp.py +++ b/lidar_pipeline/webapp.py @@ -1054,7 +1054,12 @@ def _merge_remote_index(): path.write_text(json.dumps({ "tiles": merged, "viz_meta": viz_meta, - "stats": remote.get("stats") or data.get("stats") or {}, + # Les stats décrivent les réglages de l'INTERFACE (couches par + # défaut) : elles viennent du rebuild local, fait avec le code de + # cette webapp — celles du worker ne servent que de repli tant + # qu'aucun index local n'existe (sinon une machine mise à jour avant + # l'autre afficherait les vieux réglages du worker). + "stats": data.get("stats") or remote.get("stats") or {}, }, ensure_ascii=False), encoding="utf-8") # Coquille HTML : la liste embarquée doit être la même (fusionnée). Le # JSON ne contient jamais de « ; » : la première séquence « ]; » après diff --git a/serve-webapp.sh b/serve-webapp.sh index acf63b2..4fa1bef 100755 --- a/serve-webapp.sh +++ b/serve-webapp.sh @@ -1,35 +1,39 @@ #!/bin/bash -# Stack webapp LiDAR — gestion du conteneur carte interactive sur la machine -# légère (Raspberry Pi). Cf. docs/DEPLOY_WEBAPP.md. +# Stack webapp LiDAR — gestion de la stack compose sur la machine légère +# (Raspberry Pi). Cf. docs/DEPLOY_WEBAPP.md. +# +# Pilote docker compose (jamais docker run direct, qui ignorerait labels et +# volumes de l'override) : +# - docker-compose.webapp.yml (image légère Dockerfile.webapp, port 8973) ; +# - docker-compose.webapp.override.yml s'il existe (non versionné : labels +# Traefik, volume réel, environnement local) — chargé automatiquement : +# la route publique HTTPS et le cache de tuiles réels sont préservés. # # 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_REGEN_CIDR réseau autorisé à lancer les générations -# (défaut 192.168.1.0/24, vide = restriction levée) +# non versionné) ou exportées — elles alimentent l'interpolation compose +# (${LIDAR_WEBAPP_HOST} des labels Traefik, etc.) ; l'environnement du +# CONTENEUR vit dans les fichiers compose / override. # -# GPS sur téléphone (optionnel) : l'API Geolocation exige une connexion -# sécurisée. ./make-tls-cert.sh crée un certificat auto-signé dans ./tls ; -# dès qu'il est présent, ce script sert la carte en HTTPS (https://:port) -# et le bouton ⌖ « centrer sur la position GPS » fonctionne sur téléphone -# (accepter l'avertissement « certificat non fiable » une fois). +# HTTPS : terminé par Traefik (override). Sans Traefik, monter ./tls via un +# override local avec LIDAR_SSL_CERTFILE / LIDAR_SSL_KEYFILE (cf. +# make-tls-cert.sh) — ce script ne gère plus le certificat auto-signé. # # 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 [start] démarrer (build + arrière-plan, redémarrage auto) +# ./serve-webapp.sh stop arrêter et supprimer la stack (cache conservé) +# ./serve-webapp.sh restart reconstruire l'image et recréer le conteneur # ./serve-webapp.sh status état du conteneur et de la dernière sync # ./serve-webapp.sh sync forcer un rebuild + régénération des vignettes # ./serve-webapp.sh logs suivre les logs du conteneur set -e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -IMAGE="lidar-webapp" CONTAINER="lidar-webapp" +COMPOSE_BASE="docker-compose.webapp.yml" +COMPOSE_OVERRIDE="docker-compose.webapp.override.yml" # Configuration locale (non versionnée) : webapp.env surcharge l'environnement +# (sert à l'interpolation ${...} des fichiers compose). if [ -f "${SCRIPT_DIR}/webapp.env" ]; then set -a # shellcheck disable=SC1091 @@ -38,151 +42,109 @@ if [ -f "${SCRIPT_DIR}/webapp.env" ]; then fi WEBAPP_PORT="${WEBAPP_PORT:-8973}" -# HTTPS (requis pour la géolocalisation GPS depuis un téléphone) : l'API -# Geolocation n'existe qu'en contexte sécurisé. Si le certificat auto-signé -# ./tls (cf. ./make-tls-cert.sh) est présent, la carte est servie en -# https://:PORT ; le téléphone accepte l'avertissement « certificat non -# fiable » une fois. Sans certificat : HTTP (le bouton GPS est indisponible). -TLS_DIR="${SCRIPT_DIR}/tls" -if [ -f "${TLS_DIR}/webapp.crt" ] && [ -f "${TLS_DIR}/webapp.key" ]; then - SCHEME=https - KFLAG=" -k" # -k : les sondes locales tolèrent le certificat auto-signé -else - SCHEME=http - KFLAG="" +COMPOSE_FILES=(-f "${SCRIPT_DIR}/${COMPOSE_BASE}") +if [ -f "${SCRIPT_DIR}/${COMPOSE_OVERRIDE}" ]; then + COMPOSE_FILES+=(-f "${SCRIPT_DIR}/${COMPOSE_OVERRIDE}") fi -BASE_URL="${SCHEME}://127.0.0.1:${WEBAPP_PORT}" -CMD="${1:-start}" +compose() { + docker compose "${COMPOSE_FILES[@]}" "$@" +} -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 +# Port hôte réellement publié par le conteneur (mapping compose) ; repli sur +# WEBAPP_PORT tant que le conteneur est arrêté. +published_port() { + local p + p="$(docker port "$CONTAINER" 8973/tcp 2>/dev/null | head -1 | sed 's/.*://')" + echo "${p:-$WEBAPP_PORT}" } lan_ip() { hostname -I 2> /dev/null | awk '{print $1}' } -cmd_start() { - mkdir -p "${SCRIPT_DIR}/output" - if curl -fsS${KFLAG} "${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 - # 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 - # Certificat auto-signé (HTTPS → GPS téléphone) : monté en lecture seule - # et activé via LIDAR_SSL_CERTFILE / LIDAR_SSL_KEYFILE (cf. make-tls-cert.sh). - TLS_ARGS=() - if [ "$SCHEME" = "https" ]; then - TLS_ARGS+=(-v "${TLS_DIR}:/app/tls:ro") - TLS_ARGS+=(-e LIDAR_SSL_CERTFILE=/app/tls/webapp.crt) - TLS_ARGS+=(-e LIDAR_SSL_KEYFILE=/app/tls/webapp.key) - fi - # Clé SSH du hôte (déploiement, git pull distant) : montée en lecture seule - # si présente. LIDAR_MOUNT_SSH=0 pour ne PAS l'exposer au conteneur - # (durcissement : la webapp sert des requêtes réseau, inutile d'y donner - # une clé privée si le git pull distant n'est pas utilisé). - SSH_ARGS=() - if [ "${LIDAR_MOUNT_SSH:-1}" = "1" ] && [ -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" \ - "${TLS_ARGS[@]}" \ - "${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${KFLAG} "${BASE_URL}/api/status" > /dev/null 2>&1; then - UP=1 +wait_up() { + echo -n "Attente du serveur sur le port $1..." + local up=0 i + for i in $(seq 1 30); do + if curl -fsS "http://127.0.0.1:$1/api/status" > /dev/null 2>&1; then + up=1 break fi sleep 1 done - if [ "$UP" != "1" ]; then + if [ "$up" != "1" ]; then echo " KO (voir ./serve-webapp.sh logs)" >&2 exit 1 fi echo " OK" +} + +cmd_start() { + compose up -d --build + wait_up "$(published_port)" + local port + port="$(published_port)" echo "============================================" echo " Carte LiDAR — webapp (${CONTAINER})" echo "============================================" - echo " URL : ${BASE_URL}/" + echo " URL : http://127.0.0.1:${port}/" if [ -n "$(lan_ip)" ]; then - echo " URL (LAN) : ${SCHEME}://$(lan_ip):${WEBAPP_PORT}/" + echo " URL (LAN) : http://$(lan_ip):${port}/" fi - if [ "$SCHEME" = "https" ]; then - echo " HTTPS (GPS tél.) : actif — sur le téléphone, acceptez l'avertissement « certificat non fiable »" + if [ -f "${SCRIPT_DIR}/${COMPOSE_OVERRIDE}" ]; then + echo " Override compose : ${COMPOSE_OVERRIDE} chargé (Traefik/volume local)" + else + echo " Override compose : absent (HTTP direct, sans Traefik)" fi - echo " Génération distante: ${LIDAR_GENERATION_URL:-non configurée}" - echo " Cache local tuiles : à la demande (LIDAR_GENERATION_URL)" + echo " Génération distante: ${LIDAR_GENERATION_URL:-non configurée (override / webapp.env)}" echo "============================================" } cmd_stop() { - docker rm -f "$CONTAINER" > /dev/null 2>&1 || true - echo "Conteneur ${CONTAINER} arrêté." + compose down + echo "Stack webapp arrêtée (conteneur ${CONTAINER} supprimé, cache conservé)." +} + +cmd_restart() { + compose up -d --build --force-recreate + wait_up "$(published_port)" + echo "Stack webapp recréée (image reconstruite, relit webapp.env)." } 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${KFLAG} "${BASE_URL}/api/sync" 2> /dev/null; then + compose ps + local port + port="$(published_port)" + if curl -fsS "http://127.0.0.1:${port}/api/sync" 2> /dev/null; then echo "" else - echo "Webapp : injoignable sur ${BASE_URL}" + echo "Webapp : injoignable sur http://127.0.0.1:${port}" >&2 fi } cmd_sync() { - CODE="$(curl -s${KFLAG} -o /dev/null -w '%{http_code}' -X POST "${BASE_URL}/api/sync" || true)" - case "$CODE" in + local port code + port="$(published_port)" + code="$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:${port}/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 + echo "Échec : HTTP ${code:-pas de réponse} (webapp injoignable sur http://127.0.0.1:${port} ?)" >&2 exit 1 ;; esac } +CMD="${1:-start}" case "$CMD" in start) cmd_start ;; stop) cmd_stop ;; - restart) cmd_stop; cmd_start ;; + restart) cmd_restart ;; status) cmd_status ;; sync) cmd_sync ;; - logs) exec docker logs -f "$CONTAINER" ;; + logs) compose logs -f webapp ;; *) echo "Usage : $0 [start|stop|restart|status|sync|logs]" >&2 exit 1 diff --git a/webapp.env.example b/webapp.env.example index 23eee17..74a04eb 100644 --- a/webapp.env.example +++ b/webapp.env.example @@ -2,7 +2,8 @@ # 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) +# Port des sondes locales de serve-webapp.sh quand le conteneur est arrêté +# (le port publié vient du mapping docker-compose.webapp.yml ; défaut 8973) #WEBAPP_PORT=8973 # Machine de traitement (service worker de docker-compose.worker.yml). @@ -31,12 +32,12 @@ # versionné) — cf. docker-compose.webapp.override.yml.example et # docs/DEPLOY_WEBAPP.md. Rien à définir dans ce fichier. # -# b. Sans Traefik (chemin serve-webapp.sh) : certificat auto-signé. +# b. Sans Traefik : certificat auto-signé monté via l'override compose +# local (docker-compose.webapp.override.yml) : # 1. ./make-tls-cert.sh (crée tls/webapp.crt + tls/webapp.key) -# 2. ./serve-webapp.sh restart (sert en https://:PORT, monte ./tls -# et passe ces variables automatiquement). Accepter l'avertissement -# « certificat non fiable » sur le téléphone. Ces lignes n'ont d'effet -# que si le certificat est stocké ailleurs que dans ./tls : +# 2. ajouter à l'override le montage ./tls:/app/tls:ro et les deux +# variables ci-dessous, puis ./serve-webapp.sh restart. +# Accepter l'avertissement « certificat non fiable » sur le téléphone : #LIDAR_SSL_CERTFILE=/app/tls/webapp.crt #LIDAR_SSL_KEYFILE=/app/tls/webapp.key