.
This commit is contained in:
168
README.md
168
README.md
@@ -5,10 +5,20 @@ Dieses Script überprüft mehrere Docker-Compose-Stacks auf Image-Updates und ak
|
|||||||
## 🚀 Features
|
## 🚀 Features
|
||||||
|
|
||||||
- 🔄 Stack-basiertes Update
|
- 🔄 Stack-basiertes Update
|
||||||
|
Aktualisiert komplette Docker-Compose Stacks strukturiert und kontrolliert
|
||||||
- 🧪 Dry-Run Modus
|
- 🧪 Dry-Run Modus
|
||||||
|
Zeigt an, was passieren würde, ohne Änderungen durchzuführen
|
||||||
- 📲 ntfy Benachrichtigungen
|
- 📲 ntfy Benachrichtigungen
|
||||||
- ⏭️ Exclude-Liste für ganze Stacks oder einzelne Container
|
Push-Notifications über ntfy bei Updates, Fehlern oder Status
|
||||||
|
- ⏭️ Exclude-Liste
|
||||||
|
Einzelne Container oder komplette Stacks gezielt vom Update ausschließen
|
||||||
- 🗑️ Prune Funktion
|
- 🗑️ Prune Funktion
|
||||||
|
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
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -16,6 +26,7 @@ Dieses Script überprüft mehrere Docker-Compose-Stacks auf Image-Updates und ak
|
|||||||
|
|
||||||
- Docker + Docker Compose (v2)
|
- Docker + Docker Compose (v2)
|
||||||
- Bash
|
- Bash
|
||||||
|
- jq
|
||||||
- Optional: ntfy Server
|
- Optional: ntfy Server
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -23,93 +34,85 @@ Dieses Script überprüft mehrere Docker-Compose-Stacks auf Image-Updates und ak
|
|||||||
## ⚙️ Konfiguration (`config.conf`)
|
## ⚙️ Konfiguration (`config.conf`)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# =============================
|
# =========================================================
|
||||||
# =============================
|
# DOCKER COMPOSE UPDATER - CONFIG
|
||||||
# Pfade
|
# =========================================================
|
||||||
# =============================
|
|
||||||
|
|
||||||
# Pfad zu deinen Compose-Files
|
|
||||||
COMPOSE_DIR="/pfad/zu/deinen/stacks"
|
|
||||||
# Logging
|
|
||||||
LOG_FILE="/pfad/zum/log/update.log"
|
|
||||||
LOG_LEVEL="INFO" # DEBUG=sehr detailliert, INFO=Standard, WARN=nur wichtige Hinweise/Updates, ERROR=nur Fehler
|
|
||||||
|
|
||||||
# Dateimuster
|
# ---------------------------------------------------------
|
||||||
COMPOSE_PATTERN="docker-compose.yml"
|
# PATH
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
# =============================
|
PATH_COMPOSE_DIR="/pfad/zu/deinen/stacks" # Basisverzeichnis der Stacks
|
||||||
# =============================
|
PATH_COMPOSE_PATTERN="*compose*.yml" # Compose-Dateiname
|
||||||
# Allgemein Einstellungen
|
|
||||||
# =============================
|
|
||||||
|
|
||||||
# Verhalten bei gestoppten Containern
|
|
||||||
UPDATE_STOPPED=true # Image aktualisieren
|
|
||||||
START_STOPPED=false # danach NICHT starten
|
|
||||||
|
|
||||||
# Dry Run (true/false)
|
# ---------------------------------------------------------
|
||||||
DRY_RUN=false
|
# LOG
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
# =============================
|
LOG_FILE="/pfad/zum/log/update.log" # Log-Datei
|
||||||
# =============================
|
LOG_LEVEL="INFO" # DEBUG [ INFO | WARN | ERROR ]
|
||||||
# Exclude
|
|
||||||
# =============================
|
|
||||||
|
|
||||||
# Exclude Container
|
|
||||||
EXCLUDE_SERVICES=(
|
|
||||||
"example_container_1"
|
|
||||||
"example_container_2"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Exclude Stack
|
# ---------------------------------------------------------
|
||||||
EXCLUDE_STACKS=(
|
# UPDATE
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
UPDATE_DRY_RUN=false # Nur Simulation, keine Änderungen [ true | false ]
|
||||||
|
UPDATE_INCLUDE_STOPPED=true # Gestoppte Container updaten [ true | false ]
|
||||||
|
UPDATE_START_STOPPED=false # Danach wieder starten [ true | false ]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# REDEPLOY
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
REDEPLOY_WAIT_HEALTHY=true # Warten bis Container healthy [ true | false ]
|
||||||
|
REDEPLOY_TIMEOUT=60 # Timeout in Sekunden [ Sekunden ]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# EXCLUDES
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
EXCLUDE_STACKS=( # Liste mit ganzen Stacks die nicht geupdated werden
|
||||||
"example_stack_1"
|
"example_stack_1"
|
||||||
"example_stack_2"
|
"example_stack_2"
|
||||||
)
|
)
|
||||||
|
|
||||||
# =============================
|
EXCLUDE_SERVICES=( # Liste mit einzelnen Containern die nicht geupdated werden
|
||||||
# =============================
|
"example_container_1"
|
||||||
# NTFY
|
"example_container_2"
|
||||||
# =============================
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# NTFY SETTINGS
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
NTFY_ENABLED=true
|
NTFY_ENABLED=true
|
||||||
NTFY_TITLE="Docker Update ($(hostname))"
|
|
||||||
NTFY_TOKEN="DEIN_TOKEN"
|
|
||||||
NTFY_URL="https://ntfy.example.com/topic"
|
NTFY_URL="https://ntfy.example.com/topic"
|
||||||
NTFY_IMAGE_URL="http://dein-server/host-icon.png"
|
NTFY_TOKEN="DEIN_TOKEN"
|
||||||
|
NTFY_TITLE="Docker Update ($(hostname))"
|
||||||
NTFY_TAGS="docker,update"
|
NTFY_TAGS="docker,update"
|
||||||
NTFY_ONLY_ON_CHANGES=false
|
NTFY_IMAGE_URL="http://dein-server/host-icon.png"
|
||||||
# Versions Nr. anzeigen (true/false)
|
NTFY_ONLY_ON_CHANGES=false # Nur senden wenn Updates vorhanden
|
||||||
SHOW_VERSIONS=true
|
NTFY_SHOW_VERSIONS=true # Versionsnummern anzeigen
|
||||||
|
|
||||||
# =============================
|
|
||||||
# =============================
|
|
||||||
# Docker Cleanup
|
|
||||||
# =============================
|
|
||||||
|
|
||||||
ENABLE_CLEANUP=true
|
# ---------------------------------------------------------
|
||||||
CLEANUP_ONLY_ON_UPDATE=true
|
# DOCKER CLEANUP
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
# Images:
|
CLEANUP_ENABLED=true # Aktivieren [ true | false ]
|
||||||
# 🟢 dangling → docker image prune (nur <none> Images)
|
CLEANUP_ONLY_ON_UPDATE=true # Nur nach Updates ausführen [ true | false ]
|
||||||
# 🟡 unused → docker image prune -a (alle ungenutzten Images)
|
|
||||||
CLEANUP_IMAGES=true
|
|
||||||
CLEANUP_IMAGES_MODE="unused" # dangling | unused
|
|
||||||
|
|
||||||
# Container:
|
CLEANUP_IMAGES_ENABLED=true # Images löschen [ true | false ]
|
||||||
# entfernt gestoppte Container
|
CLEANUP_IMAGES_MODE="unused" # Methode [ dangling | unused ]
|
||||||
# 🟢 docker container prune
|
|
||||||
CLEANUP_CONTAINERS=true
|
|
||||||
|
|
||||||
# Volume:
|
CLEANUP_CONTAINERS_ENABLED=true # Container löschen [ true | false ]
|
||||||
# entfernt ungenutzte Volumes
|
CLEANUP_VOLUMES_ENABLED=false # Volumes löschen [ true | false ]
|
||||||
# ⚠️ kann Daten löschen
|
CLEANUP_NETWORKS_ENABLED=true # Networks löschen [ true | false ]
|
||||||
CLEANUP_VOLUMES=false
|
|
||||||
|
|
||||||
# Networks:
|
|
||||||
# entfernt ungenutzte Netzwerke
|
|
||||||
# 🟢 meist unkritisch
|
|
||||||
CLEANUP_NETWORKS=true
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -125,13 +128,28 @@ chmod +x script.sh
|
|||||||
|
|
||||||
## 🧠 Funktionsweise
|
## 🧠 Funktionsweise
|
||||||
|
|
||||||
1. Alle `docker-compose.yml` Dateien werden gefunden
|
1. Alle `*compose*.yml` Dateien werden rekursiv gefunden
|
||||||
2. Alphabetisch sortiert
|
2. Verarbeitung erfolgt alphabetisch (deterministische Reihenfolge)
|
||||||
3. Jeder Stack wird geprüft:
|
3. Für jeden Stack:
|
||||||
- Image wird gepullt
|
- Compose-Konfiguration wird ausgewertet (docker compose config)
|
||||||
- Vergleich: Container Image-ID vs. aktuelles Image
|
- Verwendete Images werden extrahiert
|
||||||
4. Wenn ein Service ein Update hat:
|
- Für jedes Image:
|
||||||
- kompletter Stack wird neu deployed
|
- Remote-Digest wird aus der Registry abgefragt
|
||||||
|
-Lokaler Digest wird ermittelt
|
||||||
|
-Vergleich lokal vs. remote
|
||||||
|
4. Entscheidungslogik:
|
||||||
|
- ❌ Kein Unterschied → kein Pull, kein Restart
|
||||||
|
- ✅ Digest unterschiedlich → Image wird gepullt
|
||||||
|
5. Wenn mindestens ein Service ein Update hat:
|
||||||
|
- kompletter Stack wird neu deployed (docker compose up -d)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚡ Verhalten im Detail
|
||||||
|
- Kein unnötiger Netzwerk-Traffic (kein blindes docker pull)
|
||||||
|
- Updates erfolgen nur bei tatsächlichen Änderungen
|
||||||
|
- Mehrere Services im Stack → einheitlicher Redeploy, kein Teilzustand
|
||||||
|
- Optional: Dry-Run zeigt exakt diese Entscheidungen ohne Ausführung
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
126
config.conf
126
config.conf
@@ -1,87 +1,79 @@
|
|||||||
# =============================
|
# =========================================================
|
||||||
# =============================
|
# DOCKER COMPOSE UPDATER - CONFIG
|
||||||
# Pfade
|
# =========================================================
|
||||||
# =============================
|
|
||||||
|
|
||||||
# Pfad zu deinen Compose-Files
|
|
||||||
COMPOSE_DIR="/pfad/zu/deinen/stacks"
|
|
||||||
# Logging
|
|
||||||
LOG_FILE="/pfad/zum/log/update.log"
|
|
||||||
LOG_LEVEL="INFO" # DEBUG=sehr detailliert, INFO=Standard, WARN=nur wichtige Hinweise/Updates, ERROR=nur Fehler
|
|
||||||
|
|
||||||
# Dateimuster
|
# ---------------------------------------------------------
|
||||||
COMPOSE_PATTERN="docker-compose.yml"
|
# PATH
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
# =============================
|
PATH_COMPOSE_DIR="/pfad/zu/deinen/stacks" # Basisverzeichnis der Stacks
|
||||||
# =============================
|
PATH_COMPOSE_PATTERN="*compose*.yml" # Compose-Dateiname
|
||||||
# Allgemein Einstellungen
|
|
||||||
# =============================
|
|
||||||
|
|
||||||
# Verhalten bei gestoppten Containern
|
|
||||||
UPDATE_STOPPED=true # Image aktualisieren
|
|
||||||
START_STOPPED=false # danach NICHT starten
|
|
||||||
|
|
||||||
# Dry Run (true/false)
|
# ---------------------------------------------------------
|
||||||
DRY_RUN=false
|
# LOG
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
# =============================
|
LOG_FILE="/pfad/zum/log/update.log" # Log-Datei
|
||||||
# =============================
|
LOG_LEVEL="INFO" # DEBUG [ INFO | WARN | ERROR ]
|
||||||
# Exclude
|
|
||||||
# =============================
|
|
||||||
|
|
||||||
# Exclude Container
|
|
||||||
EXCLUDE_SERVICES=(
|
|
||||||
"example_container_1"
|
|
||||||
"example_container_2"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Exclude Stack
|
# ---------------------------------------------------------
|
||||||
EXCLUDE_STACKS=(
|
# UPDATE
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
UPDATE_DRY_RUN=false # Nur Simulation, keine Änderungen [ true | false ]
|
||||||
|
UPDATE_INCLUDE_STOPPED=true # Gestoppte Container updaten [ true | false ]
|
||||||
|
UPDATE_START_STOPPED=false # Danach wieder starten [ true | false ]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# REDEPLOY
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
REDEPLOY_WAIT_HEALTHY=true # Warten bis Container healthy [ true | false ]
|
||||||
|
REDEPLOY_TIMEOUT=60 # Timeout in Sekunden [ Sekunden ]
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# EXCLUDES
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
|
EXCLUDE_STACKS=( # Liste mit ganzen Stacks die nicht geupdated werden
|
||||||
"example_stack_1"
|
"example_stack_1"
|
||||||
"example_stack_2"
|
"example_stack_2"
|
||||||
)
|
)
|
||||||
|
|
||||||
# =============================
|
EXCLUDE_SERVICES=( # Liste mit einzelnen Containern die nicht geupdated werden
|
||||||
# =============================
|
"example_container_1"
|
||||||
# NTFY
|
"example_container_2"
|
||||||
# =============================
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
# NTFY SETTINGS
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
NTFY_ENABLED=true
|
NTFY_ENABLED=true
|
||||||
NTFY_TITLE="Docker Update ($(hostname))"
|
|
||||||
NTFY_TOKEN="DEIN_TOKEN"
|
|
||||||
NTFY_URL="https://ntfy.example.com/topic"
|
NTFY_URL="https://ntfy.example.com/topic"
|
||||||
NTFY_IMAGE_URL="http://dein-server/host-icon.png"
|
NTFY_TOKEN="DEIN_TOKEN"
|
||||||
|
NTFY_TITLE="Docker Update ($(hostname))"
|
||||||
NTFY_TAGS="docker,update"
|
NTFY_TAGS="docker,update"
|
||||||
NTFY_ONLY_ON_CHANGES=false
|
NTFY_IMAGE_URL="http://dein-server/host-icon.png"
|
||||||
# Versions Nr. anzeigen (true/false)
|
NTFY_ONLY_ON_CHANGES=false # Nur senden wenn Updates vorhanden
|
||||||
SHOW_VERSIONS=true
|
NTFY_SHOW_VERSIONS=true # Versionsnummern anzeigen
|
||||||
|
|
||||||
# =============================
|
|
||||||
# =============================
|
|
||||||
# Docker Cleanup
|
|
||||||
# =============================
|
|
||||||
|
|
||||||
ENABLE_CLEANUP=true
|
# ---------------------------------------------------------
|
||||||
CLEANUP_ONLY_ON_UPDATE=true
|
# DOCKER CLEANUP
|
||||||
|
# ---------------------------------------------------------
|
||||||
|
|
||||||
# Images:
|
CLEANUP_ENABLED=true # Aktivieren [ true | false ]
|
||||||
# 🟢 dangling → docker image prune (nur <none> Images)
|
CLEANUP_ONLY_ON_UPDATE=true # Nur nach Updates ausführen [ true | false ]
|
||||||
# 🟢 unused → docker image prune -a (alle ungenutzten Images)
|
|
||||||
CLEANUP_IMAGES=true
|
|
||||||
CLEANUP_IMAGES_MODE="unused" # dangling | unused
|
|
||||||
|
|
||||||
# Container:
|
CLEANUP_IMAGES_ENABLED=true # Images löschen [ true | false ]
|
||||||
# entfernt gestoppte Container
|
CLEANUP_IMAGES_MODE="unused" # Methode [ dangling | unused ]
|
||||||
# 🟢 docker container prune
|
|
||||||
CLEANUP_CONTAINERS=true
|
|
||||||
|
|
||||||
# Volume:
|
CLEANUP_CONTAINERS_ENABLED=true # Container löschen [ true | false ]
|
||||||
# entfernt ungenutzte Volumes
|
CLEANUP_VOLUMES_ENABLED=false # Volumes löschen [ true | false ]
|
||||||
# ⚠️ kann Daten löschen
|
CLEANUP_NETWORKS_ENABLED=true # Networks löschen [ true | false ]
|
||||||
CLEANUP_VOLUMES=false
|
|
||||||
|
|
||||||
# Networks:
|
|
||||||
# entfernt ungenutzte Netzwerke
|
|
||||||
# 🟢 meist unkritisch
|
|
||||||
CLEANUP_NETWORKS=true
|
|
||||||
|
|
||||||
# =============================
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
BASE_DIR="$(cd "$(dirname "$0")" && pwd)"
|
BASE_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
@@ -24,19 +23,14 @@ should_log() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
local level="$1"
|
local level="$1"; shift
|
||||||
shift
|
|
||||||
local msg="$*"
|
local msg="$*"
|
||||||
|
|
||||||
if should_log "$level"; then
|
if should_log "$level"; then
|
||||||
echo "$(date '+%Y-%m-%d %H:%M:%S') | $level | $msg" | tee -a "$LOG_FILE"
|
echo "$(date '+%Y-%m-%d %H:%M:%S') | $level | $msg" | tee -a "$LOG_FILE"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# =============================
|
|
||||||
# Log begrenzen
|
# Log begrenzen
|
||||||
# =============================
|
|
||||||
|
|
||||||
if [ -f "$LOG_FILE" ]; then
|
if [ -f "$LOG_FILE" ]; then
|
||||||
line_count=$(wc -l < "$LOG_FILE")
|
line_count=$(wc -l < "$LOG_FILE")
|
||||||
if [ "$line_count" -gt 1000 ]; then
|
if [ "$line_count" -gt 1000 ]; then
|
||||||
@@ -66,16 +60,16 @@ is_stack_excluded() {
|
|||||||
|
|
||||||
get_image() {
|
get_image() {
|
||||||
local svc="$1"
|
local svc="$1"
|
||||||
docker compose config | awk "/^ $svc:/,/image:/" | grep image | head -n1 | awk '{print $2}'
|
|
||||||
|
echo "$compose_json" \
|
||||||
|
| jq -r --arg svc "$svc" '.services[$svc].image // empty'
|
||||||
}
|
}
|
||||||
|
|
||||||
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 -q "$svc" 2>/dev/null || true)
|
||||||
[ -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 ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,33 +78,16 @@ get_local_image_id() {
|
|||||||
docker image inspect -f '{{.Id}}' "$image" 2>/dev/null || echo ""
|
docker image inspect -f '{{.Id}}' "$image" 2>/dev/null || echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
get_container_image_ref() {
|
|
||||||
local svc="$1"
|
|
||||||
local cid
|
|
||||||
|
|
||||||
cid=$(docker compose ps -q "$svc" 2>/dev/null || true)
|
|
||||||
[ -z "$cid" ] && echo "none" && return
|
|
||||||
|
|
||||||
docker inspect -f '{{.Config.Image}}' "$cid" 2>/dev/null || echo "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
get_local_image_digest() {
|
|
||||||
local image="$1"
|
|
||||||
docker inspect --format='{{index .RepoDigests 0}}' "$image" 2>/dev/null || echo "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
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 -q "$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
|
||||||
}
|
}
|
||||||
|
|
||||||
run_cmd() {
|
run_cmd() {
|
||||||
if [ "$DRY_RUN" = true ]; then
|
if [ "$UPDATE_DRY_RUN" = true ]; then
|
||||||
log DEBUG "[DRY RUN] $*"
|
log DEBUG "[DRY RUN] $*"
|
||||||
else
|
else
|
||||||
eval "$@"
|
eval "$@"
|
||||||
@@ -122,33 +99,21 @@ send_ntfy() {
|
|||||||
local prio="$2"
|
local prio="$2"
|
||||||
local image_url="${NTFY_IMAGE_URL:-}"
|
local image_url="${NTFY_IMAGE_URL:-}"
|
||||||
|
|
||||||
if [ -n "$image_url" ]; then
|
curl -s \
|
||||||
curl -s \
|
-H "Authorization: Bearer $NTFY_TOKEN" \
|
||||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
-H "Title: $NTFY_TITLE" \
|
||||||
-H "Title: $NTFY_TITLE" \
|
-H "Priority: $prio" \
|
||||||
-H "Priority: $prio" \
|
-H "Tags: $NTFY_TAGS" \
|
||||||
-H "Tags: $NTFY_TAGS" \
|
${image_url:+-H "Icon: $image_url"} \
|
||||||
-H "Icon: $image_url" \
|
-d "$msg" \
|
||||||
-d "$msg" \
|
"$NTFY_URL" > /dev/null || true
|
||||||
"$NTFY_URL" > /dev/null || true
|
|
||||||
else
|
|
||||||
curl -s \
|
|
||||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
|
||||||
-H "Title: $NTFY_TITLE" \
|
|
||||||
-H "Priority: $prio" \
|
|
||||||
-H "Tags: $NTFY_TAGS" \
|
|
||||||
-d "$msg" \
|
|
||||||
"$NTFY_URL" > /dev/null || true
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get_docker_disk_usage() {
|
get_docker_disk_usage() {
|
||||||
local total=0
|
local total=0
|
||||||
|
|
||||||
while read -r size; do
|
while read -r size; do
|
||||||
num="${size//[!0-9.]/}"
|
num="${size//[!0-9.]/}"
|
||||||
num="${num:-0}"
|
num="${num:-0}"
|
||||||
|
|
||||||
if [[ "$size" == *GB ]]; then
|
if [[ "$size" == *GB ]]; then
|
||||||
total=$(bc <<< "$total + ($num * 1024)")
|
total=$(bc <<< "$total + ($num * 1024)")
|
||||||
elif [[ "$size" == *MB ]]; then
|
elif [[ "$size" == *MB ]]; then
|
||||||
@@ -161,17 +126,133 @@ get_docker_disk_usage() {
|
|||||||
LC_NUMERIC=C printf "%.0f\n" "$total"
|
LC_NUMERIC=C printf "%.0f\n" "$total"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wait_for_healthy() {
|
||||||
|
local timeout="$1"
|
||||||
|
shift
|
||||||
|
local services=("$@")
|
||||||
|
|
||||||
|
local start
|
||||||
|
start=$(date +%s)
|
||||||
|
|
||||||
|
# 👉 Containerliste bestimmen
|
||||||
|
if [ ${#services[@]} -gt 0 ]; then
|
||||||
|
mapfile -t cids < <(docker compose ps -q "${services[@]}")
|
||||||
|
else
|
||||||
|
mapfile -t cids < <(docker compose ps -q)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 👉 keine Container → nichts zu tun
|
||||||
|
if [ ${#cids[@]} -eq 0 ]; then
|
||||||
|
log INFO " ℹ️ Keine Container gefunden → überspringe Healthcheck"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 👉 prüfen ob überhaupt Healthchecks existieren
|
||||||
|
local has_healthcheck=false
|
||||||
|
|
||||||
|
for cid in "${cids[@]}"; do
|
||||||
|
health=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{end}}' "$cid" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$health" ]; then
|
||||||
|
has_healthcheck=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# 👉 wenn keiner vorhanden → direkt raus
|
||||||
|
if [ "$has_healthcheck" = false ]; then
|
||||||
|
log INFO " ℹ️ Keine Healthchecks definiert → überspringe warten"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log INFO " ⏳ Warte auf healthy Container (max ${timeout}s)"
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
local all_ok=true
|
||||||
|
|
||||||
|
for cid in "${cids[@]}"; do
|
||||||
|
status=$(docker inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo "none")
|
||||||
|
|
||||||
|
if [ "$status" = "starting" ] || [ "$status" = "unhealthy" ]; then
|
||||||
|
all_ok=false
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$all_ok" = true ]; then
|
||||||
|
log INFO " ✔️ Alle Container healthy"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local now
|
||||||
|
now=$(date +%s)
|
||||||
|
|
||||||
|
if [ $((now - start)) -ge "$timeout" ]; then
|
||||||
|
log WARN " ⚠️ Healthcheck Timeout erreicht"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
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() {
|
||||||
|
local image="$1"
|
||||||
|
|
||||||
|
local retries=3
|
||||||
|
local delay=3
|
||||||
|
|
||||||
|
for ((i=1; i<=retries; i++)); do
|
||||||
|
|
||||||
|
if docker pull "$image" >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$i" -lt "$retries" ]; then
|
||||||
|
log WARN " ⚠️ Pull fehlgeschlagen – Versuch $i/$retries, retry in ${delay}s"
|
||||||
|
sleep "$delay"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
rate_limit() {
|
||||||
|
sleep 0.2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# =============================
|
# =============================
|
||||||
# Start
|
# Start
|
||||||
# =============================
|
# =============================
|
||||||
|
|
||||||
log INFO "==== Docker Compose Update gestartet ===="
|
log INFO "==== Docker Compose Update gestartet ===="
|
||||||
|
|
||||||
|
script_start=$(date +%s)
|
||||||
|
|
||||||
notify_stacks_updated=()
|
notify_stacks_updated=()
|
||||||
notify_excluded_updates=()
|
notify_excluded_updates=()
|
||||||
error_flag=false
|
error_flag=false
|
||||||
|
|
||||||
cd "$COMPOSE_DIR"
|
declare -A stack_tree
|
||||||
|
stack_tree=()
|
||||||
|
|
||||||
|
cd "$PATH_COMPOSE_DIR"
|
||||||
|
|
||||||
while IFS= read -r -d '' file; do
|
while IFS= read -r -d '' file; do
|
||||||
|
|
||||||
@@ -186,29 +267,30 @@ while IFS= read -r -d '' file; do
|
|||||||
log INFO ""
|
log INFO ""
|
||||||
log INFO "→ Prüfe Stack: $stack"
|
log INFO "→ Prüfe Stack: $stack"
|
||||||
|
|
||||||
|
stack_start=$(date +%s)
|
||||||
|
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
|
|
||||||
mapfile -t services < <(docker compose config --services)
|
compose_json=$(docker compose config --format json)
|
||||||
|
|
||||||
|
mapfile -t services < <(docker compose config --services)
|
||||||
total_services=${#services[@]}
|
total_services=${#services[@]}
|
||||||
current_index=0
|
current_index=0
|
||||||
|
|
||||||
# Running State merken
|
|
||||||
declare -A was_running
|
declare -A was_running
|
||||||
for svc in "${services[@]}"; do
|
for svc in "${services[@]}"; do
|
||||||
if is_running "$svc"; then
|
is_running "$svc" && was_running[$svc]=1 || was_running[$svc]=0
|
||||||
was_running["$svc"]=1
|
|
||||||
else
|
|
||||||
was_running["$svc"]=0
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
stack_updated=false
|
stack_updated=false
|
||||||
changed_services=()
|
changed_services=()
|
||||||
version_report=()
|
stack_block=""
|
||||||
|
update_lines=()
|
||||||
|
|
||||||
for svc in "${services[@]}"; do
|
for svc in "${services[@]}"; do
|
||||||
|
|
||||||
|
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
|
||||||
@@ -225,7 +307,7 @@ while IFS= read -r -d '' file; do
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if is_excluded "$svc"; then
|
if is_excluded "$svc"; then
|
||||||
docker pull "$image" >/dev/null 2>&1 || true
|
pull_with_retry "$image" || true
|
||||||
notify_excluded_updates+=("$stack/$svc")
|
notify_excluded_updates+=("$stack/$svc")
|
||||||
log INFO " $prefix $svc (excluded)"
|
log INFO " $prefix $svc (excluded)"
|
||||||
continue
|
continue
|
||||||
@@ -234,48 +316,87 @@ while IFS= read -r -d '' file; do
|
|||||||
log INFO " $prefix $svc ($image)"
|
log INFO " $prefix $svc ($image)"
|
||||||
|
|
||||||
before_id=$(get_container_image_id "$svc")
|
before_id=$(get_container_image_id "$svc")
|
||||||
before_digest=$(docker inspect -f '{{.Image}}' "$(docker compose ps -q "$svc")" 2>/dev/null || echo "none")
|
|
||||||
|
|
||||||
if ! docker pull "$image" >/dev/null 2>&1; then
|
# 👉 Skip wenn kein Container und nicht erlaubt
|
||||||
log ERROR " $prefix ❌ Pull fehlgeschlagen"
|
if [ -z "$before_id" ] && [ "$UPDATE_INCLUDE_STOPPED" = false ]; then
|
||||||
error_flag=true
|
log INFO " ⏭️ übersprungen (kein Container vorhanden)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
after_id=$(get_local_image_id "$image")
|
# =============================
|
||||||
after_digest=$(get_local_image_digest "$image")
|
# Digest Check
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
local_digest=$(get_local_digest "$image")
|
||||||
|
|
||||||
|
if [ -z "$local_digest" ]; then
|
||||||
|
update_needed=true
|
||||||
|
|
||||||
|
elif [ -n "$before_id" ]; then
|
||||||
|
# 👉 nur bei laufenden Containern → teurer Check
|
||||||
|
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
|
||||||
|
|
||||||
|
if ! pull_with_retry "$image"; then
|
||||||
|
log ERROR " ❌ Pull fehlgeschlagen"
|
||||||
|
error_flag=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
after_id=$(get_local_image_id "$image")
|
||||||
|
|
||||||
|
# 👉 Nur vergleichen wenn Container existiert
|
||||||
|
if [ -n "$before_id" ]; then
|
||||||
|
if [ "$before_id" != "$after_id" ]; then
|
||||||
|
update_needed=true
|
||||||
|
else
|
||||||
|
update_needed=false
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# 👉 Kein Container → kein echtes "Update"
|
||||||
|
update_needed=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Update erkannt
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
if [ "$update_needed" = true ]; then
|
||||||
|
|
||||||
if [ -n "$before_id" ] && [ "$before_id" != "$after_id" ]; then
|
|
||||||
stack_updated=true
|
stack_updated=true
|
||||||
changed_services+=("$svc")
|
changed_services+=("$svc")
|
||||||
|
|
||||||
if [ "$SHOW_VERSIONS" = true ]; then
|
log INFO " ⬆️ UPDATE"
|
||||||
log INFO " ⬆️ UPDATE"
|
log INFO " alt: ${image}@${before_id}"
|
||||||
|
log INFO " neu: ${image}@${after_id}"
|
||||||
|
|
||||||
# Log (vollständig, für Debugging)
|
short_before="${before_id#sha256:}"
|
||||||
if [ -n "$before_id" ]; then
|
short_before="${short_before:0:6}"
|
||||||
log INFO " alt: ${image}@${before_id}"
|
short_after="${after_id#sha256:}"
|
||||||
else
|
short_after="${short_after:0:6}"
|
||||||
log INFO " alt: none"
|
|
||||||
fi
|
|
||||||
log INFO " neu: ${image}@${after_id}"
|
|
||||||
|
|
||||||
# Kurzversion für ntfy
|
update_lines+=("$svc|$short_before|$short_after")
|
||||||
if [ -n "$before_id" ]; then
|
|
||||||
short_before="${before_id#sha256:}"
|
|
||||||
short_before="${short_before:0:6}"
|
|
||||||
else
|
|
||||||
short_before="new"
|
|
||||||
fi
|
|
||||||
|
|
||||||
short_after="${after_id#sha256:}"
|
|
||||||
short_after="${short_after:0:6}"
|
|
||||||
|
|
||||||
version_report+=("$svc: $short_before → $short_after")
|
|
||||||
|
|
||||||
else
|
|
||||||
log INFO " ⬆️ UPDATE"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -287,65 +408,89 @@ while IFS= read -r -d '' file; do
|
|||||||
|
|
||||||
if [ "$total_services" -eq 1 ]; then
|
if [ "$total_services" -eq 1 ]; then
|
||||||
svc="${services[0]}"
|
svc="${services[0]}"
|
||||||
|
|
||||||
log INFO " 🔄 Einzelcontainer-Update: $svc"
|
log INFO " 🔄 Einzelcontainer-Update: $svc"
|
||||||
|
|
||||||
if [ "${was_running[$svc]}" = 1 ]; then
|
if [ "${was_running[$svc]}" = 1 ]; then
|
||||||
if ! run_cmd docker compose up -d "$svc" --remove-orphans --no-color >/dev/null 2>&1; then
|
run_cmd docker compose up -d "$svc" --remove-orphans
|
||||||
log ERROR " ❌ Update fehlgeschlagen für $svc"
|
|
||||||
error_flag=true
|
|
||||||
else
|
|
||||||
log INFO " ✔️ Container $svc aktualisiert"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
if ! run_cmd docker compose create "$svc" >/dev/null 2>&1; then
|
if [ "$UPDATE_START_STOPPED" = true ]; then
|
||||||
log ERROR " ❌ Create fehlgeschlagen für $svc"
|
run_cmd docker compose up -d "$svc" --remove-orphans
|
||||||
error_flag=true
|
|
||||||
else
|
else
|
||||||
log INFO " ✔️ Container $svc aktualisiert (gestoppt)"
|
run_cmd docker compose create "$svc"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
notify_stacks_updated+=("$stack ($svc)")
|
log INFO " ✔️ Container $svc aktualisiert"
|
||||||
|
|
||||||
|
if [ "$REDEPLOY_WAIT_HEALTHY" = true ]; then
|
||||||
|
wait_for_healthy "$REDEPLOY_TIMEOUT" "$svc"
|
||||||
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
log INFO " 🔄 Stack wird neu deployt (Trigger: ${changed_services[*]})"
|
log INFO " 🔄 Stack wird neu deployt (Trigger: ${changed_services[*]})"
|
||||||
|
|
||||||
log INFO " ⏳ Deploy läuft..."
|
log INFO " ⏳ Deploy läuft..."
|
||||||
if ! run_cmd docker compose up -d --remove-orphans --no-color >/dev/null 2>&1; then
|
|
||||||
|
if ! run_cmd docker compose up -d --remove-orphans >/dev/null 2>&1; then
|
||||||
log ERROR " ❌ Stack Update fehlgeschlagen"
|
log ERROR " ❌ Stack Update fehlgeschlagen"
|
||||||
error_flag=true
|
error_flag=true
|
||||||
else
|
else
|
||||||
log INFO " ✔️ Stack erfolgreich aktualisiert"
|
log INFO " ✔️ Stack erfolgreich aktualisiert"
|
||||||
|
|
||||||
# vorher gestoppte wieder stoppen
|
if [ "$REDEPLOY_WAIT_HEALTHY" = true ]; then
|
||||||
|
wait_for_healthy "$REDEPLOY_TIMEOUT" "${changed_services[@]}"
|
||||||
|
fi
|
||||||
|
|
||||||
for svc in "${services[@]}"; do
|
for svc in "${services[@]}"; do
|
||||||
if [ "${was_running[$svc]}" = 0 ]; then
|
if [ "${was_running[$svc]}" = 0 ]; then
|
||||||
log INFO " ⏹️ Stoppe $svc (war vorher gestoppt)"
|
log INFO " ⏹️ Stoppe $svc (war vorher gestoppt)"
|
||||||
run_cmd docker compose stop "$svc" >/dev/null 2>&1 || true
|
run_cmd docker compose stop "$svc" >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
notify_stacks_updated+=("$stack (${changed_services[*]})")
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
# =============================
|
||||||
log DEBUG " ✔️ Keine Updates"
|
# Baum für ntfy bauen
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
if [ ${#update_lines[@]} -gt 0 ]; then
|
||||||
|
stack_block=""
|
||||||
|
|
||||||
|
for i in "${!update_lines[@]}"; do
|
||||||
|
IFS="|" read -r svc before after <<< "${update_lines[$i]}"
|
||||||
|
|
||||||
|
if [ "$i" -eq $((${#update_lines[@]} - 1)) ]; then
|
||||||
|
prefix="└─"
|
||||||
|
else
|
||||||
|
prefix="├─"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NTFY_SHOW_VERSIONS" = true ]; then
|
||||||
|
stack_block+=$'\n'"$prefix $svc ($before → $after)"
|
||||||
|
else
|
||||||
|
stack_block+=$'\n'"$prefix $svc"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
stack_tree["$stack"]="$stack_block"
|
||||||
|
notify_stacks_updated+=("$stack")
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cd "$COMPOSE_DIR"
|
cd "$PATH_COMPOSE_DIR"
|
||||||
|
|
||||||
done < <(find . -name "$COMPOSE_PATTERN" -print0 | sort -z)
|
stack_end=$(date +%s)
|
||||||
|
log INFO " ⏱ Dauer: $((stack_end - stack_start))s"
|
||||||
|
|
||||||
|
done < <(find . -name "$PATH_COMPOSE_PATTERN" -print0 | sort -z)
|
||||||
|
|
||||||
# =============================
|
# =============================
|
||||||
# Cleanup (mit Statistik)
|
# Cleanup
|
||||||
# =============================
|
# =============================
|
||||||
|
|
||||||
freed_space="0"
|
freed_space="0"
|
||||||
|
|
||||||
if [ "$ENABLE_CLEANUP" = true ]; then
|
if [ "$CLEANUP_ENABLED" = true ]; then
|
||||||
|
|
||||||
if [ "$CLEANUP_ONLY_ON_UPDATE" = true ] && \
|
if [ "$CLEANUP_ONLY_ON_UPDATE" = true ] && \
|
||||||
[ ${#notify_stacks_updated[@]} -eq 0 ]; then
|
[ ${#notify_stacks_updated[@]} -eq 0 ]; then
|
||||||
@@ -353,42 +498,31 @@ if [ "$ENABLE_CLEANUP" = true ]; then
|
|||||||
else
|
else
|
||||||
|
|
||||||
before_size=$(get_docker_disk_usage)
|
before_size=$(get_docker_disk_usage)
|
||||||
|
|
||||||
log INFO "🧹 Docker Cleanup läuft..."
|
log INFO "🧹 Docker Cleanup läuft..."
|
||||||
|
|
||||||
if [ "$CLEANUP_IMAGES" = true ]; then
|
if [ "$CLEANUP_IMAGES_ENABLED" = true ]; then
|
||||||
case "$CLEANUP_IMAGES_MODE" in
|
case "$CLEANUP_IMAGES_MODE" in
|
||||||
unused)
|
unused) run_cmd docker image prune -a -f >/dev/null 2>&1 ;;
|
||||||
log INFO " → Entferne ungenutzte Images"
|
dangling) run_cmd docker image prune -f >/dev/null 2>&1 ;;
|
||||||
run_cmd docker image prune -a -f >/dev/null 2>&1
|
|
||||||
;;
|
|
||||||
dangling)
|
|
||||||
log INFO " → Entferne dangling Images"
|
|
||||||
run_cmd docker image prune -f >/dev/null 2>&1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$CLEANUP_CONTAINERS" = true ]; then
|
if [ "$CLEANUP_CONTAINERS_ENABLED" = true ]; then
|
||||||
log INFO " → Entferne gestoppte Container"
|
|
||||||
run_cmd docker container prune -f >/dev/null 2>&1
|
run_cmd docker container prune -f >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$CLEANUP_VOLUMES" = true ]; then
|
if [ "$CLEANUP_VOLUMES_ENABLED" = true ]; then
|
||||||
log WARN " → Entferne ungenutzte Volumes"
|
|
||||||
run_cmd docker volume prune -f >/dev/null 2>&1
|
run_cmd docker volume prune -f >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$CLEANUP_NETWORKS" = true ]; then
|
if [ "$CLEANUP_NETWORKS_ENABLED" = true ]; then
|
||||||
log INFO " → Entferne ungenutzte Netzwerke"
|
|
||||||
run_cmd docker network prune -f >/dev/null 2>&1
|
run_cmd docker network prune -f >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
after_size=$(get_docker_disk_usage)
|
after_size=$(get_docker_disk_usage)
|
||||||
|
freed_space=$((after_size < before_size ? before_size - after_size : 0))
|
||||||
|
|
||||||
freed_space=$(awk "BEGIN {print $before_size - $after_size}")
|
log INFO "✔️ Cleanup abgeschlossen (${freed_space} MB freigegeben)"
|
||||||
|
|
||||||
log INFO "✔️ Cleanup abgeschlossen (freigegeben: ${freed_space} MB)"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -398,21 +532,16 @@ fi
|
|||||||
|
|
||||||
if [ "$NTFY_ENABLED" = true ]; then
|
if [ "$NTFY_ENABLED" = true ]; then
|
||||||
|
|
||||||
msg="Docker Compose Update Report"
|
msg=""
|
||||||
|
|
||||||
if [ ${#notify_stacks_updated[@]} -gt 0 ]; then
|
if [ ${#stack_tree[@]} -gt 0 ]; then
|
||||||
msg+=$'\n\n🔄 Aktualisierte Stacks'
|
msg+=$'\n🔄 Stack Updates\n'
|
||||||
for s in "${notify_stacks_updated[@]}"; do
|
|
||||||
msg+=$'\n - '"$s"
|
for stack in $(printf "%s\n" "${!stack_tree[@]}" | sort); do
|
||||||
|
msg+=$'\n'"$stack"
|
||||||
|
msg+="${stack_tree[$stack]}"
|
||||||
|
msg+=$'\n' # Leerzeile nach jedem Stack
|
||||||
done
|
done
|
||||||
|
|
||||||
# 👉 Versionen ergänzen (nur wenn aktiviert und vorhanden)
|
|
||||||
if [ "$SHOW_VERSIONS" = true ] && [ ${#version_report[@]} -gt 0 ]; then
|
|
||||||
msg+=$'\n\n📦 Versionen'
|
|
||||||
for v in "${version_report[@]}"; do
|
|
||||||
msg+=$'\n '"$v"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ ${#notify_excluded_updates[@]} -gt 0 ]; then
|
if [ ${#notify_excluded_updates[@]} -gt 0 ]; then
|
||||||
@@ -422,16 +551,7 @@ if [ "$NTFY_ENABLED" = true ]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$error_flag" = true ]; then
|
if [ ${#stack_tree[@]} -eq 0 ] && \
|
||||||
msg+=$'\n\n❗ Fehler sind aufgetreten – Logs prüfen'
|
|
||||||
PRIORITY=5
|
|
||||||
elif [ ${#notify_stacks_updated[@]} -gt 0 ]; then
|
|
||||||
PRIORITY=3
|
|
||||||
else
|
|
||||||
PRIORITY=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ${#notify_stacks_updated[@]} -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
|
||||||
@@ -440,16 +560,18 @@ if [ "$NTFY_ENABLED" = true ]; then
|
|||||||
msg+=$'\n\n🧹 Cleanup: '"${freed_space} MB freigegeben"
|
msg+=$'\n\n🧹 Cleanup: '"${freed_space} MB freigegeben"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$NTFY_ONLY_ON_CHANGES" = false ] || \
|
if [ "$error_flag" = true ]; then
|
||||||
[ ${#notify_stacks_updated[@]} -gt 0 ] || \
|
PRIORITY=5
|
||||||
[ ${#notify_excluded_updates[@]} -gt 0 ] || \
|
elif [ ${#stack_tree[@]} -gt 0 ]; then
|
||||||
[ "$error_flag" = true ]; then
|
PRIORITY=3
|
||||||
|
|
||||||
send_ntfy "$msg" "$PRIORITY"
|
|
||||||
log INFO "ntfy Nachricht gesendet (prio=$PRIORITY)"
|
|
||||||
else
|
else
|
||||||
log INFO "keine Änderungen, keine ntfy Nachricht"
|
PRIORITY=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
send_ntfy "$msg" "$PRIORITY"
|
||||||
|
log INFO "ntfy Nachricht gesendet (prio=$PRIORITY)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
script_end=$(date +%s)
|
||||||
|
log INFO "⏱ Gesamtzeit: $((script_end - script_start))s"
|
||||||
log INFO "==== Update beendet ===="
|
log INFO "==== Update beendet ===="
|
||||||
Reference in New Issue
Block a user