deploy/vektor-docker-updates.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
# vektor-docker-updates — scan / apply des mises à jour d'images Docker.
#
# scan : docker compose pull de chaque stack compose-managée (CT 103/104),
# puis liste des conteneurs dont l'ID image courant diffère de
# l'ID fraîchement pullé -> « MAJ disponible » ou « à jour ».
# NE REDÉMARRE RIEN.
# list : comme scan (pull + comparaison des digests distants) mais
# affiche PILE PAR PILE chaque conteneur : tag courant de son
# image vs digest de la dernière version disponible. Rapport
# lecture seule (/majlist) : n'alimente ni OUI ni apply.
# apply : pour chaque stack avec au moins un conteneur à image plus récente
# (hors blacklist), docker compose up -d (recrée avec la nouvelle
# image). Compose-only : qbittorrent (hors compose) jamais touché.
#
# Sécurité :
# - CT whitelistés : 103 (arr-stack), 104 (tools), 111 (indexer/bitmagnet).
# Rien d'autre.
# - Blacklist de noms : authelia, portainer, healthchecks, sftpgo.
# - Garde-fou stream (apply) : refuse si une lecture Jellyfin est en cours
# (état du stream-guard, rafraîchi à chaque tick 60 s).
# - Appelé uniquement par vektor-actions (canal SSH verrouillé).
# Design : BLACKLIST/MODE voyagent par ENVIRONNEMENT (hérité par pct exec),
# zéro substitution de chaîne dans le snippet (gotcha quotes).
set -u
export MODE="${1:-scan}"
case "$MODE" in
scan|apply|list) ;;
*) echo "REFUS: mode inconnu (scan|apply)"; exit 1 ;;
esac
CTS="103 104 111"
export BLACKLIST='^(authelia|portainer|healthchecks|sftpgo)$'
if [ "$MODE" = "apply" ]; then
# Garde-fou stream : le stream-guard écrit son état à chaque tick.
if [ -f /var/lib/vektor/stream-guard-sess.json ]; then
NPLAYING=$(python3 -c "import json;print(len([s for s in json.load(open('/var/lib/vektor/stream-guard-sess.json')) if s.get('NowPlayingItem')]))" 2>/dev/null || echo 0)
[ "${NPLAYING:-0}" -gt 0 ] && { echo "REFUS: lecture Jellyfin en cours ($NPLAYING stream) — réessaie plus tard"; exit 1; }
fi
fi
DISCOVER='
for d in $(docker ps --format "{{.Names}}" | while read n; do
docker inspect "$n" -f "{{index .Config.Labels \"com.docker.compose.project.working_dir\"}}" 2>/dev/null
done | sort -u); do
[ -f "$d/docker-compose.yml" ] || [ -f "$d/compose.yml" ] || continue
cd "$d" 2>/dev/null && echo "$d"
done'
SNIPPET='
for d in $(docker ps --format "{{.Names}}" | while read n; do
docker inspect "$n" -f "{{index .Config.Labels \"com.docker.compose.project.working_dir\"}}" 2>/dev/null
done | sort -u); do
[ -f "$d/docker-compose.yml" ] || [ -f "$d/compose.yml" ] || continue
base=$(basename "$d")
cd "$d" 2>/dev/null || continue
if [ "$MODE" = "list" ]; then
# Rapport detaille : tag courant vs digest de la derniere image dispo.
# Meme semantique que scan : apres pull, un tag qui ne pointe plus vers
# l image du conteneur signale une version plus recente disponible.
for n in $(docker compose ps --quiet 2>/dev/null); do
name=$(docker inspect "$n" -f "{{.Name}}" | sed "s|^/||")
echo "$name" | grep -qiE "$BLACKLIST" && continue
src=$(docker inspect "$n" -f "{{.Config.Image}}")
version=${src#*:}
[ "$version" = "$src" ] && version=latest
img=$(docker inspect "$n" -f "{{.Image}}")
newid=$(docker image inspect "$src" -f "{{.Id}}" 2>/dev/null)
if [ -z "$newid" ]; then
echo " $name : $version (image introuvable localement)"
elif [ "$img" = "$newid" ]; then
echo " $name : $version (a jour)"
else
digest=$(docker image inspect "$newid" -f "{{index .RepoDigests 0}}" 2>/dev/null | sed "s|^.*@||; s|^sha256:||; s|^\(............\).*|\1|")
echo " $name : $version -> NOUVELLE IMAGE dispo (digest ${digest:-?})"
fi
done
continue
fi
todo=""
for n in $(docker compose ps --quiet 2>/dev/null); do
name=$(docker inspect "$n" -f "{{.Name}}" | sed "s|^/||")
echo "$name" | grep -qiE "$BLACKLIST" && continue
img=$(docker inspect "$n" -f "{{.Image}}")
src=$(docker inspect "$n" -f "{{.Config.Image}}")
newid=$(docker image inspect "$src" -f "{{.Id}}" 2>/dev/null)
[ -n "$newid" ] && [ "$img" != "$newid" ] && todo="$todo $name"
done
if [ -z "$todo" ]; then
echo " $base : a jour"
elif [ "$MODE" = "scan" ]; then
echo " $base : MAJ disponible ->$todo"
else
echo " $base : mise a jour de$todo"
docker compose up -d 2>&1 | tail -2
fi
done
'
for CT in $CTS; do
echo "=== CT $CT ==="
if [ "$MODE" != "apply" ]; then
# Pull de toutes les stacks (silencieux) : met à jour les refs locales
pct exec "$CT" -- bash -c "$DISCOVER" 2>/dev/null | while IFS= read -r d; do
pct exec "$CT" -- bash -c "cd '$d' 2>/dev/null && docker compose pull --quiet" >/dev/null 2>&1
done
fi
pct exec "$CT" -- bash -c "$SNIPPET" 2>&1 | grep -v "^WARNING"
done
echo "=== FIN ($MODE) ==="