This commit is contained in:
2026-04-01 13:27:26 +02:00
parent f93fb53638
commit d67d0f936e
2 changed files with 74 additions and 121 deletions

View File

@@ -142,18 +142,6 @@ services:
- composeupdater.mode=update
```
#### 📦 Stack-Level Label
Du kannst das Verhalten auch für den gesamten Stack setzen:
```yml
labels:
- composeupdater.mode=ignore
```
Das Stack-Level Label definiert den Standard für alle Services im Stack
Service-Labels können diesen Standard überschreiben
---
## 🗑️ Prune / Cleanup
@@ -321,25 +309,26 @@ chmod +x script.sh
## 📄 Beispiel Ausgabe
```
Prüfe Stack: rss
├─ read (phpdockerio/readability-js-server)
├─ merc (wangqiru/mercury-parser-api)
├─ full-text-rss (heussd/fivefilters-full-text-rss:latest)
├─ rss-bridge (rssbridge/rss-bridge:latest)
🔍 Prüfe Stack: rss
├─ read (phpdockerio/readability-js-server) [Mode: 🔄 update]
├─ merc (wangqiru/mercury-parser-api) [Mode: 🔄 update]
├─ full-text-rss (heussd/fivefilters-full-text-rss:latest) [Mode: 🔄 update]
├─ rss-bridge (rssbridge/rss-bridge:latest) [Mode: 🔄 update]
⬆ UPDATE
alt: rssbridge/rss-bridge:latest@sha256:55215923cf81b2fa6fbb7ecc1bd2555405f4fc06029ae9876e91164a735c7b9d
neu: rssbridge/rss-bridge:latest@sha256:f3f0218c8b075cbc7c559c8e6852888e95fa6d68258436da6195efc5ab98b025
└─ freshrss (freshrss/freshrss:latest)
🔄 Stack wird neu deployt (Trigger: rss-bridge)
⏳ Deploy läuft...
Stack erfolgreich aktualisiert
Keine Healthchecks definiert → überspringe warten
⏱ Dauer: 18s
└─ freshrss (freshrss/freshrss:latest) [Mode: 🔄 update]
♻️ Stack wird neu deployt (Trigger: rss-bridge)
⏳ Deploy läuft...
Stack erfolgreich aktualisiert
💤 Warte 60s nach Deploy
Keine Healthchecks definiert → überspringe warten
🕒 Dauer: 18s
→ Prüfe Stack: tinymediamanager
└─ tinymediamanager (tinymediamanager/tinymediamanager:latest)
Dauer: 1s
└─ tinymediamanager [Mode: 🚫 ignore]
🕒 Dauer: 1s
```
---

View File

@@ -18,6 +18,9 @@ level_to_int() {
esac
}
INDENT=" "
SUBINDENT=" "
should_log() {
[ "$(level_to_int "$1")" -ge "$(level_to_int "$LOG_LEVEL")" ]
}
@@ -46,45 +49,6 @@ fi
get_service_mode() {
local svc="$1"
echo "$compose_json" \
| jq -r --arg svc "$svc" '
.services[$svc].labels // []
| map(select(startswith("composeupdater.mode=")))
| .[0] // "composeupdater.mode=update"
| split("=")[1]
'
}
get_stack_mode() {
echo "$compose_json" \
| jq -r '
.labels // []
| map(select(startswith("composeupdater.mode=")))
| .[0] // "composeupdater.mode=update"
| split("=")[1]
'
}
is_excluded() {
local svc="$1"
for ex in "${EXCLUDE_SERVICES[@]}"; do
[[ "$svc" == "$ex" ]] && return 0
done
return 1
}
is_stack_excluded() {
local stack="$1"
for ex in "${EXCLUDE_STACKS[@]}"; do
[[ "$stack" == "$ex" ]] && return 0
done
return 1
}
is_label_excluded() {
local svc="$1"
echo "$compose_json" \
| jq -r --arg svc "$svc" '
.services[$svc].labels // {}
@@ -92,11 +56,11 @@ is_label_excluded() {
then map(split("=") | {(.[0]): .[1]}) | add
else .
end)
| .["composeupdater.enable"] // empty
' \
| grep -qi "^false$"
| .["composeupdater.mode"] // "update"
'
}
get_image() {
local svc="$1"
@@ -174,20 +138,20 @@ wait_for_healthy() {
local start
start=$(date +%s)
# 👉 Containerliste bestimmen
# Containerliste bestimmen
if [ ${#services[@]} -gt 0 ]; then
mapfile -t cids < <(docker compose ps -aq "${services[@]}")
else
mapfile -t cids < <(docker compose ps -aq)
fi
# 👉 keine Container → nichts zu tun
# keine Container → nichts zu tun
if [ ${#cids[@]} -eq 0 ]; then
log INFO " Keine Container gefunden → überspringe Healthcheck"
log INFO "${SUBINDENT} Keine Container gefunden → überspringe Healthcheck"
return 0
fi
# 👉 prüfen ob überhaupt Healthchecks existieren
# prüfen ob überhaupt Healthchecks existieren
local has_healthcheck=false
for cid in "${cids[@]}"; do
@@ -199,13 +163,13 @@ wait_for_healthy() {
fi
done
# 👉 wenn keiner vorhanden → direkt raus
# wenn keiner vorhanden → direkt raus
if [ "$has_healthcheck" = false ]; then
log INFO " Keine Healthchecks definiert → überspringe warten"
log INFO "${SUBINDENT} Keine Healthchecks → überspringe warten"
return 0
fi
log INFO " ⏳ Warte auf healthy Container (max ${timeout}s)"
log INFO "${SUBINDENT}⏳ Warte auf healthy Container (max ${timeout}s)"
while true; do
local all_ok=true
@@ -220,7 +184,7 @@ wait_for_healthy() {
done
if [ "$all_ok" = true ]; then
log INFO " ✔️ Alle Container healthy"
log INFO "${SUBINDENT}💚 Alle Container healthy"
return 0
fi
@@ -228,7 +192,7 @@ wait_for_healthy() {
now=$(date +%s)
if [ $((now - start)) -ge "$timeout" ]; then
log WARN " ⚠️ Healthcheck Timeout erreicht"
log WARN "${SUBINDENT}⚠️ Healthcheck Timeout erreicht"
return 1
fi
@@ -288,7 +252,7 @@ while IFS= read -r -d '' file; do
stack=$(basename "$dir")
log INFO ""
log INFO " Prüfe Stack: $stack"
log INFO "🔍 Prüfe Stack: $stack"
stack_start=$(date +%s)
@@ -296,8 +260,6 @@ while IFS= read -r -d '' file; do
compose_json=$(docker compose config --format json)
stack_mode=$(get_stack_mode)
mapfile -t services < <(docker compose config --services)
total_services=${#services[@]}
current_index=0
@@ -331,33 +293,36 @@ while IFS= read -r -d '' file; do
fi
# =============================
# Mode bestimmen (Service > Stack)
# Mode (nur Service!)
# =============================
mode=$(get_service_mode "$svc")
if [ -z "$mode" ]; then
mode="$stack_mode"
fi
case "$mode" in
update) mode_label="Mode: 🔄 update" ;;
notify-only) mode_label="Mode: 🔔 notify-only" ;;
ignore) mode_label="Mode: 🚫 ignore" ;;
*)
mode_label="Mode: ❓ unknown"
mode="update"
;;
esac
case "$mode" in
ignore)
log INFO " $prefix $svc (ignore)"
log INFO "${INDENT}$prefix $svc [$mode_label]"
continue
;;
notify-only)
log INFO " $prefix $svc (notify-only)"
log INFO "${INDENT}$prefix $svc ($image) [$mode_label]"
before_id=$(get_container_image_id "$svc")
# 👉 ohne Container kein Vergleich sinnvoll
if [ -z "$before_id" ]; then
continue
fi
# 👉 Pull nur einmal pro Image
if [ -z "${pulled_images[$image]:-}" ]; then
pull_with_retry "$image" || true
pulled_images[$image]=1
@@ -373,21 +338,15 @@ while IFS= read -r -d '' file; do
;;
update)
# normal weiterlaufen lassen
log INFO "${INDENT}$prefix $svc ($image) [$mode_label]"
;;
esac
*)
log WARN " $prefix $svc (unbekannter mode: $mode → fallback=update)"
;;
esac
log INFO " $prefix $svc ($image)"
before_id=$(get_container_image_id "$svc")
# 👉 Skip wenn kein Container und nicht erlaubt
if [ -z "$before_id" ] && [ "$UPDATE_INCLUDE_STOPPED" = false ]; then
log INFO " ⏭️ übersprungen (kein Container vorhanden)"
log INFO "${SUBINDENT}⏭️ übersprungen (kein Container vorhanden)"
continue
fi
@@ -397,13 +356,13 @@ while IFS= read -r -d '' file; do
if [ -z "${pulled_images[$image]:-}" ]; then
if ! pull_with_retry "$image"; then
log ERROR " ❌ Pull fehlgeschlagen"
log ERROR "${INDENT}❌ Pull fehlgeschlagen"
error_flag=true
continue
fi
pulled_images[$image]=1
else
log DEBUG " ⏩ Pull übersprungen (bereits gemacht)"
log DEBUG "${SUBINDENT}⏩ Pull übersprungen (bereits gemacht)"
fi
after_id=$(get_local_image_id "$image")
@@ -427,9 +386,9 @@ while IFS= read -r -d '' file; do
stack_updated=true
changed_services+=("$svc")
log INFO " ⬆️ UPDATE"
log INFO " alt: ${image}@${before_id}"
log INFO " neu: ${image}@${after_id}"
log INFO "${SUBINDENT}⬆️ UPDATE"
log INFO "${SUBINDENT} alt: ${image}@${before_id}"
log INFO "${SUBINDENT} neu: ${image}@${after_id}"
short_before="${before_id#sha256:}"
short_before="${short_before:0:6}"
@@ -448,7 +407,9 @@ while IFS= read -r -d '' file; do
if [ "$total_services" -eq 1 ]; then
svc="${services[0]}"
log INFO " 🔄 Einzelcontainer-Update: $svc"
log INFO ""
log INFO "${INDENT}🔄 Einzelcontainer-Update: $svc"
if [ "${was_running[$svc]}" = 1 ]; then
run_cmd docker compose up -d "$svc" --remove-orphans
@@ -460,44 +421,44 @@ while IFS= read -r -d '' file; do
fi
fi
log INFO " ✔️ Container $svc aktualisiert"
log INFO "${SUBINDENT} Container aktualisiert"
# 👉 feste Wartezeit
if [ "${REDEPLOY_WAIT:-0}" -gt 0 ]; then
log INFO " Warte ${REDEPLOY_WAIT}s nach Deploy"
log INFO "${SUBINDENT}💤 Warte ${REDEPLOY_WAIT}s nach Deploy"
sleep "$REDEPLOY_WAIT"
fi
# 👉 optionaler Healthcheck
if [ "$REDEPLOY_WAIT_HEALTHY" = true ]; then
wait_for_healthy "$REDEPLOY_WAIT_HEALTHY_TIMEOUT" "$svc"
else
log INFO "${SUBINDENT} Keine Healthchecks → überspringe warten"
fi
else
log INFO " 🔄 Stack wird neu deployt (Trigger: ${changed_services[*]})"
log INFO " ⏳ Deploy läuft..."
log INFO ""
log INFO "${INDENT}♻️ Stack wird neu deployt (Trigger: ${changed_services[*]})"
log INFO "${SUBINDENT}⏳ Deploy läuft..."
if ! run_cmd docker compose up -d --remove-orphans >/dev/null 2>&1; then
log ERROR " ❌ Stack Update fehlgeschlagen"
log ERROR "${SUBINDENT}❌ Stack Update fehlgeschlagen"
error_flag=true
else
log INFO " ✔️ Stack erfolgreich aktualisiert"
log INFO "${SUBINDENT} Stack erfolgreich aktualisiert"
# 👉 feste Wartezeit
if [ "${REDEPLOY_WAIT:-0}" -gt 0 ]; then
log INFO " Warte ${REDEPLOY_WAIT}s nach Deploy"
log INFO "${SUBINDENT}💤 Warte ${REDEPLOY_WAIT}s nach Deploy"
sleep "$REDEPLOY_WAIT"
fi
# 👉 optionaler Healthcheck
if [ "$REDEPLOY_WAIT_HEALTHY" = true ]; then
wait_for_healthy "$REDEPLOY_WAIT_HEALTHY_TIMEOUT" "${changed_services[@]}"
else
log INFO "${SUBINDENT} Keine Healthchecks → überspringe warten"
fi
# 👉 gestoppte wieder stoppen
for svc in "${services[@]}"; do
if [ "${was_running[$svc]}" = 0 ]; then
log INFO " ⏹️ Stoppe $svc (war vorher gestoppt)"
log INFO "${SUBINDENT}⏹️ Stoppe $svc (war vorher gestoppt)"
run_cmd docker compose stop "$svc" >/dev/null 2>&1 || true
fi
done
@@ -536,7 +497,7 @@ while IFS= read -r -d '' file; do
cd "$PATH_COMPOSE_DIR"
stack_end=$(date +%s)
log INFO " Dauer: $((stack_end - stack_start))s"
log INFO "${INDENT}🕒 Dauer: $((stack_end - stack_start))s"
done < <(find . -name "$PATH_COMPOSE_PATTERN" -print0 | sort -z)
@@ -546,6 +507,8 @@ done < <(find . -name "$PATH_COMPOSE_PATTERN" -print0 | sort -z)
freed_space="0"
log INFO ""
if [ "$CLEANUP_ENABLED" = true ]; then
if [ "$CLEANUP_ONLY_ON_UPDATE" = true ] && \
@@ -554,7 +517,8 @@ if [ "$CLEANUP_ENABLED" = true ]; then
else
before_size=$(get_docker_disk_usage)
log INFO "🧹 Docker Cleanup läuft..."
log INFO ""
log INFO "${INDENT}🧹 Docker Cleanup läuft..."
if [ "$CLEANUP_IMAGES_ENABLED" = true ]; then
case "$CLEANUP_IMAGES_MODE" in
@@ -578,7 +542,7 @@ if [ "$CLEANUP_ENABLED" = true ]; then
after_size=$(get_docker_disk_usage)
freed_space=$((after_size < before_size ? before_size - after_size : 0))
log INFO "✔️ Cleanup abgeschlossen (${freed_space} MB freigegeben)"
log INFO "${INDENT} Cleanup abgeschlossen (${freed_space} MB freigegeben)"
fi
fi
@@ -625,9 +589,9 @@ if [ "$NTFY_ENABLED" = true ]; then
fi
send_ntfy "$msg" "$PRIORITY"
log INFO "ntfy Nachricht gesendet (prio=$PRIORITY)"
log INFO "📨 ntfy Nachricht gesendet (prio=$PRIORITY)"
fi
script_end=$(date +%s)
log INFO " Gesamtzeit: $((script_end - script_start))s"
log INFO "🕒 Gesamtzeit: $((script_end - script_start))s"
log INFO "==== Update beendet ===="