#!/bin/bash
# ═══════════════════════════════════════════════════════════════════════════
# REMOTE.SH - Stratégie de déploiement distant
# rsync ou git push (pour O2switch, hébergeurs mutualisés, etc.)
#
# Méthodes supportées:
# - rsync : Copie directe via rsync (recommandé)
# - git : Push vers un bare repo avec post-receive hook
#
# Configuration YAML:
# deploy:
# method: rsync|git
# source: "." # Sous-dossier à déployer
# dest: "mon-app" # Dossier destination
# exclude: "*.log tmp/" # Exclusions additionnelles
# pre_deploy: "cmd..." # Avant le déploiement
# post_deploy: "cmd..." # Après le déploiement
# backup: true # Créer backup avant deploy
# ═══════════════════════════════════════════════════════════════════════════
# ───────────────────────────────────────────────────────────────────────────
# Exclusions par défaut pour rsync
# ───────────────────────────────────────────────────────────────────────────
REMOTE_DEFAULT_EXCLUDES=(
'.git'
'.gitignore'
'node_modules'
'.env*'
'conf.*.gouroubleu.yml'
'.gitlab-ci.yml'
'__pycache__'
'*.pyc'
'.pytest_cache'
'target/debug'
'.cargo'
'.Dockerfile.generated'
'docker-compose.generated.yml'
'.DS_Store'
'Thumbs.db'
'*.log'
'.vscode'
'.idea'
)
# ───────────────────────────────────────────────────────────────────────────
# Fonction principale de déploiement remote
# ───────────────────────────────────────────────────────────────────────────
deploy_remote() {
local method="${CFG_deploy_method:-${CFG_method:-rsync}}"
local source="${CFG_deploy_source:-${CFG_source:-.}}"
local dest="${CFG_deploy_dest:-${CFG_dest:-${CFG_name}}}"
log_info "Déploiement remote (méthode: $method)"
log_info " Source: $source"
log_info " Dest: ${TARGET_USER}@${TARGET_HOST}:${dest}"
# 1. Vérifier la connexion SSH
log_step "Vérification connexion SSH..."
if ! verify_ssh_connection; then
log_error "Impossible de se connecter à ${TARGET_HOST}"
return 1
fi
# 2. Créer un backup si demandé
if [[ "${CFG_deploy_backup:-false}" == "true" ]]; then
log_step "Création du backup..."
create_remote_backup "$dest"
fi
# 3. Exécuter pre_deploy si configuré
if [[ -n "${CFG_deploy_pre_deploy:-${CFG_pre_deploy:-}}" ]]; then
log_step "Exécution pre_deploy..."
run_remote_hook "$dest" "${CFG_deploy_pre_deploy:-${CFG_pre_deploy}}"
fi
# 4. Déployer selon la méthode
case "$method" in
rsync)
deploy_rsync "$source" "$dest"
;;
git)
deploy_git "$dest"
;;
*)
log_error "Méthode de déploiement inconnue: $method"
return 1
;;
esac
# 5. Exécuter post_deploy si configuré
if [[ -n "${CFG_deploy_post_deploy:-${CFG_post_deploy:-}}" ]]; then
log_step "Exécution post_deploy..."
run_remote_hook "$dest" "${CFG_deploy_post_deploy:-${CFG_post_deploy}}"
fi
log_success "Déploiement remote terminé: ${CFG_name}"
}
# ───────────────────────────────────────────────────────────────────────────
# Helper: Obtenir la destination SSH (utilise alias si disponible)
# ───────────────────────────────────────────────────────────────────────────
get_ssh_dest() {
if [[ -n "${TARGET_SSH_ALIAS:-}" ]]; then
echo "${TARGET_SSH_ALIAS}"
else
echo "${TARGET_USER}@${TARGET_HOST}"
fi
}
# ───────────────────────────────────────────────────────────────────────────
# Vérifier la connexion SSH
# ───────────────────────────────────────────────────────────────────────────
verify_ssh_connection() {
local ssh_dest=$(get_ssh_dest)
ssh -o ConnectTimeout=10 -o BatchMode=yes \
"$ssh_dest" "echo ok" >/dev/null 2>&1
}
# ───────────────────────────────────────────────────────────────────────────
# Créer un backup du dossier distant
# ───────────────────────────────────────────────────────────────────────────
create_remote_backup() {
local dest="$1"
local backup_name="${dest}.backup-$(date +%Y%m%d-%H%M%S)"
local ssh_dest=$(get_ssh_dest)
ssh "$ssh_dest" \
"if [ -d '${dest}' ]; then cp -r '${dest}' '${backup_name}'; fi"
log_info "Backup créé: ${backup_name}"
}
# ───────────────────────────────────────────────────────────────────────────
# Exécuter un hook sur le serveur distant
# ───────────────────────────────────────────────────────────────────────────
run_remote_hook() {
local dest="$1"
local cmd="$2"
local ssh_dest=$(get_ssh_dest)
log_info " Hook: ${cmd}"
ssh "$ssh_dest" "cd '${dest}' 2>/dev/null && ${cmd}"
log_success "Hook exécuté"
}
# ───────────────────────────────────────────────────────────────────────────
# Construire les arguments d'exclusion rsync
# ───────────────────────────────────────────────────────────────────────────
build_exclude_args() {
local exclude_args=""
# Exclusions par défaut
for pattern in "${REMOTE_DEFAULT_EXCLUDES[@]}"; do
exclude_args="$exclude_args --exclude='$pattern'"
done
# Exclusions personnalisées (CFG_exclude ou CFG_deploy_exclude)
local custom_excludes="${CFG_deploy_exclude:-${CFG_exclude:-}}"
if [[ -n "$custom_excludes" ]]; then
for pattern in $custom_excludes; do
exclude_args="$exclude_args --exclude='$pattern'"
done
fi
echo "$exclude_args"
}
# ───────────────────────────────────────────────────────────────────────────
# Déploiement via rsync
# ───────────────────────────────────────────────────────────────────────────
deploy_rsync() {
local source="$1"
local dest="$2"
local ssh_dest=$(get_ssh_dest)
log_step "rsync vers ${ssh_dest}:${dest}..."
# Construire le chemin source complet
local src_path="${PROJECT_DIR}/${source}"
[[ "$source" == "." ]] && src_path="${PROJECT_DIR}/"
# S'assurer que le chemin se termine par /
[[ "${src_path}" != */ ]] && src_path="${src_path}/"
# Construire les exclusions
local exclude_args
exclude_args=$(build_exclude_args)
# Options rsync
local rsync_opts="-avz --delete --checksum"
# Mode dry-run si DRY_RUN=true
if [[ "${DRY_RUN:-false}" == "true" ]]; then
rsync_opts="$rsync_opts --dry-run"
log_info " (mode dry-run activé)"
fi
# Exécuter rsync
eval "rsync $rsync_opts $exclude_args '${src_path}' '${ssh_dest}:${dest}/'"
log_success "rsync terminé"
}
# ───────────────────────────────────────────────────────────────────────────
# Déploiement via git push
# ───────────────────────────────────────────────────────────────────────────
deploy_git() {
local dest="$1"
local remote_name="deploy-remote"
local target_branch="${CFG_deploy_branch:-main}"
local ssh_dest=$(get_ssh_dest)
log_step "git push vers ${ssh_dest}:${dest}..."
cd "$PROJECT_DIR"
# Vérifier qu'on est dans un repo git
if ! git rev-parse --git-dir >/dev/null 2>&1; then
log_error "Le dossier n'est pas un repository git"
return 1
fi
# Configurer le remote
# Utiliser l'alias SSH si disponible, sinon le format user@host
local remote_url
if [[ -n "${TARGET_SSH_ALIAS:-}" ]]; then
remote_url="${TARGET_SSH_ALIAS}:~/${dest}.git"
else
remote_url="ssh://${TARGET_USER}@${TARGET_HOST}/~/${dest}.git"
fi
log_info " Remote URL: $remote_url"
log_info " Branche: ${BRANCH:-HEAD} → ${target_branch}"
# Supprimer le remote s'il existe déjà
git remote remove "$remote_name" 2>/dev/null || true
# Ajouter le remote
git remote add "$remote_name" "$remote_url"
# Vérifier/créer le bare repo sur le serveur si besoin
if [[ "${CFG_deploy_init_repo:-false}" == "true" ]]; then
init_git_bare_repo "$dest"
fi
# Push vers le remote
local push_opts=""
if [[ "${CFG_deploy_force:-true}" == "true" ]]; then
push_opts="--force"
fi
git push $push_opts "$remote_name" "${BRANCH:-HEAD}:${target_branch}"
log_success "git push terminé"
# Cleanup
git remote remove "$remote_name" 2>/dev/null || true
}
# ───────────────────────────────────────────────────────────────────────────
# Initialiser un bare repo git sur le serveur distant
# ───────────────────────────────────────────────────────────────────────────
init_git_bare_repo() {
local dest="$1"
local repo_path="~/${dest}.git"
local work_tree="~/${dest}"
local ssh_dest=$(get_ssh_dest)
log_info " Initialisation bare repo: ${repo_path}"
ssh "$ssh_dest" bash << EOF
# Créer le bare repo si nécessaire
if [ ! -d "${repo_path}" ]; then
mkdir -p "${repo_path}"
cd "${repo_path}"
git init --bare
# Créer le post-receive hook
cat > hooks/post-receive << 'HOOK'
#!/bin/bash
GIT_WORK_TREE="${work_tree}" git checkout -f main
cd "${work_tree}"
# Exécuter le script de déploiement si présent
if [ -f ".deploy.sh" ]; then
bash .deploy.sh
fi
HOOK
chmod +x hooks/post-receive
echo "Bare repo créé: ${repo_path}"
fi
# Créer le dossier de travail si nécessaire
mkdir -p "${work_tree}"
EOF
log_success "Bare repo initialisé"
}
# ───────────────────────────────────────────────────────────────────────────
# Rollback vers le backup précédent
# ───────────────────────────────────────────────────────────────────────────
rollback_remote() {
local dest="${CFG_deploy_dest:-${CFG_dest:-${CFG_name}}}"
local ssh_dest=$(get_ssh_dest)
log_info "Rollback pour ${dest}..."
# Trouver le dernier backup
local latest_backup
latest_backup=$(ssh "$ssh_dest" \
"ls -1td ${dest}.backup-* 2>/dev/null | head -1")
if [[ -z "$latest_backup" ]]; then
log_error "Aucun backup trouvé pour ${dest}"
return 1
fi
log_info "Restauration depuis: ${latest_backup}"
ssh "$ssh_dest" bash << EOF
# Supprimer le dossier actuel
rm -rf "${dest}"
# Restaurer le backup
mv "${latest_backup}" "${dest}"
EOF
log_success "Rollback terminé"
}