.
This commit is contained in:
32
README.md
32
README.md
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
BIN
img/ntfy.png
Normal file
BIN
img/ntfy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@@ -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,58 +314,33 @@ 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
|
||||||
update_needed=true
|
|
||||||
else
|
elif [ "$before_id" != "$after_id" ]; then
|
||||||
update_needed=false
|
update_needed=true
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
# 👉 Kein Container → kein echtes "Update"
|
update_needed=false
|
||||||
update_needed=false
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# =============================
|
# =============================
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user