#!/usr/bin/env bash set -euo pipefail MODE="${1:-unknown}" # 📁 Logs par date LOG_ROOT="/var/log/lxc-updater-TM" DATE="$(date +%F)" TS="$(date +%H%M%S)" LOG_DIR="${LOG_ROOT}/${DATE}" LOG_FILE="${LOG_DIR}/lxc-updater-update.log" LOG_ARCHIVE="${LOG_DIR}/lxc-updater-update-${TS}.log" mkdir -p "$LOG_DIR" : > "$LOG_FILE" : > "$LOG_ARCHIVE" log() { echo "$1" echo "$1" >>"$LOG_FILE" echo "$1" >>"$LOG_ARCHIVE" } log_file_only() { echo "[$(date -Is)] $*" >>"$LOG_FILE" echo "[$(date -Is)] $*" >>"$LOG_ARCHIVE" } # ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ spinner spinner_start() { local msg="$1" local -a frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏") local i=0 printf "%s %s" "${frames[0]}" "$msg" ( while true; do i=$(( (i + 1) % ${#frames[@]} )) printf "\r%s %s" "${frames[$i]}" "$msg" sleep 0.12 done ) & SPINNER_PID=$! } spinner_stop() { local rc="${1:-0}" local msg="$2" if [[ -n "${SPINNER_PID:-}" ]]; then kill "$SPINNER_PID" >/dev/null 2>&1 || true wait "$SPINNER_PID" 2>/dev/null || true unset SPINNER_PID fi if [[ "$rc" -eq 0 ]]; then printf "\r✅ %s\n" "$msg" else printf "\r❌ %s\n" "$msg" fi } run_cmd() { local title="$1"; shift spinner_start "$title" log_file_only "----- ${title} -----" log_file_only "CMD: $*" set +e "$@" >>"$LOG_FILE" 2>&1 local rc=$? set -e spinner_stop "$rc" "$title" if [[ $rc -ne 0 ]]; then log_file_only "ERREUR (code=$rc) sur: $title" return $rc fi return 0 } # 🔎 APT : nombre de paquets upgradables apt_upgradable_count() { LC_ALL=C apt-get -s upgrade 2>/dev/null | grep -c '^Inst ' || true } update_apt() { log "📦 Mise à jour Debian / Ubuntu (APT)" export DEBIAN_FRONTEND=noninteractive run_cmd "Mise à jour de la liste des paquets" apt-get update -y local n n="$(apt_upgradable_count)" log "🔎 Paquets disponibles : ${n}" if [[ "$n" -eq 0 ]]; then log "✅ Aucun paquet à mettre à jour" return 0 fi run_cmd "Installation des mises à jour (${n} paquet(s))" apt-get upgrade -y run_cmd "Rechargement systemd (si nécessaire)" systemctl daemon-reload log "✅ Mise à jour APT terminée" } update_apk() { log "📦 Mise à jour Alpine (APK)" run_cmd "Mise à jour des dépôts" apk update run_cmd "Installation des mises à jour" apk upgrade log "✅ Mise à jour APK terminée" } # Compose: détecter dirs via labels + vérifier compose file compose_dirs_from_labels() { docker ps -q | while read -r cid; do docker inspect -f '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}' "$cid" 2>/dev/null || true done | sed '/^$/d;/^$/d' | sort -u } is_compose_dir_valid() { local d="$1" [[ -d "$d" ]] && { [[ -f "$d/docker-compose.yml" ]] || [[ -f "$d/compose.yml" ]]; } } update_compose_dir() { local dir="$1" log "📂 Projet : $dir" if docker compose version >/dev/null 2>&1; then run_cmd "Téléchargement des images (Compose)" bash -c "cd '$dir' && docker compose pull" # 🔁 Recréation SAFE (volumes conservés) run_cmd "Redéploiement des services (Compose)" bash -c "cd '$dir' && docker compose up -d --remove-orphans" return 0 fi if command -v docker-compose >/dev/null 2>&1; then run_cmd "Téléchargement des images (Compose v1)" bash -c "cd '$dir' && docker-compose pull" run_cmd "Redéploiement des services (Compose v1)" bash -c "cd '$dir' && docker-compose up -d --remove-orphans" return 0 fi log "❌ Docker Compose n'est pas installé" return 2 } update_docker_compose() { log "🐳 Mise à jour Docker (Compose)" run_cmd "Vérification Docker" docker ps log "🔍 Recherche de projets Docker Compose" mapfile -t dirs < <(compose_dirs_from_labels) valid=() for d in "${dirs[@]}"; do if is_compose_dir_valid "$d"; then valid+=("$d") else log "⚠️ Projet ignoré (chemin invalide ou sans compose.yml) : $d" fi done if (( ${#valid[@]} == 0 )); then log "⚠️ Aucun projet Compose détecté" return 0 fi log "📁 Projet(s) Compose détecté(s) : ${#valid[@]}" for d in "${valid[@]}"; do update_compose_dir "$d" done log "✅ Mise à jour Docker terminée" } # --- Parsing MODE (apt/apk + docker) --- BASE_MODE="${MODE%%+docker}" HAS_DOCKER="0" [[ "$MODE" == *"+docker" ]] && HAS_DOCKER="1" # ─────────────── Sortie lisible ─────────────── log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log "📦 Mise à jour du système" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" case "$BASE_MODE" in apt) update_apt ;; apk) update_apk ;; *) log "⚠️ Impossible de détecter le gestionnaire de paquets (apt/apk)" ;; esac if [[ "$HAS_DOCKER" == "1" ]]; then log "" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log "🐳 Mise à jour du ou des conteneurs" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" update_docker_compose else log "" log "🐳 Aucun conteneur Docker détecté" fi log "" log "🧾 Log update : ${LOG_FILE}" log "🗃️ Archive update : ${LOG_ARCHIVE}" log "🎉 Mise à jour terminée"