#!/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}" 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" "$2" else printf "\r❌ %s\n" "$2" 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 (simulation) 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" } docker_image_id() { docker image inspect -f '{{.Id}}' "$1" 2>/dev/null || true } update_docker_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" run_cmd "RedΓ©ploiement des services (Compose)" bash -c "cd '$dir' && docker compose up -d" 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" return 0 fi log "❌ Docker Compose n'est pas installΓ©" return 2 } update_docker() { log "🐳 Mise Γ  jour Docker" # VΓ©rif Docker avec spinner aussi run_cmd "VΓ©rification Docker" docker ps log "πŸ” Recherche de projets Docker Compose" mapfile -t dirs < <( 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 ) valid_dirs=() for d in "${dirs[@]}"; do if [[ -d "$d" ]] && { [[ -f "$d/docker-compose.yml" ]] || [[ -f "$d/compose.yml" ]]; }; then valid_dirs+=("$d") else log "⚠️ Compose ignorΓ© (chemin invalide ou sans compose.yml) : $d" fi done if (( ${#valid_dirs[@]} > 0 )); then log "πŸ“ Projet(s) Compose dΓ©tectΓ©(s) : ${#valid_dirs[@]}" for d in "${valid_dirs[@]}"; do update_docker_compose_dir "$d" done log "βœ… Mise Γ  jour Docker terminΓ©e" return 0 fi log "⚠️ Aucun projet Compose utilisable β†’ mise Γ  jour image par image" mapfile -t images < <(docker ps --format '{{.Image}}' | sort -u) if (( ${#images[@]} == 0 )); then log "βœ… Aucun conteneur Docker en cours" return 0 fi local updated=0 for img in "${images[@]}"; do [[ -n "$img" ]] || continue local old_id new_id old_id="$(docker_image_id "$img")" # Pull avec spinner run_cmd "Mise Γ  jour de l'image : ${img}" docker pull "$img" new_id="$(docker_image_id "$img")" if [[ -n "$old_id" && -n "$new_id" && "$old_id" != "$new_id" ]]; then updated=$((updated+1)) log "⬆️ Image mise Γ  jour : ${img}" log " πŸ” ${old_id:0:20} ➜ ${new_id:0:20}" else log "βœ… Image dΓ©jΓ  Γ  jour : ${img}" if [[ -n "$new_id" ]]; then log " 🏷️ ID : ${new_id:0:20}" fi fi done if [[ "$updated" -eq 0 ]]; then log "βœ… Aucune mise Γ  jour Docker Γ  appliquer" else log "βœ… Images Docker mises Γ  jour : ${updated}" log "⚠️ Note : sans Compose, les conteneurs ne sont pas recréés automatiquement." fi } # ───────────────────────────── # Mode : apt / apk + docker Γ©ventuel # Exemples : apt, apk, apt+docker, apk+docker # ───────────────────────────── BASE_MODE="${MODE%%+docker}" HAS_DOCKER="0" if [[ "$MODE" == *"+docker" ]]; then HAS_DOCKER="1" fi # ─────────────── Sections lisibles ─────────────── 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 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"