- run-dev.sh s'exécute dans sa propre session (setsid) et tue le GROUPE de process entier au cleanup : le backend (Kestrel) et ses enfants sont bien arrêtés, le port backend est réellement libéré à l'arrêt - Port backend paramétrable via BACKEND_PORT (défaut 8080), transmis à la fois au backend (dotnet --urls) et au proxy de dev du front - Nouveau frontend/proxy.conf.js : ng serve relaie /api vers http://localhost:${BACKEND_PORT} ; référencé dans angular.json (serve) - environment.ts (dev) : apiUrl passe à '/api' (same-origin via proxy), le port du backend n'est plus codé en dur dans le front - Prod inchangée (environment.prod.ts déjà en '/api', nginx)
170 lines
7.8 KiB
Bash
Executable File
170 lines
7.8 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 sont arrêtés ensemble, port backend libéré).
|
||
#
|
||
# 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
|
||
|
||
# --- Groupe de process dédié ------------------------------------------------
|
||
# 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 le port backend occupé. Solution : exécuter tout le script
|
||
# dans sa PROPRE session/groupe de process (via setsid), puis tuer le GROUPE ENTIER
|
||
# au cleanup. On se relance une seule fois sous setsid si ce n'est pas déjà le cas.
|
||
if [[ -z "${RUN_DEV_SETSID:-}" ]] && command -v setsid >/dev/null 2>&1; then
|
||
export RUN_DEV_SETSID=1
|
||
exec setsid "$0" "$@"
|
||
fi
|
||
|
||
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)"
|
||
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…)"
|
||
( cd "$RACINE/frontend" && npm start ) &
|
||
PID_FRONT=$!
|
||
|
||
# --- Arrêt propre : on tue TOUT le groupe de process ------------------------
|
||
# Comme le script tourne dans sa propre session (setsid), son PID est aussi le PGID.
|
||
# `kill -- -$$` envoie le signal à TOUT le groupe (backend, frontend et leurs enfants),
|
||
# ce qui libère réellement le port backend. Repli ciblé par sécurité.
|
||
cleanup() {
|
||
echo ""
|
||
echo "→ Arrêt (groupe de process)…"
|
||
# Signal au groupe entier (le '-' devant le PID = groupe). $$ = PID du script = PGID.
|
||
kill -TERM -- "-$$" 2>/dev/null || true
|
||
# Repli : au cas où certains process ne seraient pas dans le groupe.
|
||
kill "$PID_BACK" "$PID_FRONT" 2>/dev/null || true
|
||
pkill -f "SchotzIadKmTracker.Api" 2>/dev/null || true
|
||
}
|
||
trap cleanup EXIT INT TERM
|
||
|
||
# --- 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
|