Files
SchotzIAD/run-dev.sh
T
MadJax 2fa3e35a88 fix: arrêt propre de run-dev.sh sans dégrader le terminal
Remplace le job control (set -m) par un kill récursif de l'arbre de
process de chaque service (tuer_arbre via pgrep -P), évitant que les
réglages du terminal interactif (flèches, historique) ne soient altérés
après Ctrl+C. Ajout d'un stty sane de sécurité en fin de cleanup.
2026-10-13 20:51:26 +02:00

203 lines
9.7 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Lance en mode développement le backend (port BACKEND_PORT, défaut 8080) et le
# frontend Angular (ng serve, port 4200) avec la base SQLite réelle.
# À utiliser hors Docker, pour le développement local.
#
# Usage : ./run-dev.sh
# Arrêt : Ctrl+C — backend et frontend (et leurs process enfants : Kestrel,
# ng serve, esbuild) sont arrêtés ensemble, les ports 4200/8080 libérés.
#
# Le port du backend est paramétrable via BACKEND_PORT (ex. BACKEND_PORT=5080 ./run-dev.sh).
# Ce même port est transmis au backend (dotnet --urls) ET au proxy de dev du front
# (proxy.conf.js lit BACKEND_PORT), de sorte qu'il n'est défini qu'à un seul endroit.
set -uo pipefail
# --- Arrêt propre des sous-process : rappel d'implémentation -----------------
# Problème résolu : à l'arrêt, « dotnet run » / « npm start » lancent des process
# ENFANTS (le vrai serveur Kestrel, esbuild…) qui survivaient à un simple kill du
# PID parent, laissant les ports occupés.
#
# Choix d'implémentation : backend et frontend sont lancés en arrière-plan (« & ») comme
# ENFANTS DIRECTS du script. Au cleanup, on tue RÉCURSIVEMENT tout l'arbre de process de
# chacun (via les enfants rapportés par « ps --ppid »), ce qui termine Kestrel / ng serve
# / esbuild et leurs descendants. Le script reste attaché au terminal → Ctrl+C fonctionne
# et le trap, retiré dès l'entrée de cleanup, ne boucle pas.
#
# On N'UTILISE NI « set -m » (job control) NI « setsid » :
# - set -m laisse le terminal dégradé après Ctrl+C (plus de flèches/historique ni Tab),
# car les enfants interactifs (node/ng serve) modifient les réglages du terminal ;
# - setsid fork et fausse « $! » (surveillance kill -0 / wait non fiables).
# Par sécurité, le cleanup restaure quand même le terminal via « stty sane ».
RACINE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Port du backend (paramétrable). Transmis au backend et au proxy de dev du front.
export BACKEND_PORT="${BACKEND_PORT:-8080}"
# --- Backend (.NET 8) -------------------------------------------------------
export PATH="$HOME/.dotnet:$HOME/.dotnet/tools:$PATH"
export DOTNET_ROOT="$HOME/.dotnet"
# Base réelle, chemin ABSOLU pour éviter deux bases selon le dossier de lancement.
export ConnectionStrings__Default="Data Source=$RACINE/backend/SchotzIadKmTracker.Api/trajets.db"
export ASPNETCORE_ENVIRONMENT=Development
# Clé OpenRouteService : lue depuis .env (ORS_API_KEY) et injectée sous le nom attendu
# par la configuration .NET (ORS__ApiKey). Sans clé valide, le backend calcule les
# distances à vol d'oiseau (fallback Haversine) au lieu de la distance routière réelle.
if [[ -z "${ORS__ApiKey:-}" && -f "$RACINE/.env" ]]; then
ORS_KEY_FROM_ENV="$(grep -E '^ORS_API_KEY=' "$RACINE/.env" | head -n1 | cut -d= -f2- || true)"
if [[ -n "$ORS_KEY_FROM_ENV" ]]; then
export ORS__ApiKey="$ORS_KEY_FROM_ENV"
echo "→ Clé ORS chargée depuis .env (calcul de distance routière activé)."
else
echo "⚠ Aucune clé ORS dans .env : distances calculées à vol d'oiseau."
fi
fi
# --- Authentification (dev) -------------------------------------------------
# Clé de signature JWT : lue depuis .env (JWT_SECRET_KEY). Si absente, on génère
# un secret ALÉATOIRE de développement (valable pour cette session). En dev, cela
# signifie que les sessions existantes sont invalidées à chaque relance du script,
# ce qui est acceptable ; en production, définis JWT_SECRET_KEY dans .env.
if [[ -z "${JWT__SecretKey:-}" ]]; then
JWT_KEY_FROM_ENV=""
if [[ -f "$RACINE/.env" ]]; then
JWT_KEY_FROM_ENV="$(grep -E '^JWT_SECRET_KEY=' "$RACINE/.env" | head -n1 | cut -d= -f2- || true)"
fi
# Ignore la valeur placeholder du .env.example si elle a été copiée telle quelle.
if [[ -n "$JWT_KEY_FROM_ENV" && "$JWT_KEY_FROM_ENV" != "change-me-generate-a-long-random-secret" ]]; then
export JWT__SecretKey="$JWT_KEY_FROM_ENV"
echo "→ Clé JWT chargée depuis .env."
else
# Secret de dev aléatoire (>= 32 octets) : openssl si dispo, sinon /dev/urandom.
if command -v openssl >/dev/null 2>&1; then
export JWT__SecretKey="$(openssl rand -base64 48)"
else
export JWT__SecretKey="$(head -c 48 /dev/urandom | base64)"
fi
echo "⚠ Aucune clé JWT dans .env : secret de développement aléatoire généré (sessions invalidées à chaque relance)."
fi
fi
# URL de base du frontend pour les liens de réinitialisation (dev = ng serve sur 4200).
export Auth__FrontendBaseUrl="${Auth__FrontendBaseUrl:-http://localhost:4200}"
# SMTP non configuré en dev : le lien de réinitialisation est journalisé dans la
# sortie du backend (repli LoggingEmailService). Cherche « reset-password?token= »
# dans les logs pour tester la réinitialisation sans email réel.
echo "→ Démarrage du backend sur http://localhost:$BACKEND_PORT (base : $RACINE/backend/SchotzIadKmTracker.Api/trajets.db)"
# Lancement classique en arrière-plan : le backend est un ENFANT DIRECT du script,
# donc $!, kill -0 et wait restent fiables (contrairement à setsid, qui fork et
# fausse $!). Au cleanup, on tue récursivement tout l'arbre de PID_BACK (Kestrel inclus).
dotnet run --project "$RACINE/backend/SchotzIadKmTracker.Api" --urls "http://localhost:$BACKEND_PORT" &
PID_BACK=$!
# --- Frontend (Angular via nvm/Node 22) -------------------------------------
export NVM_DIR="$HOME/.nvm"
# shellcheck source=/dev/null
. "$NVM_DIR/nvm.sh"
nvm use 22 >/dev/null
# Vide le cache de compilation Angular pour garantir un bundle à jour, notamment
# le polyfill zone.js (sans lui, la vue ne se rafraîchit pas sur les réponses HTTP :
# `window.Zone` doit être défini au runtime). Un cache périmé peut resservir un
# ancien polyfills sans zone.js.
echo "→ Nettoyage du cache Angular (.angular/cache)"
rm -rf "$RACINE/frontend/.angular/cache"
echo "→ Démarrage du frontend sur http://localhost:4200 (proxy /api → :$BACKEND_PORT ; première compilation : patiente…)"
# Même principe que le backend : enfant direct du script. L'arbre (npm → ng serve →
# esbuild) sera tué récursivement au cleanup à partir de PID_FRONT.
( cd "$RACINE/frontend" && npm start ) &
PID_FRONT=$!
# Tue récursivement un process et toute sa descendance (enfants, petits-enfants…).
# On remonte les enfants AVANT de tuer le parent pour ne pas perdre la filiation, puis
# on envoie TERM à chaque PID. Robuste et sans effet sur le terminal (ni set -m ni setsid).
tuer_arbre() {
local pid="$1"
[ -z "$pid" ] && return 0
local enfant
for enfant in $(pgrep -P "$pid" 2>/dev/null); do
tuer_arbre "$enfant"
done
kill -TERM "$pid" 2>/dev/null || true
}
# --- Arrêt propre : on tue l'ARBRE de chaque service (pas le script lui-même) --
# On tue le groupe de process (PGID) du backend et du frontend créés par setsid, ce
# qui termine Kestrel / ng serve / esbuild et leurs enfants. On NE touche PAS au groupe
# du script : Ctrl+C reste fonctionnel et le trap, retiré dès l'entrée de cleanup, ne
# boucle pas. On restaure enfin le terminal (stty sane) au cas où un enfant interactif
# l'aurait laissé dans un état dégradé (flèches/historique, autocomplétion Tab).
cleanup() {
trap - INT TERM EXIT
echo ""
echo "→ Arrêt…"
# Tue tout l'arbre du frontend puis du backend (service + enfants : ng serve, esbuild,
# Kestrel…). On capture les PID avant qu'ils ne disparaissent.
tuer_arbre "${PID_FRONT:-}"
tuer_arbre "${PID_BACK:-}"
# Dernier filet : process résiduels identifiables du projet, au cas où.
pkill -f "SchotzIadKmTracker.Api" 2>/dev/null || true
pkill -f "ng serve" 2>/dev/null || true
# Restaure un terminal sain (écho, flèches/historique, autocomplétion) au cas où un
# enfant interactif (node/ng serve) aurait modifié les réglages du terminal.
stty sane 2>/dev/null || true
}
trap cleanup INT TERM EXIT
# --- Attente active : ng serve prêt à répondre en HTTP 200 ------------------
# La première compilation (surtout après vidage du cache) peut prendre 30–60 s.
# Tant que ng serve n'a pas fini, le port répond « connexion refusée » (HTTP 000)
# et le navigateur chargerait une page incomplète (zone.js absent → window.Zone
# undefined). On ne montre l'invitation qu'une fois le serveur réellement prêt.
attendre_frontend() {
local url="http://localhost:4200/"
local max=120 # secondes
local t=0
printf "→ Compilation du frontend"
while [ "$t" -lt "$max" ]; do
# Le process ng serve est-il toujours vivant ?
if ! kill -0 "$PID_FRONT" 2>/dev/null; then
echo ""
echo "✗ Le frontend s'est arrêté avant d'être prêt (voir les logs ci-dessus)."
return 1
fi
local code
code=$(curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo 000)
if [ "$code" = "200" ]; then
echo " prêt."
return 0
fi
printf "."
sleep 2
t=$((t + 2))
done
echo ""
echo "✗ Le frontend n'a pas répondu 200 au bout de ${max}s. Vérifie les logs ng serve."
return 1
}
if attendre_frontend; then
echo ""
echo "======================================================================"
echo " Application prête."
echo " Backend : http://localhost:$BACKEND_PORT/api"
echo " Frontend : http://localhost:4200 ← ouvre cette URL maintenant"
echo ""
echo " Au premier chargement, fais Ctrl+Shift+R pour vider le cache navigateur."
echo " Vérif : dans la console DevTools, 'window.Zone' doit renvoyer un objet."
echo ""
echo " Auth : à la 1re utilisation, crée le compte via /register puis connecte-toi."
echo " Reset de mot de passe (dev) : le lien est journalisé dans la sortie backend"
echo " ci-dessus (cherche « reset-password?token= »), aucun email n'est envoyé."
echo "======================================================================"
echo ""
fi
wait