first commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
config.conf.own
|
||||||
128
README.md
Normal file
128
README.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Docker Compose Auto-Updater
|
||||||
|
|
||||||
|
Dieses Script überprüft mehrere Docker-Compose-Stacks auf Image-Updates und aktualisiert diese automatisch.
|
||||||
|
|
||||||
|
## 🚀 Features
|
||||||
|
|
||||||
|
- 🔄 Stack-basiertes Update
|
||||||
|
- 🧪 Dry-Run Modus
|
||||||
|
- 📲 ntfy Benachrichtigungen
|
||||||
|
- ⏭️ Exclude-Liste für Services
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Voraussetzungen
|
||||||
|
|
||||||
|
- Docker + Docker Compose (v2)
|
||||||
|
- Bash
|
||||||
|
- Optional: ntfy Server
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Konfiguration (`config.conf`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pfad zu deinen Compose-Files
|
||||||
|
COMPOSE_DIR="/pfad/zu/deinen/stacks"
|
||||||
|
# Dateimuster
|
||||||
|
COMPOSE_PATTERN="docker-compose.yml"
|
||||||
|
|
||||||
|
# Exclude Container
|
||||||
|
EXCLUDE_SERVICES=(
|
||||||
|
"example_container_1"
|
||||||
|
"example_container_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exclude Stack
|
||||||
|
EXCLUDE_STACKS=(
|
||||||
|
"example_stack_1"
|
||||||
|
"example_stack_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verhalten bei gestoppten Containern
|
||||||
|
UPDATE_STOPPED=true # Image aktualisieren
|
||||||
|
START_STOPPED=false # danach NICHT starten
|
||||||
|
|
||||||
|
# Dry Run (true/false)
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_FILE="/pfad/zum/log/update.log"
|
||||||
|
LOG_LEVEL="INFO"
|
||||||
|
|
||||||
|
# ntfy
|
||||||
|
NTFY_ENABLED=true
|
||||||
|
NTFY_TITLE="Docker Update ($(hostname))"
|
||||||
|
NTFY_TOKEN="DEIN_TOKEN"
|
||||||
|
NTFY_URL="https://ntfy.example.com/topic"
|
||||||
|
NTFY_TAGS="docker,update"
|
||||||
|
NTFY_ONLY_ON_CHANGES=false
|
||||||
|
# Versions Nr. der Container in der NTFY Nachricht anzeigen (true/false)
|
||||||
|
SHOW_VERSIONS=true
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ▶️ Nutzung
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x script.sh
|
||||||
|
./script.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧠 Funktionsweise
|
||||||
|
|
||||||
|
1. Alle `docker-compose.yml` Dateien werden gefunden
|
||||||
|
2. Alphabetisch sortiert
|
||||||
|
3. Jeder Stack wird geprüft:
|
||||||
|
- Image wird gepullt
|
||||||
|
- Vergleich: Container Image-ID vs. aktuelles Image
|
||||||
|
4. Wenn ein Service ein Update hat:
|
||||||
|
- kompletter Stack wird neu deployed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔔 ntfy Prioritäten
|
||||||
|
|
||||||
|
| Zustand | Priorität |
|
||||||
|
|----------------------|----------|
|
||||||
|
| ✔️ Keine Updates | 1 |
|
||||||
|
| 🔄 Updates vorhanden | 3 |
|
||||||
|
| ❌ Fehler | 5 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📄 Beispiel Ausgabe
|
||||||
|
|
||||||
|
```
|
||||||
|
→ Prüfe Stack: homepage
|
||||||
|
├─ dockerproxy (image)
|
||||||
|
└─ homepage (image)
|
||||||
|
|
||||||
|
→ Prüfe Stack: app
|
||||||
|
├─ db (image)
|
||||||
|
⬆️ UPDATE
|
||||||
|
alt: sha256:abc
|
||||||
|
neu: sha256:def
|
||||||
|
└─ web (image)
|
||||||
|
🔄 Stack wird neu deployt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Hinweise
|
||||||
|
|
||||||
|
Wird in einem Stack ein Container aktualisiert, wird anschließend der gesamte Stack neu gestartet, sofern er mehr als einen Container enthält. Dadurch wird sichergestellt, dass alle Abhängigkeiten wieder gemäß der `docker-compose.yml` ausgeführt werden.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 Dry Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DRY_RUN=true
|
||||||
|
```
|
||||||
|
|
||||||
|
→ zeigt nur, was passieren würde
|
||||||
BIN
composeupdater.png
Normal file
BIN
composeupdater.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 266 KiB |
41
config.conf
Normal file
41
config.conf
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Pfad zu deinen Compose-Files
|
||||||
|
COMPOSE_DIR="/pfad/zu/deinen/stacks"
|
||||||
|
|
||||||
|
# Dateimuster
|
||||||
|
COMPOSE_PATTERN="docker-compose.yml"
|
||||||
|
|
||||||
|
# Exclude Container
|
||||||
|
EXCLUDE_SERVICES=(
|
||||||
|
"example_container_1"
|
||||||
|
"example_container_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Exclude Stack
|
||||||
|
EXCLUDE_STACKS=(
|
||||||
|
"example_stack_1"
|
||||||
|
"example_stack_2"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verhalten bei gestoppten Containern
|
||||||
|
UPDATE_STOPPED=true # Image aktualisieren
|
||||||
|
START_STOPPED=false # danach NICHT starten
|
||||||
|
|
||||||
|
# Dry Run (true/false)
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_FILE="/pfad/zum/log/update.log"
|
||||||
|
LOG_LEVEL="INFO" # DEBUG=sehr detailliert, INFO=Standard, WARN=nur wichtige Hinweise/Updates, ERROR=nur Fehler
|
||||||
|
|
||||||
|
# ntfy
|
||||||
|
NTFY_ENABLED=true
|
||||||
|
NTFY_TITLE="Docker Update ($(hostname))" # ntfy Titel (frei definierbar)
|
||||||
|
NTFY_TOKEN="DEIN_TOKEN"
|
||||||
|
NTFY_URL="https://ntfy.example.com/topic"
|
||||||
|
NTFY_TAGS="docker,update"
|
||||||
|
NTFY_ONLY_ON_CHANGES=false
|
||||||
|
# Versions Nr. anzeigen (true/false)
|
||||||
|
SHOW_VERSIONS=true
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
304
shell_docker_compose_update.sh
Normal file
304
shell_docker_compose_update.sh
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
BASE_DIR="$(dirname "$0")"
|
||||||
|
source "$BASE_DIR/config.conf"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$LOG_FILE")"
|
||||||
|
|
||||||
|
LOG_LEVEL="${LOG_LEVEL:-INFO}"
|
||||||
|
|
||||||
|
level_to_int() {
|
||||||
|
case "$1" in
|
||||||
|
DEBUG) echo 0 ;;
|
||||||
|
INFO) echo 1 ;;
|
||||||
|
WARN) echo 2 ;;
|
||||||
|
ERROR) echo 3 ;;
|
||||||
|
*) echo 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
should_log() {
|
||||||
|
[ "$(level_to_int "$1")" -ge "$(level_to_int "$LOG_LEVEL")" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
local level="$1"
|
||||||
|
shift
|
||||||
|
local msg="$*"
|
||||||
|
|
||||||
|
if should_log "$level"; then
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') | $level | $msg" | tee -a "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Log begrenzen
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
if [ -f "$LOG_FILE" ]; then
|
||||||
|
line_count=$(wc -l < "$LOG_FILE")
|
||||||
|
if [ "$line_count" -gt 1000 ]; then
|
||||||
|
tail -n 1000 "$LOG_FILE" > "$LOG_FILE.tmp" && mv "$LOG_FILE.tmp" "$LOG_FILE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Helper
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
get_image() {
|
||||||
|
local svc="$1"
|
||||||
|
docker compose config | awk "/^ $svc:/,/image:/" | grep image | head -n1 | awk '{print $2}'
|
||||||
|
}
|
||||||
|
|
||||||
|
get_container_image_id() {
|
||||||
|
local svc="$1"
|
||||||
|
local cid
|
||||||
|
|
||||||
|
cid=$(docker compose ps -q "$svc" 2>/dev/null || true)
|
||||||
|
[ -z "$cid" ] && echo "" && return
|
||||||
|
|
||||||
|
docker inspect -f '{{.Image}}' "$cid" 2>/dev/null || echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
get_local_image_id() {
|
||||||
|
local image="$1"
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
run_cmd() {
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
log DEBUG "[DRY RUN] $*"
|
||||||
|
else
|
||||||
|
eval "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
send_ntfy() {
|
||||||
|
local msg="$1"
|
||||||
|
local prio="$2"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Start
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
log INFO "==== Docker Compose Update gestartet ===="
|
||||||
|
|
||||||
|
notify_stacks_updated=()
|
||||||
|
notify_excluded_updates=()
|
||||||
|
error_flag=false
|
||||||
|
|
||||||
|
cd "$COMPOSE_DIR"
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Sortierung
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
|
||||||
|
dir=$(dirname "$file")
|
||||||
|
stack=$(basename "$dir")
|
||||||
|
|
||||||
|
if is_stack_excluded "$stack"; then
|
||||||
|
log INFO "→ Stack $stack übersprungen (excluded)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log INFO ""
|
||||||
|
log INFO "→ Prüfe Stack: $stack"
|
||||||
|
|
||||||
|
cd "$dir"
|
||||||
|
|
||||||
|
mapfile -t services < <(docker compose config --services)
|
||||||
|
|
||||||
|
total_services=${#services[@]}
|
||||||
|
current_index=0
|
||||||
|
|
||||||
|
stack_updated=false
|
||||||
|
changed_services=()
|
||||||
|
version_report=()
|
||||||
|
|
||||||
|
for svc in "${services[@]}"; do
|
||||||
|
|
||||||
|
current_index=$((current_index + 1))
|
||||||
|
|
||||||
|
if [ "$current_index" -eq "$total_services" ]; then
|
||||||
|
prefix="└─"
|
||||||
|
else
|
||||||
|
prefix="├─"
|
||||||
|
fi
|
||||||
|
|
||||||
|
image=$(get_image "$svc")
|
||||||
|
|
||||||
|
if [ -z "$image" ]; then
|
||||||
|
log WARN " $prefix $svc → kein Image"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
if is_excluded "$svc"; then
|
||||||
|
docker pull "$image" >/dev/null 2>&1 || true
|
||||||
|
notify_excluded_updates+=("$stack/$svc")
|
||||||
|
log INFO " $prefix $svc (excluded)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log INFO " $prefix $svc ($image)"
|
||||||
|
|
||||||
|
before_id=$(get_container_image_id "$svc")
|
||||||
|
before_ref=$(get_container_image_ref "$svc")
|
||||||
|
|
||||||
|
if ! docker pull "$image" >/dev/null 2>&1; then
|
||||||
|
log ERROR " $prefix ❌ Pull fehlgeschlagen"
|
||||||
|
error_flag=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
after_id=$(get_local_image_id "$image")
|
||||||
|
after_digest=$(get_local_image_digest "$image")
|
||||||
|
|
||||||
|
if [ "$before_id" != "$after_id" ] && [ -n "$after_id" ]; then
|
||||||
|
stack_updated=true
|
||||||
|
changed_services+=("$svc")
|
||||||
|
|
||||||
|
if [ "$SHOW_VERSIONS" = true ]; then
|
||||||
|
log WARN " ⬆️ UPDATE"
|
||||||
|
log INFO " alt: $before_ref"
|
||||||
|
log INFO " neu: $after_digest"
|
||||||
|
version_report+=("$svc: ${before_ref##*@} → ${after_digest##*@}")
|
||||||
|
else
|
||||||
|
log WARN " ⬆️ UPDATE"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Update Logik
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
if [ "$stack_updated" = true ]; then
|
||||||
|
|
||||||
|
if [ "$total_services" -eq 1 ]; then
|
||||||
|
svc="${services[0]}"
|
||||||
|
|
||||||
|
log WARN " 🔄 Einzelcontainer-Update: $svc"
|
||||||
|
|
||||||
|
if ! run_cmd docker compose up -d "$svc" >/dev/null 2>&1; then
|
||||||
|
log ERROR " ❌ Update fehlgeschlagen"
|
||||||
|
error_flag=true
|
||||||
|
else
|
||||||
|
log INFO " ✔️ Container erfolgreich aktualisiert"
|
||||||
|
notify_stacks_updated+=("$stack ($svc)")
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
log WARN " 🔄 Stack wird neu deployt (Trigger: ${changed_services[*]})"
|
||||||
|
|
||||||
|
if ! run_cmd docker compose up -d --remove-orphans >/dev/null 2>&1; then
|
||||||
|
log ERROR " ❌ Stack Update fehlgeschlagen"
|
||||||
|
error_flag=true
|
||||||
|
else
|
||||||
|
log INFO " ✔️ Stack erfolgreich aktualisiert"
|
||||||
|
notify_stacks_updated+=("$stack (${changed_services[*]})")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
log DEBUG " ✔️ Keine Updates"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$COMPOSE_DIR"
|
||||||
|
|
||||||
|
done < <(find . -name "$COMPOSE_PATTERN" -print0 | sort -z)
|
||||||
|
|
||||||
|
# =============================
|
||||||
|
# Notification
|
||||||
|
# =============================
|
||||||
|
|
||||||
|
if [ "$NTFY_ENABLED" = true ]; then
|
||||||
|
|
||||||
|
msg="Docker Compose Update Report"
|
||||||
|
|
||||||
|
if [ ${#notify_stacks_updated[@]} -gt 0 ]; then
|
||||||
|
msg+=$'\n\n🔄 Aktualisierte Stacks'
|
||||||
|
for s in "${notify_stacks_updated[@]}"; do
|
||||||
|
msg+=$'\n - '"$s"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ${#notify_excluded_updates[@]} -gt 0 ]; then
|
||||||
|
msg+=$'\n\n⏭️ Excluded (Update verfügbar)'
|
||||||
|
for s in "${notify_excluded_updates[@]}"; do
|
||||||
|
msg+=$'\n - '"$s"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Priority fix
|
||||||
|
if [ "$error_flag" = true ]; then
|
||||||
|
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
|
||||||
|
msg+=$'\n\n✔️ Alles aktuell'
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$NTFY_ONLY_ON_CHANGES" = false ] || \
|
||||||
|
[ ${#notify_stacks_updated[@]} -gt 0 ] || \
|
||||||
|
[ ${#notify_excluded_updates[@]} -gt 0 ] || \
|
||||||
|
[ "$error_flag" = true ]; then
|
||||||
|
|
||||||
|
send_ntfy "$msg" "$PRIORITY"
|
||||||
|
log INFO "ntfy Nachricht gesendet (prio=$PRIORITY)"
|
||||||
|
else
|
||||||
|
log INFO "keine Änderungen, keine ntfy Nachricht"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log INFO "==== Update beendet ===="
|
||||||
Reference in New Issue
Block a user