This commit is contained in:
2026-04-01 11:56:27 +02:00
parent 38b5371b81
commit b4006cdeec
3 changed files with 175 additions and 49 deletions

104
README.md
View File

@@ -16,7 +16,7 @@ Aktualisiert komplette Docker-Compose Stacks strukturiert und kontrolliert
Zeigt an, was passieren würde, ohne Änderungen durchzuführen Zeigt an, was passieren würde, ohne Änderungen durchzuführen
- 📲 ntfy Benachrichtigungen - 📲 ntfy Benachrichtigungen
Push-Notifications über ntfy bei Updates, Fehlern oder Status Push-Notifications über ntfy bei Updates, Fehlern oder Status
- Exclude-Liste - Service-Modi (per Label steuerbar)
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
@@ -65,6 +65,91 @@ Entfernt nicht mehr benötigte Images/Container automatisch
--- ---
## ⚙️ Service-Modi (per Label steuerbar)
Du kannst das Verhalten einzelner Services oder ganzer Stacks über Labels steuern:
```yml
labels:
- composeupdater.mode=update
```
### 📊 Übersicht
| Mode | Pull | Compare | Update | ntfy |
| ----------- | ---- | ------- | ------ | ---- |
| update | ✅ | ✅ | ✅ | ✅ |
| notify-only | ✅ | ✅ | ❌ | ✅ |
| ignore | ❌ | ❌ | ❌ | ❌ |
### 🧠 Erklärung der Modi
🔄 `update` (**Standard**)
- Images werden gepullt
- Lokales Image wird mit dem Container verglichen
- Bei Änderungen wird der Service bzw. Stack aktualisiert
- ntfy-Benachrichtigung wird gesendet
🔔 `notify-only`
- Image wird gepullt (für Vergleich notwendig)
- Es wird geprüft, ob ein Update verfügbar ist
- Kein Container-Update / kein Restart
- ntfy informiert über verfügbare Updates
🚫 `ignore`
- Service wird komplett ignoriert
- Kein Pull
- Kein Vergleich
- Kein Update
- Keine Benachrichtigung
### 🧩 Beispiele
#### Service ausschließen (komplett ignorieren)
```yml
services:
db:
image: postgres:15
labels:
- composeupdater.mode=ignore
```
#### Nur Benachrichtigung, kein automatisches Update
```yml
services:
app:
image: myapp:latest
labels:
- composeupdater.mode=notify-only
```
#### Explizit Standardverhalten setzen
```yml
services:
web:
image: nginx:latest
labels:
- composeupdater.mode=update
```
#### 📦 Stack-Level Label (optional)
Du kannst das Verhalten auch für den gesamten Stack setzen:
```yml
labels:
- composeupdater.mode=ignore
```
---
## 🗑️ Prune / Cleanup ## 🗑️ Prune / Cleanup
Nach Abschluss der Updates kann optional ein Docker Cleanup durchgeführt werden. Nach Abschluss der Updates kann optional ein Docker Cleanup durchgeführt werden.
@@ -161,23 +246,6 @@ REDEPLOY_WAIT_HEALTHY=true
REDEPLOY_WAIT_HEALTHY_TIMEOUT=60 REDEPLOY_WAIT_HEALTHY_TIMEOUT=60
# ----------------------------------------------------------
# EXCLUDES
# ----------------------------------------------------------
# Stacks vom Update ausschließen [ Array ]
EXCLUDE_STACKS=(
"example_stack_1"
"example_stack_2"
)
# Services vom Update ausschließen [ Array ]
EXCLUDE_SERVICES=(
"example_container_1"
"example_container_2"
)
# ---------------------------------------------------------- # ----------------------------------------------------------
# NTFY SETTINGS # NTFY SETTINGS
# ---------------------------------------------------------- # ----------------------------------------------------------

View File

@@ -53,23 +53,6 @@ REDEPLOY_WAIT_HEALTHY=true
REDEPLOY_WAIT_HEALTHY_TIMEOUT=60 REDEPLOY_WAIT_HEALTHY_TIMEOUT=60
# ----------------------------------------------------------
# EXCLUDES
# ----------------------------------------------------------
# Stacks vom Update ausschließen [ Array ]
EXCLUDE_STACKS=(
"example_stack_1"
"example_stack_2"
)
# Services vom Update ausschließen [ Array ]
EXCLUDE_SERVICES=(
"example_container_1"
"example_container_2"
)
# ---------------------------------------------------------- # ----------------------------------------------------------
# NTFY SETTINGS # NTFY SETTINGS
# ---------------------------------------------------------- # ----------------------------------------------------------

View File

@@ -42,6 +42,30 @@ fi
# Helper # Helper
# ============================= # =============================
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() { is_excluded() {
local svc="$1" local svc="$1"
for ex in "${EXCLUDE_SERVICES[@]}"; do for ex in "${EXCLUDE_SERVICES[@]}"; do
@@ -58,6 +82,21 @@ is_stack_excluded() {
return 1 return 1
} }
is_label_excluded() {
local svc="$1"
echo "$compose_json" \
| jq -r --arg svc "$svc" '
.services[$svc].labels // {}
| (if type=="array"
then map(split("=") | {(.[0]): .[1]}) | add
else .
end)
| .["composeupdater.enable"] // empty
' \
| grep -qi "^false$"
}
get_image() { get_image() {
local svc="$1" local svc="$1"
@@ -241,7 +280,6 @@ 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 declare -A pulled_images
@@ -249,11 +287,6 @@ while IFS= read -r -d '' file; do
dir=$(dirname "$file") dir=$(dirname "$file")
stack=$(basename "$dir") stack=$(basename "$dir")
if is_stack_excluded "$stack"; then
log INFO "→ Stack $stack übersprungen (excluded)"
continue
fi
log INFO "" log INFO ""
log INFO "→ Prüfe Stack: $stack" log INFO "→ Prüfe Stack: $stack"
@@ -263,6 +296,14 @@ while IFS= read -r -d '' file; do
compose_json=$(docker compose config --format json) compose_json=$(docker compose config --format json)
stack_mode=$(get_stack_mode)
if [ "$stack_mode" = "ignore" ]; then
log INFO "→ Stack $stack übersprungen (label=ignore)"
cd "$PATH_COMPOSE_DIR"
continue
fi
mapfile -t services < <(docker compose config --services) mapfile -t services < <(docker compose config --services)
total_services=${#services[@]} total_services=${#services[@]}
current_index=0 current_index=0
@@ -280,7 +321,6 @@ while IFS= read -r -d '' file; do
for svc in "${services[@]}"; do for svc in "${services[@]}"; do
update_needed=false update_needed=false
current_index=$((current_index + 1)) current_index=$((current_index + 1))
if [ "$current_index" -eq "$total_services" ]; then if [ "$current_index" -eq "$total_services" ]; then
@@ -296,13 +336,48 @@ while IFS= read -r -d '' file; do
continue continue
fi fi
if is_excluded "$svc"; then mode=$(get_service_mode "$svc")
pull_with_retry "$image" || true
notify_excluded_updates+=("$stack/$svc") case "$mode" in
log INFO " $prefix $svc (excluded)"
ignore)
log INFO " $prefix $svc (ignore)"
continue
;;
notify-only)
log INFO " $prefix $svc (notify-only)"
before_id=$(get_container_image_id "$svc")
# 👉 ohne Container kein Vergleich sinnvoll
if [ -z "$before_id" ]; then
continue continue
fi fi
if [ -z "${pulled_images[$image]:-}" ]; then
pull_with_retry "$image" || true
pulled_images[$image]=1
fi
after_id=$(get_local_image_id "$image")
if [ "$before_id" != "$after_id" ]; then
notify_excluded_updates+=("$stack/$svc")
fi
continue
;;
update)
# normal weiterlaufen lassen
;;
*)
log WARN " $prefix $svc (unbekannter mode: $mode → fallback=update)"
;;
esac
log INFO " $prefix $svc ($image)" log INFO " $prefix $svc ($image)"
before_id=$(get_container_image_id "$svc") before_id=$(get_container_image_id "$svc")
@@ -314,7 +389,7 @@ while IFS= read -r -d '' file; do
fi fi
# ============================= # =============================
# Pull + Vergleich (NEU) # Pull + Vergleich
# ============================= # =============================