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:
Antoine Jacquin
2026-09-04 21:48:35 +02:00
parent 23969c9e14
commit 8e1087b12b
5 changed files with 251 additions and 15 deletions

180
serve-webapp.sh Executable file
View 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