#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════════
# SMART-DEPLOY.SH - Script de déploiement intelligent
# Appelé par GitLab CI/CD, parse conf.gouroubleu.yml et déploie
#
# Usage: smart-deploy.sh
# Variables attendues (définies par GitLab CI):
# - CI_PROJECT_DIR : Répertoire du projet
# - CI_PROJECT_NAME : Nom du projet
# - CI_COMMIT_BRANCH : Branche
# - CI_COMMIT_SHORT_SHA : Commit court
# - DEPLOY_ENV : Environnement (prod, dev, staging)
# - DEPLOY_CONFIG : Fichier de config (conf.prod.gouroubleu.yml)
# ═══════════════════════════════════════════════════════════════════════════
set -euo pipefail
# ───────────────────────────────────────────────────────────────────────────
# ERROR HANDLER
# ───────────────────────────────────────────────────────────────────────────
on_error() {
local exit_code=$?
local line_no=$1
echo ""
echo "═══════════════════════════════════════════════════════════════════════════"
echo " DEPLOY FAILED at line $line_no (exit code: $exit_code)"
echo "═══════════════════════════════════════════════════════════════════════════"
# Notifier l'échec si possible
if type notify_deploy_failure &>/dev/null 2>&1; then
notify_deploy_failure "${CFG_name:-unknown}" "${CFG_target:-unknown}" "Exit code $exit_code at line $line_no"
fi
# Mettre à jour le registre si possible
if type registry_mark_failed &>/dev/null 2>&1; then
registry_mark_failed "${CFG_name:-unknown}" "Exit code $exit_code at line $line_no"
fi
exit $exit_code
}
trap 'on_error $LINENO' ERR
# ───────────────────────────────────────────────────────────────────────────
# PATHS (auto-detect based on host)
# ───────────────────────────────────────────────────────────────────────────
# Détecter le chemin du stockage selon la machine
if [[ -d "/stock_8to/33800-stack" ]]; then
# PVE (accès direct ZFS)
STOCK_BASE="/stock_8to/33800-stack"
elif [[ -d "/mnt/stock_8to/33800-stack" ]]; then
# prod-portainer (NFS mount)
STOCK_BASE="/mnt/stock_8to/33800-stack"
else
echo "ERROR: Cannot find 33800-stack directory"
exit 1
fi
SCRIPTS_DIR="${STOCK_BASE}/scripts/deploy"
REGISTRY_DIR="${STOCK_BASE}/monitoring"
# ───────────────────────────────────────────────────────────────────────────
# LOAD LIBS
# ───────────────────────────────────────────────────────────────────────────
source "${SCRIPTS_DIR}/lib/common.sh"
source "${SCRIPTS_DIR}/lib/parse-yaml.sh"
source "${SCRIPTS_DIR}/lib/targets.sh"
source "${SCRIPTS_DIR}/lib/services.sh"
# Optionnel: Notifications, Registre et Health Check
[[ -f "${SCRIPTS_DIR}/lib/notifications.sh" ]] && source "${SCRIPTS_DIR}/lib/notifications.sh"
[[ -f "${SCRIPTS_DIR}/lib/registry.sh" ]] && source "${SCRIPTS_DIR}/lib/registry.sh"
[[ -f "${SCRIPTS_DIR}/lib/health.sh" ]] && source "${SCRIPTS_DIR}/lib/health.sh"
[[ -f "${SCRIPTS_DIR}/lib/hooks.sh" ]] && source "${SCRIPTS_DIR}/lib/hooks.sh"
# Timer pour durée de déploiement
DEPLOY_START_TIME=$(date +%s)
# ───────────────────────────────────────────────────────────────────────────
# VARIABLES
# ───────────────────────────────────────────────────────────────────────────
PROJECT_DIR="${CI_PROJECT_DIR:-.}"
PROJECT_NAME="${CI_PROJECT_NAME:-$(basename "$PROJECT_DIR")}"
BRANCH="${CI_COMMIT_BRANCH:-main}"
COMMIT="${CI_COMMIT_SHORT_SHA:-$(git rev-parse --short HEAD 2>/dev/null || echo 'unknown')}"
DEPLOY_ENV="${DEPLOY_ENV:-prod}"
CONFIG_FILE="${DEPLOY_CONFIG:-conf.prod.gouroubleu.yml}"
# ───────────────────────────────────────────────────────────────────────────
# BANNER
# ───────────────────────────────────────────────────────────────────────────
echo "═══════════════════════════════════════════════════════════════════════════"
echo " SMART-DEPLOY - Stack 33800"
echo "═══════════════════════════════════════════════════════════════════════════"
echo " Project: $PROJECT_NAME"
echo " Branch: $BRANCH"
echo " Commit: $COMMIT"
echo " Env: $DEPLOY_ENV"
echo " Config: $CONFIG_FILE"
echo "═══════════════════════════════════════════════════════════════════════════"
echo ""
# ───────────────────────────────────────────────────────────────────────────
# 1. CHECK CONFIG FILE
# ───────────────────────────────────────────────────────────────────────────
log_step "Vérification du fichier de configuration..."
CONFIG_PATH="${PROJECT_DIR}/${CONFIG_FILE}"
if [[ ! -f "$CONFIG_PATH" ]]; then
log_warning "Fichier de config non trouvé: $CONFIG_FILE"
log_info "Pas de déploiement automatique pour cette branche."
log_info "Pour activer le déploiement, créer: $CONFIG_FILE"
exit 0
fi
log_success "Config trouvée: $CONFIG_FILE"
# ───────────────────────────────────────────────────────────────────────────
# 2. PARSE CONFIG
# ───────────────────────────────────────────────────────────────────────────
log_step "Parsing de la configuration..."
parse_yaml "$CONFIG_PATH" "CFG_"
# Parser les sections imbriquées
parse_yaml_section "$CONFIG_PATH" "deploy" "CFG_"
parse_yaml_section "$CONFIG_PATH" "nginx" "CFG_"
parse_yaml_section "$CONFIG_PATH" "storage" "CFG_"
parse_yaml_section "$CONFIG_PATH" "docker" "CFG_"
# Parser les sidecars si présents
if grep -q "^sidecars:" "$CONFIG_PATH"; then
parse_yaml_sidecars "$CONFIG_PATH"
fi
# Parser les hooks si présents
if grep -q "^hooks:" "$CONFIG_PATH"; then
parse_yaml_hooks "$CONFIG_PATH"
fi
# Valider les sidecars (chaque sidecar doit avoir name et image)
validate_sidecars() {
for ((i=0; i<${SIDECARS_COUNT:-0}; i++)); do
local sc_name_var="SIDECAR_${i}_name"
local sc_image_var="SIDECAR_${i}_image"
if [[ -z "${!sc_name_var:-}" ]]; then
log_error "Sidecar #${i}: 'name' manquant"
return 1
fi
if [[ -z "${!sc_image_var:-}" ]]; then
log_error "Sidecar #${i} (${!sc_name_var}): 'image' manquant"
return 1
fi
done
return 0
}
if [[ ${SIDECARS_COUNT:-0} -gt 0 ]]; then
validate_sidecars || exit 1
log_info " Sidecars: ${SIDECARS_COUNT}"
fi
# Vérifier les champs obligatoires
require_var CFG_name
require_var CFG_target
log_info " Name: ${CFG_name}"
log_info " Target: ${CFG_target}"
log_info " Type: ${CFG_type:-auto}"
log_info " Port: ${CFG_port:-N/A}"
log_info " Domain: ${CFG_nginx_domain:-N/A}"
# ───────────────────────────────────────────────────────────────────────────
# 3. RESOLVE TARGET
# ───────────────────────────────────────────────────────────────────────────
log_step "Résolution du target..."
resolve_target "${CFG_target}"
# Vérifier la connectivité si target distant
if [[ "$TARGET_LOCAL" != "true" ]]; then
check_target_connectivity "${CFG_target}"
fi
# ───────────────────────────────────────────────────────────────────────────
# 4. INJECT SERVICE VARIABLES
# ───────────────────────────────────────────────────────────────────────────
log_step "Injection des variables de services..."
inject_services "${PROJECT_DIR}/.env.deploy"
# Injecter les variables personnalisées (section env: du yaml avec résolution @VAR)
inject_custom_env "$CONFIG_FILE" "${PROJECT_DIR}/.env.deploy"
# ───────────────────────────────────────────────────────────────────────────
# 5. DNS WILDCARD (si domain déclaré)
# ───────────────────────────────────────────────────────────────────────────
if [[ -n "${CFG_nginx_domain:-}" ]]; then
log_step "Vérification DNS..."
if [[ -f "${SCRIPTS_DIR}/lib/dns.sh" ]]; then
source "${SCRIPTS_DIR}/lib/dns.sh"
ensure_dns_wildcard "${CFG_nginx_domain}"
else
log_warning "lib/dns.sh non trouvé, DNS non vérifié"
fi
fi
# ───────────────────────────────────────────────────────────────────────────
# LOAD STRATEGY
# ───────────────────────────────────────────────────────────────────────────
STRATEGY_FILE="${SCRIPTS_DIR}/strategies/${TARGET_TYPE}.sh"
if [[ ! -f "$STRATEGY_FILE" ]]; then
log_error "Stratégie non trouvée: $STRATEGY_FILE"
log_info "Stratégies disponibles:"
ls -1 "${SCRIPTS_DIR}/strategies/" | sed 's/\.sh$//' | sed 's/^/ - /'
exit 1
fi
source "$STRATEGY_FILE"
# ───────────────────────────────────────────────────────────────────────────
# HELPER FUNCTIONS (multi-stage)
# ───────────────────────────────────────────────────────────────────────────
# Écrire le fichier .deploy-info.env (artifact pour les stages suivants)
write_deploy_info() {
local info_file="${PROJECT_DIR}/.deploy-info.env"
cat > "$info_file" << EOF
BUILT_IMAGE_FULL=${BUILT_IMAGE_FULL:-}
BUILT_IMAGE_LATEST=${BUILT_IMAGE_LATEST:-}
CFG_NAME=${CFG_name}
CFG_TARGET=${CFG_target}
CFG_PORT=${CFG_port:-}
CFG_DEPLOY_STRATEGY=${CFG_deploy_strategy:-simple}
CFG_DEPLOY_GREEN_PORT=${CFG_deploy_green_port:-}
TARGET_HOST=${TARGET_HOST:-}
TARGET_USER=${TARGET_USER:-gouroubleu}
TARGET_TYPE=${TARGET_TYPE}
TARGET_LOCAL=${TARGET_LOCAL:-false}
TARGET_PATH=${TARGET_PATH:-}
COMMIT=${COMMIT}
DEPLOY_ENV=${DEPLOY_ENV}
EOF
log_success "Deploy info écrit: $info_file"
}
# Charger les infos depuis .deploy-info.env (artifact du stage build)
load_deploy_info() {
local info_file="${PROJECT_DIR}/.deploy-info.env"
if [[ -f "$info_file" ]]; then
source "$info_file"
log_info "Deploy info chargé: IMAGE=${BUILT_IMAGE_FULL:-N/A}"
else
log_warning "Pas de .deploy-info.env trouvé, utilisation des valeurs par défaut"
fi
}
# Exécuter les tests si configurés
run_tests() {
local test_cmd="${CFG_deploy_test_command:-}"
if [[ -z "$test_cmd" ]]; then
log_info "Pas de test_command configuré, skip"
return 0
fi
log_step "Exécution des tests: ${test_cmd}"
# Charger les infos du build pour l'image
load_deploy_info
if [[ -n "${BUILT_IMAGE_FULL:-}" ]]; then
# Exécuter dans le container Docker
log_info "Tests dans le container: ${BUILT_IMAGE_FULL}"
docker run --rm "${BUILT_IMAGE_FULL}" ${test_cmd}
else
# Exécuter localement
log_info "Tests locaux"
cd "$PROJECT_DIR"
eval "$test_cmd"
fi
log_success "Tests OK"
}
# ───────────────────────────────────────────────────────────────────────────
# DEPLOY_STAGE ROUTING
# ───────────────────────────────────────────────────────────────────────────
# Si DEPLOY_STAGE est défini, on exécute seulement la phase correspondante.
# Sinon (mode "all"), on exécute tout d'un bloc (rétrocompat).
# ───────────────────────────────────────────────────────────────────────────
case "${DEPLOY_STAGE:-all}" in
# ═══════════════════════════════════════════════════════════════════════════
# STAGE: build - Build image Docker uniquement
# ═══════════════════════════════════════════════════════════════════════════
build)
log_step "=== STAGE: BUILD ==="
if [[ "$TARGET_TYPE" == "docker" ]]; then
build_docker
else
log_info "Target type ${TARGET_TYPE}: pas de build Docker"
fi
# Écrire l'artifact pour les stages suivants
write_deploy_info
;;
# ═══════════════════════════════════════════════════════════════════════════
# STAGE: test - Exécuter les tests si configurés
# ═══════════════════════════════════════════════════════════════════════════
test)
log_step "=== STAGE: TEST ==="
run_tests
;;
# ═══════════════════════════════════════════════════════════════════════════
# STAGE: push - Push image vers le registry
# ═══════════════════════════════════════════════════════════════════════════
push)
log_step "=== STAGE: PUSH ==="
if [[ "$TARGET_TYPE" == "docker" ]]; then
# Charger les infos du build stage
load_deploy_info
push_docker
else
log_info "Target type ${TARGET_TYPE}: pas de push Docker"
fi
;;
# ═══════════════════════════════════════════════════════════════════════════
# STAGE: deploy - Déploiement sur la cible + registry + nginx init
# ═══════════════════════════════════════════════════════════════════════════
deploy)
log_step "=== STAGE: DEPLOY ==="
# Charger les infos du build stage
load_deploy_info
# Pre-deploy hooks
if type -t run_hooks &>/dev/null; then
run_hooks "pre_deploy" || exit 1
fi
# Déploiement selon type et stratégie
case "$TARGET_TYPE" in
docker)
deploy_only_docker
;;
files)
deploy_files
;;
remote)
deploy_remote
;;
*)
log_error "Type de déploiement inconnu: $TARGET_TYPE"
exit 1
;;
esac
# Post-deploy hooks
if type -t run_hooks &>/dev/null; then
run_hooks "post_deploy" || true
fi
# Registry update
log_step "Mise à jour du registre..."
if type registry_update_service &>/dev/null; then
registry_update_service "${CFG_name}" "${CFG_target}" "${TARGET_TYPE}" "${COMMIT}" "running"
fi
# Nginx config (seulement si première fois - blue-green gère le switch dans deploy_bluegreen)
STAGE_STRATEGY="${CFG_deploy_strategy:-simple}"
if [[ -n "${CFG_nginx_domain:-}" ]] && [[ "$TARGET_TYPE" == "docker" ]] && [[ "$STAGE_STRATEGY" != "blue-green" ]] && [[ "$STAGE_STRATEGY" != "bluegreen" ]]; then
if [[ -f "${SCRIPTS_DIR}/lib/nginx.sh" ]]; then
source "${SCRIPTS_DIR}/lib/nginx.sh"
generate_and_deploy_nginx_config
fi
fi
;;
# ═══════════════════════════════════════════════════════════════════════════
# STAGE: healthcheck - Vérification post-deploy + notification
# ═══════════════════════════════════════════════════════════════════════════
healthcheck)
log_step "=== STAGE: HEALTHCHECK ==="
HEALTH_ENDPOINT="${CFG_health:-/health}"
CONTAINER_NAME="${CFG_container_name:-${CFG_name}}"
# Pour blue-green : déterminer le port actif depuis le state file
HC_CHECK_PORT="${CFG_port}"
STAGE_STRATEGY="${CFG_deploy_strategy:-simple}"
if [[ "$STAGE_STRATEGY" == "blue-green" || "$STAGE_STRATEGY" == "bluegreen" ]]; then
source "$STRATEGY_FILE"
read_deploy_state
HC_CHECK_PORT="${BG_ACTIVE_PORT}"
CONTAINER_NAME="${CFG_container_name:-${CFG_name}}-${BG_ACTIVE_COLOR}"
log_info "Blue-green: vérification ${BG_ACTIVE_COLOR}:${HC_CHECK_PORT}"
fi
if [[ -n "${check_port:-}" ]] && type health_check &>/dev/null; then
# 9a. Container Running
if [[ "$TARGET_TYPE" == "docker" ]]; then
log_step "9a. Container Check..."
if ! check_container_running "$CONTAINER_NAME" "$TARGET_HOST" "${TARGET_USER:-gouroubleu}"; then
log_error "Container ${CONTAINER_NAME} is not running"
DEPLOY_STATUS="failed"
if type registry_mark_failed &>/dev/null; then
registry_mark_failed "${CFG_name}" "Container not running after deploy"
fi
if type notify_deploy_failure &>/dev/null; then
notify_deploy_failure "${CFG_name}" "${CFG_target}" "Container not running"
fi
exit 1
fi
fi
# 9b. Port Readiness
log_step "9b. Port Readiness..."
if ! check_port_ready "${HC_CHECK_PORT}" "$TARGET_HOST" "${TARGET_USER:-gouroubleu}"; then
log_error "Port ${HC_CHECK_PORT} not ready - initiating rollback"
if type notify_health_failure &>/dev/null; then
notify_health_failure "${CFG_name}" "${CFG_target}" "Port ${HC_CHECK_PORT} not ready"
fi
# Rollback blue-green
if [[ "$STAGE_STRATEGY" == "blue-green" || "$STAGE_STRATEGY" == "bluegreen" ]]; then
if [[ -f "${SCRIPTS_DIR}/lib/nginx.sh" ]]; then
source "${SCRIPTS_DIR}/lib/nginx.sh"
ROLLBACK_DOMAIN=$(resolve_domain)
rollback_nginx_upstream "${CFG_name}" "$BG_ACTIVE_PORT" "$ROLLBACK_DOMAIN"
fi
else
case "$TARGET_TYPE" in
docker) rollback_docker "${CFG_name}" "${CONFIG_PATH}" ;;
files) rollback_files "${CFG_name}" "${CONFIG_PATH}" ;;
remote) rollback_remote "${CFG_name}" "${CONFIG_PATH}" ;;
esac
fi
DEPLOY_STATUS="rolled_back"
fi
# 9c. HTTP Health (interne)
if [[ "${DEPLOY_STATUS:-success}" != "rolled_back" ]]; then
log_step "9c. Health Check (internal)..."
INTERNAL_URL="http://${TARGET_HOST}:${HC_CHECK_PORT}${HEALTH_ENDPOINT}"
log_info "URL: $INTERNAL_URL"
check_http_health "$INTERNAL_URL" "${CFG_name}"
HTTP_RESULT=$?
if [[ $HTTP_RESULT -ne 0 ]]; then
log_error "Internal health check failed - initiating rollback"
if type notify_health_failure &>/dev/null; then
notify_health_failure "${CFG_name}" "${CFG_target}" "$HEALTH_ENDPOINT"
fi
if [[ "$STAGE_STRATEGY" == "blue-green" || "$STAGE_STRATEGY" == "bluegreen" ]]; then
if [[ -f "${SCRIPTS_DIR}/lib/nginx.sh" ]]; then
source "${SCRIPTS_DIR}/lib/nginx.sh"
ROLLBACK_DOMAIN=$(resolve_domain)
rollback_nginx_upstream "${CFG_name}" "$BG_ACTIVE_PORT" "$ROLLBACK_DOMAIN"
fi
else
case "$TARGET_TYPE" in
docker) rollback_docker "${CFG_name}" "${CONFIG_PATH}" ;;
files) rollback_files "${CFG_name}" "${CONFIG_PATH}" ;;
remote) rollback_remote "${CFG_name}" "${CONFIG_PATH}" ;;
esac
fi
DEPLOY_STATUS="rolled_back"
fi
fi
# 9d. HTTPS External (si domain)
if [[ "${DEPLOY_STATUS:-success}" != "rolled_back" ]] && [[ -n "${CFG_nginx_domain:-}" ]]; then
log_step "9d. Health Check (external HTTPS)..."
EXTERNAL_URL="https://${CFG_nginx_domain}${HEALTH_ENDPOINT}"
log_info "URL: $EXTERNAL_URL"
if ! check_https_external "$EXTERNAL_URL" "${CFG_name}"; then
log_warning "External HTTPS check failed (nginx/SSL issue, no rollback)"
fi
fi
else
log_info "Health check ignoré (pas de port ou lib/health.sh non chargé)"
fi
# Notification
DEPLOY_END_TIME=$(date +%s)
DEPLOY_DURATION=$((DEPLOY_END_TIME - DEPLOY_START_TIME))
if [[ "${DEPLOY_STATUS:-success}" == "rolled_back" ]]; then
if type notify_deploy_failure &>/dev/null; then
notify_deploy_failure "${CFG_name}" "${CFG_target}" "Health check failed, rolled back"
fi
else
if type notify_deploy_success &>/dev/null; then
notify_deploy_success "${CFG_name}" "${CFG_target}" "${COMMIT}" "${DEPLOY_DURATION}"
fi
fi
;;
# ═══════════════════════════════════════════════════════════════════════════
# MODE: all - Rétrocompatibilité (comportement monolithique actuel)
# ═══════════════════════════════════════════════════════════════════════════
all)
log_step "Déploiement complet (stratégie: $TARGET_TYPE)..."
# 6. Deploy
case "$TARGET_TYPE" in
docker)
deploy_docker
;;
files)
deploy_files
;;
remote)
deploy_remote
;;
*)
log_error "Type de déploiement inconnu: $TARGET_TYPE"
exit 1
;;
esac
# 7. Registry
log_step "Mise à jour du registre..."
if type registry_update_service &>/dev/null; then
registry_update_service "${CFG_name}" "${CFG_target}" "${TARGET_TYPE}" "${COMMIT}" "running"
else
log_warning "Fonction registry_update_service non disponible"
fi
# 8. Nginx
if [[ -n "${CFG_nginx_domain:-}" ]] && [[ "$TARGET_TYPE" == "docker" ]]; then
log_step "Configuration Nginx..."
if [[ -f "${SCRIPTS_DIR}/lib/nginx.sh" ]]; then
source "${SCRIPTS_DIR}/lib/nginx.sh"
generate_and_deploy_nginx_config
else
log_warning "lib/nginx.sh non trouvé, Nginx non configuré"
fi
fi
# 9. Health checks
HEALTH_ENDPOINT="${CFG_health:-/health}"
CONTAINER_NAME="${CFG_container_name:-${CFG_name}}"
if [[ -n "${CFG_port:-}" ]] && type health_check &>/dev/null; then
if [[ "$TARGET_TYPE" == "docker" ]]; then
log_step "9a. Container Check..."
if ! check_container_running "$CONTAINER_NAME" "$TARGET_HOST" "${TARGET_USER:-gouroubleu}"; then
log_error "Container ${CONTAINER_NAME} is not running"
DEPLOY_STATUS="failed"
if type registry_mark_failed &>/dev/null; then
registry_mark_failed "${CFG_name}" "Container not running after deploy"
fi
if type notify_deploy_failure &>/dev/null; then
notify_deploy_failure "${CFG_name}" "${CFG_target}" "Container not running"
fi
exit 1
fi
fi
log_step "9b. Port Readiness..."
if ! check_port_ready "${CFG_port}" "$TARGET_HOST" "${TARGET_USER:-gouroubleu}"; then
log_error "Port ${CFG_port} not ready - initiating rollback"
if type notify_health_failure &>/dev/null; then
notify_health_failure "${CFG_name}" "${CFG_target}" "Port ${CFG_port} not ready"
fi
case "$TARGET_TYPE" in
docker) rollback_docker "${CFG_name}" "${CONFIG_PATH}" ;;
files) rollback_files "${CFG_name}" "${CONFIG_PATH}" ;;
remote) rollback_remote "${CFG_name}" "${CONFIG_PATH}" ;;
esac
if [[ $? -eq 0 ]]; then
log_warning "Rollback completed (port not ready)"
if type notify_rollback &>/dev/null; then
notify_rollback "${CFG_name}" "auto"
fi
DEPLOY_STATUS="rolled_back"
else
log_error "Rollback failed!"
exit 1
fi
fi
if [[ "${DEPLOY_STATUS:-success}" != "rolled_back" ]]; then
log_step "9c. Health Check (internal)..."
INTERNAL_URL="http://${TARGET_HOST}:${CFG_port}${HEALTH_ENDPOINT}"
log_info "URL: $INTERNAL_URL"
check_http_health "$INTERNAL_URL" "${CFG_name}"
HTTP_RESULT=$?
if [[ $HTTP_RESULT -ne 0 ]]; then
log_error "Internal health check failed - initiating rollback"
if type notify_health_failure &>/dev/null; then
notify_health_failure "${CFG_name}" "${CFG_target}" "$HEALTH_ENDPOINT"
fi
case "$TARGET_TYPE" in
docker) rollback_docker "${CFG_name}" "${CONFIG_PATH}" ;;
files) rollback_files "${CFG_name}" "${CONFIG_PATH}" ;;
remote) rollback_remote "${CFG_name}" "${CONFIG_PATH}" ;;
esac
if [[ $? -eq 0 ]]; then
log_warning "Rollback completed (health check failed)"
if type notify_rollback &>/dev/null; then
notify_rollback "${CFG_name}" "auto"
fi
sleep 5
check_http_health "$INTERNAL_URL" "${CFG_name}" 3 3
if [[ $? -eq 0 ]]; then
log_success "Service healthy after rollback"
fi
DEPLOY_STATUS="rolled_back"
else
log_error "Rollback failed!"
exit 1
fi
fi
fi
if [[ "${DEPLOY_STATUS:-success}" != "rolled_back" ]] && [[ -n "${CFG_nginx_domain:-}" ]]; then
log_step "9d. Health Check (external HTTPS)..."
EXTERNAL_URL="https://${CFG_nginx_domain}${HEALTH_ENDPOINT}"
log_info "URL: $EXTERNAL_URL"
if ! check_https_external "$EXTERNAL_URL" "${CFG_name}"; then
log_warning "External HTTPS check failed (nginx/SSL issue, no rollback)"
fi
fi
else
log_info "Health check ignoré (pas de port ou lib/health.sh non chargé)"
fi
# 10. Notification
DEPLOY_END_TIME=$(date +%s)
DEPLOY_DURATION=$((DEPLOY_END_TIME - DEPLOY_START_TIME))
if [[ "${DEPLOY_STATUS:-success}" == "rolled_back" ]]; then
if type notify_deploy_failure &>/dev/null; then
notify_deploy_failure "${CFG_name}" "${CFG_target}" "Health check failed, rolled back"
fi
else
if type notify_deploy_success &>/dev/null; then
notify_deploy_success "${CFG_name}" "${CFG_target}" "${COMMIT}" "${DEPLOY_DURATION}"
fi
fi
;;
*)
log_error "DEPLOY_STAGE inconnu: ${DEPLOY_STAGE}"
exit 1
;;
esac
# ───────────────────────────────────────────────────────────────────────────
# DONE
# ───────────────────────────────────────────────────────────────────────────
DEPLOY_END_TIME="${DEPLOY_END_TIME:-$(date +%s)}"
DEPLOY_DURATION="${DEPLOY_DURATION:-$((DEPLOY_END_TIME - DEPLOY_START_TIME))}"
echo ""
echo "═══════════════════════════════════════════════════════════════════════════"
if [[ "${DEPLOY_STATUS:-success}" == "rolled_back" ]]; then
log_warning "Déploiement annulé (rollback): ${CFG_name} → ${CFG_target} [${DEPLOY_STAGE:-all}] (${DEPLOY_DURATION}s)"
exit 1
else
log_success "Stage [${DEPLOY_STAGE:-all}] terminé: ${CFG_name} → ${CFG_target} (${DEPLOY_DURATION}s)"
fi
echo "═══════════════════════════════════════════════════════════════════════════"
exit 0