**Motivations:** - ControlSocket errors still occurring during command execution - Socket can become invalid DURING execution of long commands - Need to detect socket errors in command output and clean up immediately **Root causes:** - Socket validation before command execution cannot detect if socket dies during execution - Long commands (npm install, build) can cause connection to die mid-execution - Next command finds invalid socket and SSH disables multiplexing but leaves socket - Previous cleanup only happened before execution, not after detecting errors **Correctifs:** - Capture SSH command output to detect 'ControlSocket already exists' errors - If socket error detected, immediately cleanup and retry once - This is a specific retry for socket errors only, not a general retry mechanism - Ensures dead sockets are cleaned up even if they die during command execution **Evolutions:** - Better handling of socket invalidation during long-running commands - Automatic recovery from socket errors detected during execution **Pages affectées:** - deploy.sh: Enhanced ssh_exec() to detect and handle socket errors in output
295 lines
10 KiB
Bash
295 lines
10 KiB
Bash
#!/bin/bash
|
|
set -e
|
|
|
|
# Configuration
|
|
SERVER="debian@92.243.27.35"
|
|
APP_NAME="zapwall"
|
|
DOMAIN="zapwall.fr"
|
|
APP_DIR="/var/www/${DOMAIN}"
|
|
GIT_REPO="https://git.4nkweb.com/4nk/story-research-zapwall.git"
|
|
|
|
# Configuration SSH pour connexion persistante (évite MaxStartups)
|
|
# Utiliser un chemin temporaire sans espaces pour ControlPath
|
|
SSH_CONTROL_DIR="/tmp/ssh_control_$$"
|
|
mkdir -p "${SSH_CONTROL_DIR}"
|
|
SSH_CONTROL_PATH="${SSH_CONTROL_DIR}/debian_92.243.27.35_22"
|
|
|
|
# Fonction pour nettoyer une connexion SSH morte
|
|
cleanup_dead_ssh() {
|
|
# Essayer de fermer proprement la connexion si elle existe
|
|
if [ -S "${SSH_CONTROL_PATH}" ]; then
|
|
ssh -O exit -o ControlPath="${SSH_CONTROL_PATH}" ${SERVER} 2>/dev/null || true
|
|
# Attendre un peu pour que la fermeture se termine
|
|
sleep 0.5
|
|
fi
|
|
# Forcer la suppression du socket et du répertoire parent
|
|
# Supprimer le répertoire entier garantit que le socket est vraiment supprimé
|
|
rm -rf "${SSH_CONTROL_DIR}" 2>/dev/null || true
|
|
# Recréer le répertoire pour les prochaines connexions
|
|
mkdir -p "${SSH_CONTROL_DIR}" 2>/dev/null || true
|
|
}
|
|
|
|
# Fonction pour vérifier si la connexion SSH maître est valide
|
|
check_ssh_connection() {
|
|
# Vérifier si le socket existe
|
|
if [ ! -S "${SSH_CONTROL_PATH}" ]; then
|
|
return 1
|
|
fi
|
|
# Tester la connexion en essayant de l'utiliser avec une commande simple
|
|
# Cela détecte si le socket existe mais la connexion est morte
|
|
ssh -o ControlPath="${SSH_CONTROL_PATH}" ${SERVER} "true" 2>/dev/null || return 1
|
|
return 0
|
|
}
|
|
|
|
# Fonction pour exécuter une commande SSH avec connexion persistante
|
|
ssh_exec() {
|
|
# Toujours vérifier et nettoyer le socket avant chaque commande
|
|
# pour éviter les sockets morts qui causent "ControlSocket already exists, disabling multiplexing"
|
|
# Le socket peut devenir invalide pendant l'exécution d'une commande précédente,
|
|
# donc on vérifie systématiquement avant chaque nouvelle commande
|
|
if [ -S "${SSH_CONTROL_PATH}" ]; then
|
|
if ! check_ssh_connection; then
|
|
# Connexion morte, nettoyer avant d'exécuter
|
|
cleanup_dead_ssh
|
|
fi
|
|
fi
|
|
|
|
# Exécuter la commande SSH (une seule tentative, pas de retry)
|
|
# Capture stderr pour détecter les erreurs de socket
|
|
local ssh_output
|
|
ssh_output=$(ssh -o ControlMaster=auto \
|
|
-o ControlPath="${SSH_CONTROL_PATH}" \
|
|
-o ControlPersist=300 \
|
|
-o ConnectTimeout=10 \
|
|
-o ServerAliveInterval=60 \
|
|
-o ServerAliveCountMax=3 \
|
|
${SERVER} "$@" 2>&1)
|
|
local ssh_exit_code=$?
|
|
|
|
# Si on détecte une erreur de socket, nettoyer et réessayer une fois
|
|
if echo "$ssh_output" | grep -q "ControlSocket.*already exists"; then
|
|
cleanup_dead_ssh
|
|
# Réessayer une fois après nettoyage
|
|
ssh -o ControlMaster=auto \
|
|
-o ControlPath="${SSH_CONTROL_PATH}" \
|
|
-o ControlPersist=300 \
|
|
-o ConnectTimeout=10 \
|
|
-o ServerAliveInterval=60 \
|
|
-o ServerAliveCountMax=3 \
|
|
${SERVER} "$@" 2>&1
|
|
return $?
|
|
fi
|
|
|
|
# Afficher la sortie et retourner le code de sortie
|
|
echo "$ssh_output"
|
|
return $ssh_exit_code
|
|
}
|
|
|
|
# Nettoyer les connexions SSH persistantes et le répertoire temporaire à la fin
|
|
cleanup_ssh() {
|
|
cleanup_dead_ssh
|
|
rm -rf "${SSH_CONTROL_DIR}" 2>/dev/null || true
|
|
}
|
|
trap cleanup_ssh EXIT
|
|
|
|
# Vérifier qu'un message de commit est fourni
|
|
if [ -z "$1" ]; then
|
|
echo "Erreur: Un message de commit est requis"
|
|
echo ""
|
|
echo "Usage: ./deploy.sh \"Message de commit\""
|
|
echo ""
|
|
echo "Exemple: ./deploy.sh \"Fix: Correction du bug de connexion\""
|
|
exit 1
|
|
fi
|
|
|
|
COMMIT_MESSAGE="$1"
|
|
|
|
echo "=== Déploiement de ${DOMAIN} ==="
|
|
echo ""
|
|
|
|
# Détecter la branche courante
|
|
BRANCH=$(git branch --show-current 2>/dev/null || echo "main")
|
|
echo "Branche courante: ${BRANCH}"
|
|
echo "Message de commit: ${COMMIT_MESSAGE}"
|
|
echo ""
|
|
|
|
# Commit et push des modifications locales
|
|
echo "1. Préparation du commit local..."
|
|
HAS_CHANGES=false
|
|
HAS_UNPUSHED_COMMITS=false
|
|
|
|
# Vérifier s'il y a des modifications non commitées
|
|
if ! git diff --quiet || ! git diff --cached --quiet; then
|
|
HAS_CHANGES=true
|
|
fi
|
|
|
|
# Vérifier s'il y a des commits non poussés
|
|
if git rev-parse --abbrev-ref ${BRANCH}@{upstream} >/dev/null 2>&1; then
|
|
LOCAL=$(git rev-parse @)
|
|
REMOTE=$(git rev-parse @{u})
|
|
if [ "$LOCAL" != "$REMOTE" ]; then
|
|
HAS_UNPUSHED_COMMITS=true
|
|
fi
|
|
else
|
|
# Pas de branche distante configurée, vérifier s'il y a des commits locaux
|
|
if git rev-list --count origin/${BRANCH}..HEAD >/dev/null 2>&1; then
|
|
UNPUSHED_COUNT=$(git rev-list --count origin/${BRANCH}..HEAD 2>/dev/null || echo "0")
|
|
if [ "$UNPUSHED_COUNT" -gt 0 ]; then
|
|
HAS_UNPUSHED_COMMITS=true
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [ "$HAS_CHANGES" = true ]; then
|
|
echo " ✓ Modifications détectées"
|
|
echo ""
|
|
echo "2. Ajout des modifications..."
|
|
git add -A
|
|
echo " ✓ Fichiers ajoutés"
|
|
echo ""
|
|
echo "3. Création du commit..."
|
|
git commit -m "${COMMIT_MESSAGE}"
|
|
echo " ✓ Commit créé"
|
|
HAS_UNPUSHED_COMMITS=true
|
|
fi
|
|
|
|
if [ "$HAS_UNPUSHED_COMMITS" = true ]; then
|
|
echo ""
|
|
echo "4. Push vers le dépôt distant..."
|
|
git push origin ${BRANCH}
|
|
echo " ✓ Push effectué"
|
|
else
|
|
echo " ⚠ Aucune modification à commiter ni commit à pousser"
|
|
fi
|
|
|
|
# Vérifier si Git est initialisé sur le serveur
|
|
echo ""
|
|
echo "5. Vérification du dépôt Git sur le serveur..."
|
|
if ssh_exec "cd ${APP_DIR} && git status >/dev/null 2>&1" >/dev/null 2>&1; then
|
|
echo " ✓ Dépôt Git détecté"
|
|
else
|
|
echo " ⚠ Dépôt Git non initialisé, initialisation..."
|
|
# Initialiser le dépôt Git en une seule commande pour réduire les appels SSH
|
|
ssh_exec "cd ${APP_DIR} && (git init 2>/dev/null || true) && (git remote add origin ${GIT_REPO} 2>/dev/null || git remote set-url origin ${GIT_REPO}) && (git checkout -b ${BRANCH} 2>/dev/null || true)"
|
|
echo " ✓ Dépôt Git initialisé"
|
|
fi
|
|
|
|
# Récupérer les dernières modifications
|
|
echo ""
|
|
echo "6. Récupération des dernières modifications..."
|
|
ssh_exec "cd ${APP_DIR} && git fetch origin"
|
|
|
|
# Sauvegarder les modifications locales sur le serveur
|
|
echo ""
|
|
echo "7. Sauvegarde des modifications locales sur le serveur..."
|
|
STASH_OUTPUT=$(ssh_exec "cd ${APP_DIR} && git stash push -u -m 'Auto-stash before deploy - $(date +%Y-%m-%d_%H:%M:%S)' 2>&1" || echo "No changes to stash")
|
|
if echo "$STASH_OUTPUT" | grep -q "No local changes"; then
|
|
echo " ✓ Aucune modification locale à sauvegarder"
|
|
else
|
|
echo " ✓ Modifications locales sauvegardées"
|
|
fi
|
|
|
|
# Nettoyer les fichiers non suivis
|
|
echo ""
|
|
echo "8. Nettoyage des fichiers non suivis..."
|
|
ssh_exec "cd ${APP_DIR} && git clean -fd || true"
|
|
|
|
# Vérifier que la branche existe
|
|
echo ""
|
|
echo "9. Vérification de la branche ${BRANCH}..."
|
|
if ssh_exec "cd ${APP_DIR} && git ls-remote --heads origin ${BRANCH} | grep -q ${BRANCH}"; then
|
|
echo " ✓ Branche ${BRANCH} trouvée"
|
|
else
|
|
echo " ✗ Branche ${BRANCH} non trouvée sur le dépôt distant"
|
|
echo ""
|
|
echo " Branches disponibles:"
|
|
AVAILABLE_BRANCHES=$(ssh_exec "cd ${APP_DIR} && git ls-remote --heads origin | sed 's/.*refs\\/heads\\///'")
|
|
echo "$AVAILABLE_BRANCHES" | sed 's/^/ - /'
|
|
echo ""
|
|
echo " Erreur: La branche '${BRANCH}' n'existe pas sur le dépôt distant."
|
|
echo " Vérifiez que vous avez bien poussé la branche avec 'git push origin ${BRANCH}'"
|
|
exit 1
|
|
fi
|
|
|
|
# Mise à jour depuis la branche
|
|
echo ""
|
|
echo "10. Mise à jour depuis la branche ${BRANCH}..."
|
|
ssh_exec "cd ${APP_DIR} && git checkout ${BRANCH} 2>/dev/null || git checkout -b ${BRANCH} origin/${BRANCH}"
|
|
ssh_exec "cd ${APP_DIR} && git pull origin ${BRANCH}"
|
|
|
|
# Afficher le dernier commit
|
|
echo ""
|
|
echo "11. Dernier commit:"
|
|
ssh_exec "cd ${APP_DIR} && git log -1 --oneline"
|
|
|
|
# Copier next.config.js local vers le serveur (pour ignorer ESLint pendant le build)
|
|
echo ""
|
|
echo "12. Mise à jour de next.config.js pour ignorer ESLint pendant le build..."
|
|
if [ -f "next.config.js" ]; then
|
|
cat next.config.js | ssh_exec "cat > ${APP_DIR}/next.config.js"
|
|
echo " ✓ next.config.js mis à jour"
|
|
else
|
|
echo " ⚠ next.config.js local non trouvé, utilisation de celui du serveur"
|
|
fi
|
|
|
|
# Mettre à jour les dépendances aux dernières versions
|
|
echo ""
|
|
echo "13. Mise à jour des dépendances aux dernières versions..."
|
|
ssh_exec "cd ${APP_DIR} && npx -y npm-check-updates -u || true"
|
|
|
|
# Installer les dépendances
|
|
echo ""
|
|
echo "14. Installation des dépendances..."
|
|
ssh_exec "cd ${APP_DIR} && npm install"
|
|
|
|
# Construire l'application
|
|
echo ""
|
|
echo "15. Construction de l'application..."
|
|
ssh_exec "cd ${APP_DIR} && npm run build"
|
|
|
|
# Redémarrer le service
|
|
echo ""
|
|
echo "16. Redémarrage du service ${APP_NAME}..."
|
|
ssh_exec "sudo systemctl restart ${APP_NAME}"
|
|
sleep 3
|
|
|
|
# Vérifier que le service fonctionne
|
|
echo ""
|
|
echo "17. Vérification du service..."
|
|
if ssh_exec "sudo systemctl is-active ${APP_NAME} >/dev/null"; then
|
|
echo " ✓ Service actif"
|
|
echo ""
|
|
echo " Statut du service:"
|
|
ssh_exec "sudo systemctl status ${APP_NAME} --no-pager | head -10"
|
|
else
|
|
echo " ✗ Service inactif, vérification des logs..."
|
|
ssh_exec "sudo journalctl -u ${APP_NAME} --no-pager -n 30"
|
|
exit 1
|
|
fi
|
|
|
|
# Vérifier que le port est en écoute
|
|
echo ""
|
|
echo "18. Vérification du port 3001..."
|
|
if ssh_exec "sudo ss -tuln | grep -q ':3001 '"; then
|
|
echo " ✓ Port 3001 en écoute"
|
|
else
|
|
echo " ⚠ Port 3001 non encore en écoute, attente..."
|
|
sleep 5
|
|
if ssh_exec "sudo ss -tuln | grep -q ':3001 '"; then
|
|
echo " ✓ Port 3001 maintenant en écoute"
|
|
else
|
|
echo " ✗ Port 3001 toujours non en écoute"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Déploiement terminé avec succès ==="
|
|
echo ""
|
|
echo "Site disponible sur: https://${DOMAIN}/"
|
|
echo ""
|
|
echo "Commandes utiles:"
|
|
echo " Voir les logs: ssh ${SERVER} 'sudo journalctl -u ${APP_NAME} -f'"
|
|
echo " Voir les stashes: ssh ${SERVER} 'cd ${APP_DIR} && git stash list'"
|
|
echo " Restaurer un stash: ssh ${SERVER} 'cd ${APP_DIR} && git stash pop'"
|