From 9dfebf8a0b5611f4a9ab26a78e5bbe774b175574 Mon Sep 17 00:00:00 2001 From: thomas Date: Thu, 5 Mar 2026 16:03:24 +0100 Subject: [PATCH] Actualiser update.sh Signed-off-by: thomas --- update.sh | 241 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 206 insertions(+), 35 deletions(-) diff --git a/update.sh b/update.sh index 4f93740..37aaeaf 100644 --- a/update.sh +++ b/update.sh @@ -1,6 +1,15 @@ #!/usr/bin/env bash set -euo pipefail +# ========================================== +# 📦🐳 LXC Updater (TM) - update.sh +# - Logs par date : /var/log/lxc-updater-TM/YYYY-MM-DD/ +# - Spinner sur chaque action +# - Mise à jour OS (apt/apk) + Docker (compose ou run) +# - Docker Compose : pull + up -d --remove-orphans (recreate safe) +# - Docker Run : pull + recreate auto (si jq dispo) sinon restart +# ========================================== + MODE="${1:-unknown}" # 📁 Logs par date @@ -80,7 +89,9 @@ run_cmd() { return 0 } -# 🔎 APT : nombre de paquets upgradables +# ------------------------------ +# 📦 Mise à jour OS +# ------------------------------ apt_upgradable_count() { LC_ALL=C apt-get -s upgrade 2>/dev/null | grep -c '^Inst ' || true } @@ -112,7 +123,13 @@ update_apk() { log "✅ Mise à jour APK terminée" } -# Compose: détecter dirs via labels + vérifier compose file +# ------------------------------ +# 🐳 Mise à jour Docker +# ------------------------------ +docker_image_id() { + docker image inspect -f '{{.Id}}' "$1" 2>/dev/null || true +} + 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 @@ -130,7 +147,6 @@ update_compose_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 @@ -145,43 +161,189 @@ update_compose_dir() { return 2 } -update_docker_compose() { - log "🐳 Mise à jour Docker (Compose)" +recreate_docker_run_container() { + local name="$1" + local image="$2" - 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é" + # jq obligatoire pour recréer proprement + if ! command -v jq >/dev/null 2>&1; then + run_cmd "Redémarrage du conteneur : $name" docker restart "$name" + log "⚠️ 'jq' absent → redémarrage uniquement (recréation automatique impossible)" return 0 fi - log "📁 Projet(s) Compose détecté(s) : ${#valid[@]}" - for d in "${valid[@]}"; do - update_compose_dir "$d" + local tmp="/tmp/lxc-updater-${name}.json" + docker inspect "$name" > "$tmp" 2>>"$LOG_FILE" || true + + local restart_name network_mode user workdir + restart_name="$(jq -r '.[0].HostConfig.RestartPolicy.Name // empty' "$tmp")" + network_mode="$(jq -r '.[0].HostConfig.NetworkMode // "default"' "$tmp")" + user="$(jq -r '.[0].Config.User // empty' "$tmp")" + workdir="$(jq -r '.[0].Config.WorkingDir // empty' "$tmp")" + + mapfile -t envs < <(jq -r '.[0].Config.Env[]? // empty' "$tmp") + mapfile -t binds < <(jq -r '.[0].HostConfig.Binds[]? // empty' "$tmp") + + # Ports: containerPort/proto = HostIp:HostPort + mapfile -t port_rules < <( + jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.key)=\(.value[0].HostIp // ""):\(.value[0].HostPort)"' "$tmp" + ) + + # Réseaux (si default/bridge) + mapfile -t nets < <(jq -r '.[0].NetworkSettings.Networks | keys[]? // empty' "$tmp") + + local new_name="${name}__new" + docker rm -f "$new_name" >>"$LOG_FILE" 2>&1 || true + + args=(docker create --name "$new_name") + + if [[ -n "$restart_name" && "$restart_name" != "no" ]]; then + args+=(--restart "$restart_name") + fi + [[ -n "$user" ]] && args+=(--user "$user") + [[ -n "$workdir" ]] && args+=(--workdir "$workdir") + + for e in "${envs[@]}"; do + args+=(-e "$e") + done + + # volumes/binds -> données conservées + for b in "${binds[@]}"; do + args+=(-v "$b") + done + + # ports + for pr in "${port_rules[@]}"; do + cport="${pr%%=*}" # ex: 8080/tcp + mapping="${pr#*=}" # ex: 0.0.0.0:8080 ou :8080 + cportnum="${cport%/*}" # 8080 + + if [[ "$mapping" == :* ]]; then + hostport="${mapping#:}" + args+=(-p "${hostport}:${cportnum}") + else + hip="${mapping%%:*}" + hp="${mapping#*:}" + args+=(-p "${hip}:${hp}:${cportnum}") + fi + done + + # network mode (si spécial) + if [[ "$network_mode" != "default" && "$network_mode" != "bridge" ]]; then + args+=(--network "$network_mode") + fi + + # image + args+=("$image") + + run_cmd "Création du nouveau conteneur : $name" "${args[@]}" + + # connecter aux réseaux additionnels si needed + if [[ "$network_mode" == "default" || "$network_mode" == "bridge" ]]; then + for n in "${nets[@]}"; do + [[ "$n" == "bridge" || "$n" == "host" || "$n" == "none" ]] && continue + run_cmd "Connexion réseau ($n) : $name" docker network connect "$n" "$new_name" + done + fi + + # switch + run_cmd "Arrêt du conteneur : $name" docker stop "$name" + run_cmd "Renommage de l'ancien conteneur" docker rename "$name" "${name}__old" + run_cmd "Renommage du nouveau conteneur" docker rename "$new_name" "$name" + run_cmd "Démarrage du conteneur : $name" docker start "$name" + run_cmd "Suppression de l'ancien conteneur" docker rm "${name}__old" + + log "✅ Conteneur recréé et redémarré : $name (données conservées via volumes/binds)" +} + +update_docker() { + log "🐳 Mise à jour des conteneurs Docker" + + run_cmd "Vérification Docker" docker ps + + # ---- 1) Compose via labels + log "🔍 Recherche de projets Docker Compose" + mapfile -t dirs < <(compose_dirs_from_labels) + + valid_compose=() + for d in "${dirs[@]}"; do + if is_compose_dir_valid "$d"; then + valid_compose+=("$d") + else + log "⚠️ Projet Compose ignoré (chemin invalide ou sans compose.yml) : $d" + fi + done + + if (( ${#valid_compose[@]} > 0 )); then + log "📁 Projet(s) Docker Compose détecté(s) : ${#valid_compose[@]}" + for d in "${valid_compose[@]}"; do + update_compose_dir "$d" + done + else + log "⚠️ Aucun projet Compose détecté via labels" + fi + + # ---- 2) Conteneurs non-Compose = docker run + mapfile -t run_containers < <( + docker ps -q | while read -r cid; do + p="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project" }}' "$cid" 2>/dev/null || true)" + if [[ -z "$p" || "$p" == "" ]]; then + echo "$cid" + fi + done + ) + + if (( ${#run_containers[@]} == 0 )); then + log "✅ Aucun conteneur 'docker run' détecté" + log "✅ Mise à jour Docker terminée" + return 0 + fi + + log "🧩 Conteneur(s) 'docker run' détecté(s) : ${#run_containers[@]}" + + for cid in "${run_containers[@]}"; do + name="$(docker inspect -f '{{.Name}}' "$cid" | sed 's#^/##')" + image="$(docker inspect -f '{{.Config.Image}}' "$cid")" + + log "📦 Conteneur : $name" + log " 🧱 Image : $image" + + old_id="$(docker_image_id "$image")" + run_cmd "Téléchargement des mises à jour : $name" docker pull "$image" + new_id="$(docker_image_id "$image")" + + if [[ -n "$old_id" && -n "$new_id" && "$old_id" == "$new_id" ]]; then + log "✅ Aucun changement d'image : $name" + continue + fi + + log "⬆️ Image mise à jour pour : $name" + if [[ -n "$old_id" && -n "$new_id" ]]; then + log " 🔁 ${old_id:0:20} ➜ ${new_id:0:20}" + fi + + recreate_docker_run_container "$name" "$image" done log "✅ Mise à jour Docker terminée" } -# --- Parsing MODE (apt/apk + docker) --- +# ------------------------------ +# 🔎 Mode : base + docker éventuel +# Exemples : apt, apk, apt+docker, apk+docker, docker, unknown +# ------------------------------ BASE_MODE="${MODE%%+docker}" HAS_DOCKER="0" [[ "$MODE" == *"+docker" ]] && HAS_DOCKER="1" -# ─────────────── Sortie lisible ─────────────── +# Si MODE == "docker" seul (au cas où), on active docker +if [[ "$MODE" == "docker" ]]; then + HAS_DOCKER="1" +fi +# ------------------------------ +# 🖥️ Sortie lisible +# ------------------------------ log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log "📦 Mise à jour du système" log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" @@ -189,18 +351,27 @@ log "━━━━━━━━━━━━━━━━━━━━━━━━━ case "$BASE_MODE" in apt) update_apt ;; apk) update_apk ;; - *) log "⚠️ Impossible de détecter le gestionnaire de paquets (apt/apk)" ;; + *) + # Si apt/apk non détecté mais apt-get existe, on tente apt + if command -v apt-get >/dev/null 2>&1; then + update_apt + elif command -v apk >/dev/null 2>&1; then + update_apk + else + log "⚠️ Impossible de détecter le gestionnaire de paquets (apt/apk)" + fi + ;; esac -if [[ "$HAS_DOCKER" == "1" ]]; then - log "" - log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - log "🐳 Mise à jour du ou des conteneurs" - log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" - update_docker_compose +log "" +log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +log "🐳 Mise à jour du ou des conteneurs" +log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +if command -v docker >/dev/null 2>&1; then + update_docker else - log "" - log "🐳 Aucun conteneur Docker détecté" + log "🐳 Docker non présent" fi log ""