This commit is contained in:
2026-03-31 14:10:04 +02:00
parent 9a6d3d6551
commit b5fcd0e103
5 changed files with 51 additions and 83 deletions

View File

@@ -14,11 +14,7 @@ Push-Notifications über ntfy bei Updates, Fehlern oder Status
Einzelne Container oder komplette Stacks gezielt vom Update ausschließen Einzelne Container oder komplette Stacks gezielt vom Update ausschließen
- 🗑️ Prune Funktion - 🗑️ Prune Funktion
Entfernt nicht mehr benötigte Images/Container automatisch Entfernt nicht mehr benötigte Images/Container automatisch
- ⚡ Digest-basierter Update-Check (kein Blind-Pull)
Images werden nicht pauschal gepullt. Stattdessen:
Vergleich des lokalen Image-Digests mit dem Remote-Digest
Pull nur bei tatsächlicher Änderung
Spart Bandbreite, Zeit und unnötige Layer-Downloads
--- ---
@@ -36,17 +32,25 @@ Spart Bandbreite, Zeit und unnötige Layer-Downloads
1. Alle `*compose*.yml` Dateien werden rekursiv gefunden 1. Alle `*compose*.yml` Dateien werden rekursiv gefunden
2. Verarbeitung erfolgt alphabetisch (deterministische Reihenfolge) 2. Verarbeitung erfolgt alphabetisch (deterministische Reihenfolge)
3. Für jeden Stack: 3. Für jeden Stack:
- Compose-Konfiguration wird ausgewertet (docker compose config) - Compose-Konfiguration wird ausgewertet (`docker compose config`)
- Verwendete Images werden extrahiert - Services und deren Images werden ermittelt
- Für jedes Image: - Für jedes Image:
- Remote-Digest wird aus der Registry abgefragt - Image wird (einmal pro Stack) gepullt (`docker pull`, nutzt Cache)
- Lokaler Digest wird ermittelt - Lokale Image-ID wird ermittelt
- Vergleich lokal vs. remote - Image-ID des vorhandenen Containers wird ermittelt (auch für gestoppte Container)
4. Entscheidungslogik: 4. Entscheidungslogik:
-Kein Unterschied → kein Pull, kein Restart -Container existiert nicht → kein Update (nur Definition vorhanden)
- ✅ Digest unterschiedlich → Image wird gepullt - ❌ Image-ID unverändert → kein Update
- ✅ Image-ID hat sich geändert → Update erkannt
5. Wenn mindestens ein Service ein Update hat: 5. Wenn mindestens ein Service ein Update hat:
- kompletter Stack wird neu deployed (docker compose up -d) - Einzelcontainer: gezieltes Update des Services
- Mehrere Services: kompletter Stack wird neu deployed (`docker compose up -d`)
6. Sonderverhalten:
- Gestoppte Container werden ebenfalls geprüft und bei Updates berücksichtigt
- Gestoppte Container werden nach dem Update optional wieder gestoppt
- Excluded Services werden gepullt, aber **nicht** neu gestartet.
Verfügbare Updates werden erkannt und per NTFY gemeldet.
- Healthchecks können optional abgewartet werden
--- ---
@@ -176,7 +180,7 @@ NTFY_URL="https://ntfy.example.com/topic"
NTFY_TOKEN="DEIN_TOKEN" NTFY_TOKEN="DEIN_TOKEN"
# Titel mitsenden (optional) [ String ] # Titel mitsenden (optional) [ String ]
NTFY_TITLE="Docker Update ($(hostname))" NTFY_TITLE="Autoupdate Report ($(hostname))"
# Tags mitsenden (optional) [ String ] # Tags mitsenden (optional) [ String ]
NTFY_TAGS="docker,update" NTFY_TAGS="docker,update"

View File

@@ -81,7 +81,7 @@ NTFY_URL="https://ntfy.example.com/topic"
NTFY_TOKEN="DEIN_TOKEN" NTFY_TOKEN="DEIN_TOKEN"
# Titel mitsenden (optional) [ String ] # Titel mitsenden (optional) [ String ]
NTFY_TITLE="Docker Update ($(hostname))" NTFY_TITLE="Autoupdate Report ($(hostname))"
# Tags mitsenden (optional) [ String ] # Tags mitsenden (optional) [ String ]
NTFY_TAGS="docker,update" NTFY_TAGS="docker,update"

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

BIN
img/ntfy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@@ -68,7 +68,7 @@ get_image() {
get_container_image_id() { get_container_image_id() {
local svc="$1" local svc="$1"
local cid local cid
cid=$(docker compose ps -q "$svc" 2>/dev/null || true) cid=$(docker compose ps -aq "$svc" | head -n1)
[ -z "$cid" ] && echo "" && return [ -z "$cid" ] && echo "" && return
docker inspect -f '{{.Image}}' "$cid" 2>/dev/null || echo "" docker inspect -f '{{.Image}}' "$cid" 2>/dev/null || echo ""
} }
@@ -81,7 +81,7 @@ get_local_image_id() {
is_running() { is_running() {
local svc="$1" local svc="$1"
local cid local cid
cid=$(docker compose ps -q "$svc" 2>/dev/null || true) cid=$(docker compose ps -aq "$svc" 2>/dev/null || true)
[ -z "$cid" ] && return 1 [ -z "$cid" ] && return 1
docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null | grep -q true docker inspect -f '{{.State.Running}}' "$cid" 2>/dev/null | grep -q true
} }
@@ -137,9 +137,9 @@ wait_for_healthy() {
# 👉 Containerliste bestimmen # 👉 Containerliste bestimmen
if [ ${#services[@]} -gt 0 ]; then if [ ${#services[@]} -gt 0 ]; then
mapfile -t cids < <(docker compose ps -q "${services[@]}") mapfile -t cids < <(docker compose ps -aq "${services[@]}")
else else
mapfile -t cids < <(docker compose ps -q) mapfile -t cids < <(docker compose ps -aq)
fi fi
# 👉 keine Container → nichts zu tun # 👉 keine Container → nichts zu tun
@@ -197,21 +197,6 @@ wait_for_healthy() {
done done
} }
get_remote_digest() {
local image="$1"
docker manifest inspect "$image" 2>/dev/null \
| grep -m1 '"digest"' \
| awk -F '"' '{print $4}' \
|| true
}
get_local_digest() {
local image="$1"
docker inspect --format='{{index .RepoDigests 0}}' "$image" 2>/dev/null \
| cut -d'@' -f2
}
pull_with_retry() { pull_with_retry() {
local image="$1" local image="$1"
@@ -233,8 +218,9 @@ pull_with_retry() {
return 1 return 1
} }
rate_limit() { container_exists() {
sleep 0.2 local svc="$1"
docker compose ps -aq "$svc" 2>/dev/null | grep -q .
} }
@@ -255,8 +241,11 @@ stack_tree=()
cd "$PATH_COMPOSE_DIR" cd "$PATH_COMPOSE_DIR"
while IFS= read -r -d '' file; do while IFS= read -r -d '' file; do
declare -A pulled_images
dir=$(dirname "$file") dir=$(dirname "$file")
stack=$(basename "$dir") stack=$(basename "$dir")
@@ -325,59 +314,34 @@ while IFS= read -r -d '' file; do
fi fi
# ============================= # =============================
# Digest Check # Pull + Vergleich (NEU)
# ============================= # =============================
local_digest=$(get_local_digest "$image")
if [ -z "$local_digest" ]; then
update_needed=true
elif [ -n "$before_id" ]; then
# 👉 nur bei laufenden Containern
remote_digest=$(get_remote_digest "$image" || true)
rate_limit
if [ -z "$remote_digest" ]; then
log DEBUG " ⚠️ kein Remote Digest → fallback auf pull"
update_needed=true
elif [ "$remote_digest" != "$local_digest" ]; then
update_needed=true
else
update_needed=false
fi
else
# 👉 gestoppter Container → KEIN manifest check
update_needed=true
fi
# =============================
# Pull + echte Prüfung
# =============================
if [ "$update_needed" = true ]; then
# 👉 Pull (nutzt Cache → schnell wenn nichts neu)
if [ -z "${pulled_images[$image]:-}" ]; then
if ! pull_with_retry "$image"; then if ! pull_with_retry "$image"; then
log ERROR " ❌ Pull fehlgeschlagen" log ERROR " ❌ Pull fehlgeschlagen"
error_flag=true error_flag=true
continue continue
fi fi
pulled_images[$image]=1
else
log DEBUG " ⏩ Pull übersprungen (bereits gemacht)"
fi
after_id=$(get_local_image_id "$image") after_id=$(get_local_image_id "$image")
# 👉 Nur vergleichen wenn Container existiert if ! container_exists "$svc"; then
if [ -n "$before_id" ]; then # 👉 Container existiert nicht → KEIN Update
if [ "$before_id" != "$after_id" ]; then update_needed=false
elif [ "$before_id" != "$after_id" ]; then
update_needed=true update_needed=true
else else
update_needed=false update_needed=false
fi fi
else
# 👉 Kein Container → kein echtes "Update"
update_needed=false
fi
fi
# ============================= # =============================
# Update erkannt # Update erkannt
@@ -536,17 +500,17 @@ if [ "$NTFY_ENABLED" = true ]; then
msg="" msg=""
if [ ${#stack_tree[@]} -gt 0 ]; then if [ ${#stack_tree[@]} -gt 0 ]; then
msg+=$'\n🔄 Stack Updates\n' msg+=$'#### 🔄 Stack Updates\n'
for stack in $(printf "%s\n" "${!stack_tree[@]}" | sort); do for stack in $(printf "%s\n" "${!stack_tree[@]}" | sort); do
msg+=$'\n'"$stack" msg+=$'\n'"$stack"
msg+="${stack_tree[$stack]}" msg+="${stack_tree[$stack]}"
msg+=$'\n' # Leerzeile nach jedem Stack # msg+=$'\n' # Leerzeile nach jedem Stack
done done
fi fi
if [ ${#notify_excluded_updates[@]} -gt 0 ]; then if [ ${#notify_excluded_updates[@]} -gt 0 ]; then
msg+=$'\n\n⏭ Excluded (Update verfügbar)' msg+=$'\n\n#### ⏭️ Excluded (Update verfügbar)'
for s in "${notify_excluded_updates[@]}"; do for s in "${notify_excluded_updates[@]}"; do
msg+=$'\n - '"$s" msg+=$'\n - '"$s"
done done
@@ -554,11 +518,11 @@ if [ "$NTFY_ENABLED" = true ]; then
if [ ${#stack_tree[@]} -eq 0 ] && \ if [ ${#stack_tree[@]} -eq 0 ] && \
[ ${#notify_excluded_updates[@]} -eq 0 ]; then [ ${#notify_excluded_updates[@]} -eq 0 ]; then
msg+=$'\n\n✔ Alles aktuell' msg+=$'\n\n#### ✔️ Alles aktuell'
fi fi
if [ "$freed_space" != "0" ]; then if [ "$freed_space" != "0" ]; then
msg+=$'\n\n🧹 Cleanup: '"${freed_space} MB freigegeben" msg+=$'\n\n#### 🧹 Cleanup: '"${freed_space} MB freigegeben"
fi fi
if [ "$error_flag" = true ]; then if [ "$error_flag" = true ]; then