151
update.sh
151
update.sh
@@ -2,8 +2,11 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
MODE="${1:-unknown}"
|
MODE="${1:-unknown}"
|
||||||
|
STAGE="${2:-all}" # system | docker | all
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
# 📁 Logs par date
|
# 📁 Logs par date
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
LOG_ROOT="/var/log/lxc-updater-TM"
|
LOG_ROOT="/var/log/lxc-updater-TM"
|
||||||
DATE="$(date +%F)"
|
DATE="$(date +%F)"
|
||||||
TS="$(date +%H%M%S)"
|
TS="$(date +%H%M%S)"
|
||||||
@@ -26,7 +29,23 @@ log_file_only() {
|
|||||||
echo "[$(date -Is)] $*" >>"$LOG_ARCHIVE"
|
echo "[$(date -Is)] $*" >>"$LOG_ARCHIVE"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ spinner
|
# ─────────────────────────────────────────────
|
||||||
|
# 🎛️ Header (style Proxmox)
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
header() {
|
||||||
|
local title="$1"
|
||||||
|
log "╔══════════════════════════════════════╗"
|
||||||
|
log "║ LXC Updater (TM) ║"
|
||||||
|
log "╚══════════════════════════════════════╝"
|
||||||
|
log "🖥️ Conteneur : $(hostname)"
|
||||||
|
log "📅 Date : ${DATE} 🕒 Heure : ${TS}"
|
||||||
|
log "📌 Étape : ${title}"
|
||||||
|
log ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
|
# ⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ Spinner
|
||||||
|
# ─────────────────────────────────────────────
|
||||||
spinner_start() {
|
spinner_start() {
|
||||||
local msg="$1"
|
local msg="$1"
|
||||||
local -a frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
|
local -a frames=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
|
||||||
@@ -62,6 +81,7 @@ spinner_stop() {
|
|||||||
|
|
||||||
run_cmd() {
|
run_cmd() {
|
||||||
local title="$1"; shift
|
local title="$1"; shift
|
||||||
|
|
||||||
spinner_start "$title"
|
spinner_start "$title"
|
||||||
log_file_only "----- ${title} -----"
|
log_file_only "----- ${title} -----"
|
||||||
log_file_only "CMD: $*"
|
log_file_only "CMD: $*"
|
||||||
@@ -80,9 +100,9 @@ run_cmd() {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------
|
# ─────────────────────────────────────────────
|
||||||
# 📦 Mise à jour OS
|
# 📦 Mise à jour OS
|
||||||
# ------------------------------
|
# ─────────────────────────────────────────────
|
||||||
apt_upgradable_count() {
|
apt_upgradable_count() {
|
||||||
LC_ALL=C apt-get -s upgrade 2>/dev/null | grep -c '^Inst ' || true
|
LC_ALL=C apt-get -s upgrade 2>/dev/null | grep -c '^Inst ' || true
|
||||||
}
|
}
|
||||||
@@ -114,9 +134,9 @@ update_apk() {
|
|||||||
log "✅ Mise à jour APK terminée"
|
log "✅ Mise à jour APK terminée"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------
|
# ─────────────────────────────────────────────
|
||||||
# 🐳 Mise à jour Docker
|
# 🐳 Mise à jour Docker
|
||||||
# ------------------------------
|
# ─────────────────────────────────────────────
|
||||||
docker_image_id() {
|
docker_image_id() {
|
||||||
docker image inspect -f '{{.Id}}' "$1" 2>/dev/null || true
|
docker image inspect -f '{{.Id}}' "$1" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
@@ -132,18 +152,25 @@ is_compose_dir_valid() {
|
|||||||
[[ -d "$d" ]] && { [[ -f "$d/docker-compose.yml" ]] || [[ -f "$d/compose.yml" ]]; }
|
[[ -d "$d" ]] && { [[ -f "$d/docker-compose.yml" ]] || [[ -f "$d/compose.yml" ]]; }
|
||||||
}
|
}
|
||||||
|
|
||||||
update_compose_dir() {
|
compose_services_in_dir() {
|
||||||
local dir="$1"
|
local dir="$1"
|
||||||
log "📂 Projet : $dir"
|
|
||||||
|
|
||||||
# 🧾 Liste des services/containeurs du projet
|
|
||||||
local services=""
|
local services=""
|
||||||
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
if docker compose version >/dev/null 2>&1; then
|
||||||
services="$(cd "$dir" && docker compose config --services 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')"
|
services="$(cd "$dir" && docker compose config --services 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')"
|
||||||
elif command -v docker-compose >/dev/null 2>&1; then
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
services="$(cd "$dir" && docker-compose config --services 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')"
|
services="$(cd "$dir" && docker-compose config --services 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
echo "$services"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_compose_dir() {
|
||||||
|
local dir="$1"
|
||||||
|
log "📂 Projet : $dir"
|
||||||
|
|
||||||
|
local services
|
||||||
|
services="$(compose_services_in_dir "$dir")"
|
||||||
if [[ -n "$services" ]]; then
|
if [[ -n "$services" ]]; then
|
||||||
log "📦 Conteneurs/Services du projet : $services"
|
log "📦 Conteneurs/Services du projet : $services"
|
||||||
log "🔁 Mise à jour des conteneurs : $services"
|
log "🔁 Mise à jour des conteneurs : $services"
|
||||||
@@ -151,7 +178,6 @@ update_compose_dir() {
|
|||||||
log "📦 Conteneurs/Services du projet : (inconnus)"
|
log "📦 Conteneurs/Services du projet : (inconnus)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ✅ Pull + recreate safe
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
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 "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 --remove-orphans"
|
run_cmd "Redéploiement des services (Compose)" bash -c "cd '$dir' && docker compose up -d --remove-orphans"
|
||||||
@@ -172,7 +198,6 @@ recreate_docker_run_container() {
|
|||||||
local name="$1"
|
local name="$1"
|
||||||
local image="$2"
|
local image="$2"
|
||||||
|
|
||||||
# jq obligatoire pour recréer proprement
|
|
||||||
if ! command -v jq >/dev/null 2>&1; then
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
run_cmd "Redémarrage du conteneur : $name" docker restart "$name"
|
run_cmd "Redémarrage du conteneur : $name" docker restart "$name"
|
||||||
log "⚠️ 'jq' absent → redémarrage uniquement (recréation automatique impossible)"
|
log "⚠️ 'jq' absent → redémarrage uniquement (recréation automatique impossible)"
|
||||||
@@ -190,13 +215,9 @@ recreate_docker_run_container() {
|
|||||||
|
|
||||||
mapfile -t envs < <(jq -r '.[0].Config.Env[]? // empty' "$tmp")
|
mapfile -t envs < <(jq -r '.[0].Config.Env[]? // empty' "$tmp")
|
||||||
mapfile -t binds < <(jq -r '.[0].HostConfig.Binds[]? // empty' "$tmp")
|
mapfile -t binds < <(jq -r '.[0].HostConfig.Binds[]? // empty' "$tmp")
|
||||||
|
|
||||||
# Ports: containerPort/proto = HostIp:HostPort
|
|
||||||
mapfile -t port_rules < <(
|
mapfile -t port_rules < <(
|
||||||
jq -r '.[0].HostConfig.PortBindings | to_entries[]? | "\(.key)=\(.value[0].HostIp // ""):\(.value[0].HostPort)"' "$tmp"
|
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")
|
mapfile -t nets < <(jq -r '.[0].NetworkSettings.Networks | keys[]? // empty' "$tmp")
|
||||||
|
|
||||||
local new_name="${name}__new"
|
local new_name="${name}__new"
|
||||||
@@ -213,18 +234,14 @@ recreate_docker_run_container() {
|
|||||||
for e in "${envs[@]}"; do
|
for e in "${envs[@]}"; do
|
||||||
args+=(-e "$e")
|
args+=(-e "$e")
|
||||||
done
|
done
|
||||||
|
|
||||||
# volumes/binds -> données conservées
|
|
||||||
for b in "${binds[@]}"; do
|
for b in "${binds[@]}"; do
|
||||||
args+=(-v "$b")
|
args+=(-v "$b")
|
||||||
done
|
done
|
||||||
|
|
||||||
# ports
|
|
||||||
for pr in "${port_rules[@]}"; do
|
for pr in "${port_rules[@]}"; do
|
||||||
cport="${pr%%=*}" # ex: 8080/tcp
|
cport="${pr%%=*}" # 8080/tcp
|
||||||
mapping="${pr#*=}" # ex: 0.0.0.0:8080 ou :8080
|
mapping="${pr#*=}" # 0.0.0.0:8080 ou :8080
|
||||||
cportnum="${cport%/*}" # 8080
|
cportnum="${cport%/*}" # 8080
|
||||||
|
|
||||||
if [[ "$mapping" == :* ]]; then
|
if [[ "$mapping" == :* ]]; then
|
||||||
hostport="${mapping#:}"
|
hostport="${mapping#:}"
|
||||||
args+=(-p "${hostport}:${cportnum}")
|
args+=(-p "${hostport}:${cportnum}")
|
||||||
@@ -235,17 +252,14 @@ recreate_docker_run_container() {
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# network mode (si spécial)
|
|
||||||
if [[ "$network_mode" != "default" && "$network_mode" != "bridge" ]]; then
|
if [[ "$network_mode" != "default" && "$network_mode" != "bridge" ]]; then
|
||||||
args+=(--network "$network_mode")
|
args+=(--network "$network_mode")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# image
|
|
||||||
args+=("$image")
|
args+=("$image")
|
||||||
|
|
||||||
run_cmd "Création du nouveau conteneur : $name" "${args[@]}"
|
run_cmd "Création du nouveau conteneur : $name" "${args[@]}"
|
||||||
|
|
||||||
# connecter aux réseaux additionnels si needed
|
|
||||||
if [[ "$network_mode" == "default" || "$network_mode" == "bridge" ]]; then
|
if [[ "$network_mode" == "default" || "$network_mode" == "bridge" ]]; then
|
||||||
for n in "${nets[@]}"; do
|
for n in "${nets[@]}"; do
|
||||||
[[ "$n" == "bridge" || "$n" == "host" || "$n" == "none" ]] && continue
|
[[ "$n" == "bridge" || "$n" == "host" || "$n" == "none" ]] && continue
|
||||||
@@ -253,7 +267,6 @@ recreate_docker_run_container() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# switch
|
|
||||||
run_cmd "Arrêt du conteneur : $name" docker stop "$name"
|
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 de l'ancien conteneur" docker rename "$name" "${name}__old"
|
||||||
run_cmd "Renommage du nouveau conteneur" docker rename "$new_name" "$name"
|
run_cmd "Renommage du nouveau conteneur" docker rename "$new_name" "$name"
|
||||||
@@ -268,7 +281,7 @@ update_docker() {
|
|||||||
|
|
||||||
run_cmd "Vérification Docker" docker ps
|
run_cmd "Vérification Docker" docker ps
|
||||||
|
|
||||||
# ---- 1) Compose via labels
|
# ---- 1) Compose
|
||||||
log "🔍 Recherche de projets Docker Compose"
|
log "🔍 Recherche de projets Docker Compose"
|
||||||
mapfile -t dirs < <(compose_dirs_from_labels)
|
mapfile -t dirs < <(compose_dirs_from_labels)
|
||||||
|
|
||||||
@@ -290,7 +303,7 @@ update_docker() {
|
|||||||
log "⚠️ Aucun projet Compose détecté via labels"
|
log "⚠️ Aucun projet Compose détecté via labels"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ---- 2) Conteneurs non-Compose = docker run
|
# ---- 2) docker run (non-Compose)
|
||||||
mapfile -t run_containers < <(
|
mapfile -t run_containers < <(
|
||||||
docker ps -q | while read -r cid; do
|
docker ps -q | while read -r cid; do
|
||||||
p="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project" }}' "$cid" 2>/dev/null || true)"
|
p="$(docker inspect -f '{{ index .Config.Labels "com.docker.compose.project" }}' "$cid" 2>/dev/null || true)"
|
||||||
@@ -335,53 +348,49 @@ update_docker() {
|
|||||||
log "✅ Mise à jour Docker terminée"
|
log "✅ Mise à jour Docker terminée"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------
|
# ─────────────────────────────────────────────
|
||||||
# 🔎 Mode : base + docker éventuel
|
# 🔎 Détection base_mode depuis MODE
|
||||||
# Exemples : apt, apk, apt+docker, apk+docker, docker, unknown
|
# MODE attendu : apt / apk / apt+docker / apk+docker / docker / unknown
|
||||||
# ------------------------------
|
# ─────────────────────────────────────────────
|
||||||
BASE_MODE="${MODE%%+docker}"
|
BASE_MODE="${MODE%%+docker}"
|
||||||
HAS_DOCKER="0"
|
|
||||||
[[ "$MODE" == *"+docker" ]] && HAS_DOCKER="1"
|
|
||||||
|
|
||||||
# Si MODE == "docker" seul (au cas où), on active docker
|
# ─────────────────────────────────────────────
|
||||||
if [[ "$MODE" == "docker" ]]; then
|
# 🚦 Exécution selon STAGE
|
||||||
HAS_DOCKER="1"
|
# ─────────────────────────────────────────────
|
||||||
|
if [[ "$STAGE" == "system" || "$STAGE" == "all" ]]; then
|
||||||
|
header "Mise à jour du système"
|
||||||
|
|
||||||
|
case "$BASE_MODE" in
|
||||||
|
apt) update_apt ;;
|
||||||
|
apk) update_apk ;;
|
||||||
|
*)
|
||||||
|
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
|
||||||
|
|
||||||
|
log ""
|
||||||
|
log "🧾 Log update : ${LOG_FILE}"
|
||||||
|
log "🗃️ Archive update : ${LOG_ARCHIVE}"
|
||||||
|
log "🎉 Étape terminée"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------
|
if [[ "$STAGE" == "docker" || "$STAGE" == "all" ]]; then
|
||||||
# 🖥️ Sortie lisible
|
header "Mise à jour des conteneurs Docker"
|
||||||
# ------------------------------
|
|
||||||
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
log "📦 Mise à jour du système"
|
|
||||||
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
||||||
|
|
||||||
case "$BASE_MODE" in
|
if command -v docker >/dev/null 2>&1; then
|
||||||
apt) update_apt ;;
|
update_docker
|
||||||
apk) update_apk ;;
|
else
|
||||||
*)
|
log "🐳 Docker non présent"
|
||||||
# Si apt/apk non détecté mais apt-get existe, on tente apt
|
fi
|
||||||
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
|
|
||||||
|
|
||||||
log ""
|
log ""
|
||||||
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
log "🧾 Log update : ${LOG_FILE}"
|
||||||
log "🐳 Mise à jour du ou des conteneurs"
|
log "🗃️ Archive update : ${LOG_ARCHIVE}"
|
||||||
log "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
log "🎉 Étape terminée"
|
||||||
|
fi
|
||||||
if command -v docker >/dev/null 2>&1; then
|
|
||||||
update_docker
|
|
||||||
else
|
|
||||||
log "🐳 Docker non présent"
|
|
||||||
fi
|
|
||||||
|
|
||||||
log ""
|
|
||||||
log "🧾 Log update : ${LOG_FILE}"
|
|
||||||
log "🗃️ Archive update : ${LOG_ARCHIVE}"
|
|
||||||
log "🎉 Mise à jour terminée"
|
|
||||||
Reference in New Issue
Block a user