2026-09-12 17:41:05 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
#
|
|
|
|
|
# Redeploiement sur le serveur Hetzner.
|
|
|
|
|
#
|
|
|
|
|
# ./deploy/deploy.sh
|
|
|
|
|
#
|
|
|
|
|
# Le build tourne sur le serveur, dans le conteneur Maven : la machine de dev
|
|
|
|
|
# n'a pas besoin d'un JDK 21, et l'architecture (arm64 ici, x86_64 la-bas) ne
|
|
|
|
|
# pose pas de probleme.
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
HOST="${DEPLOY_HOST:-hetzner}"
|
|
|
|
|
REMOTE_DIR="${DEPLOY_DIR:-/opt/iot-alarm-system}"
|
|
|
|
|
LOCAL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
|
|
|
|
|
|
echo "==> Synchronisation vers ${HOST}:${REMOTE_DIR}"
|
|
|
|
|
# --delete sans toucher au .env : il contient le mot de passe de la base et
|
|
|
|
|
# n'existe que sur le serveur.
|
|
|
|
|
rsync -az --delete \
|
|
|
|
|
--exclude 'target/' \
|
|
|
|
|
--exclude '.git/' \
|
|
|
|
|
--exclude '.env' \
|
|
|
|
|
--exclude '.DS_Store' \
|
|
|
|
|
"${LOCAL_DIR}/" "${HOST}:${REMOTE_DIR}/"
|
|
|
|
|
|
2026-09-12 18:08:34 +02:00
|
|
|
echo "==> Rendu de la configuration MediaMTX"
|
|
|
|
|
# Le mot de passe de publication vit dans le .env du serveur et n'existe nulle
|
|
|
|
|
# part dans le depot. Le fichier rendu est gitignore.
|
|
|
|
|
ssh "${HOST}" "cd ${REMOTE_DIR} && \
|
|
|
|
|
PASS=\$(grep '^STREAM_PUBLISH_PASSWORD=' .env | cut -d= -f2-) && \
|
Sonde de presence video : la camera muette devient un evenement capteur
Le domaine connaissait deja CAMERA_OFFLINE, classe CRITICAL, mais rien ne
l'emettait. CameraLivenessProbe interroge l'API du serveur media et comble
ce trou.
Elle passe par le meme port IngestSensorEventUseCase que l'endpoint REST
et le simulateur : trois producteurs de natures tres differentes, un seul
contrat d'entree, et le meme trajet Kafka -> agregat ensuite. Si l'alarme
est desarmee, une coupure video ne reveille personne - c'est l'agregat qui
tranche, pas la sonde.
Le seuil est la partie qui compte. Sur un lien 4G le flux tombe
regulierement sans que personne n'ait touche au telephone : tunnel,
bascule d'antenne, congestion. Alerter des la premiere sonde ratee
produirait plusieurs CRITICAL par jour, et on apprend vite a ignorer des
alertes qui crient pour rien - y compris le jour ou elles ont raison.
Trois sondes consecutives, soit une minute d'absence reelle. Six tests
couvrent ce comportement, dont la remise a zero du compteur au retour du
flux.
Utilisateur d'API dedie cote MediaMTX, restreint au reseau Docker interne.
41 tests au vert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:24:45 +02:00
|
|
|
APIPASS=\$(grep '^MEDIA_API_PASSWORD=' .env | cut -d= -f2-) && \
|
2026-09-12 18:08:34 +02:00
|
|
|
PUBLIC_IP=\$(curl -s --max-time 10 https://api.ipify.org) && \
|
|
|
|
|
sed -e \"s|__PUBLISH_PASSWORD__|\${PASS}|\" \
|
Sonde de presence video : la camera muette devient un evenement capteur
Le domaine connaissait deja CAMERA_OFFLINE, classe CRITICAL, mais rien ne
l'emettait. CameraLivenessProbe interroge l'API du serveur media et comble
ce trou.
Elle passe par le meme port IngestSensorEventUseCase que l'endpoint REST
et le simulateur : trois producteurs de natures tres differentes, un seul
contrat d'entree, et le meme trajet Kafka -> agregat ensuite. Si l'alarme
est desarmee, une coupure video ne reveille personne - c'est l'agregat qui
tranche, pas la sonde.
Le seuil est la partie qui compte. Sur un lien 4G le flux tombe
regulierement sans que personne n'ait touche au telephone : tunnel,
bascule d'antenne, congestion. Alerter des la premiere sonde ratee
produirait plusieurs CRITICAL par jour, et on apprend vite a ignorer des
alertes qui crient pour rien - y compris le jour ou elles ont raison.
Trois sondes consecutives, soit une minute d'absence reelle. Six tests
couvrent ce comportement, dont la remise a zero du compteur au retour du
flux.
Utilisateur d'API dedie cote MediaMTX, restreint au reseau Docker interne.
41 tests au vert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 18:24:45 +02:00
|
|
|
-e \"s|__API_PASSWORD__|\${APIPASS}|\" \
|
2026-09-12 18:08:34 +02:00
|
|
|
-e \"s|__PUBLIC_IP__|\${PUBLIC_IP}|\" \
|
|
|
|
|
deploy/mediamtx/mediamtx.yml.template > deploy/mediamtx/mediamtx.yml && \
|
|
|
|
|
chmod 600 deploy/mediamtx/mediamtx.yml"
|
|
|
|
|
|
2026-09-12 17:41:05 +02:00
|
|
|
echo "==> Build de l'image (tests unitaires + ArchUnit inclus)"
|
|
|
|
|
ssh "${HOST}" "cd ${REMOTE_DIR} && docker compose build app"
|
|
|
|
|
|
|
|
|
|
echo "==> Redemarrage"
|
|
|
|
|
ssh "${HOST}" "cd ${REMOTE_DIR} && docker compose up -d"
|
2026-09-12 18:08:34 +02:00
|
|
|
# `up -d` ne recree pas un conteneur dont seule la configuration montee a
|
|
|
|
|
# change : MediaMTX garderait l'ancienne en memoire.
|
|
|
|
|
ssh "${HOST}" "cd ${REMOTE_DIR} && docker compose restart mediamtx"
|
2026-09-12 17:41:05 +02:00
|
|
|
|
|
|
|
|
echo "==> Attente du healthcheck"
|
|
|
|
|
for i in $(seq 1 30); do
|
|
|
|
|
status=$(ssh "${HOST}" "docker inspect -f '{{.State.Health.Status}}' iot-alarm-app 2>/dev/null || echo absent")
|
|
|
|
|
if [ "${status}" = "healthy" ]; then
|
|
|
|
|
echo "==> Application saine"
|
|
|
|
|
ssh "${HOST}" "docker exec iot-alarm-app wget -qO- http://localhost:8080/api/v1/alarm/status"
|
|
|
|
|
echo
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
printf '.'
|
|
|
|
|
sleep 5
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo
|
|
|
|
|
echo "!! L'application n'est pas devenue saine. Logs :" >&2
|
|
|
|
|
ssh "${HOST}" "cd ${REMOTE_DIR} && docker compose logs --tail 60 app" >&2
|
|
|
|
|
exit 1
|