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.
203 lines
9.7 KiB
Bash
Executable File
203 lines
9.7 KiB
Bash
Executable File
#!/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
|