diff --git a/README.md b/README.md index 27f2d37..40b5f1e 100644 --- a/README.md +++ b/README.md @@ -142,18 +142,6 @@ services: - composeupdater.mode=update ``` -#### 📦 Stack-Level Label - -Du kannst das Verhalten auch für den gesamten Stack setzen: - -```yml -labels: - - composeupdater.mode=ignore -``` -Das Stack-Level Label definiert den Standard für alle Services im Stack -Service-Labels können diesen Standard überschreiben - - --- ## 🗑️ Prune / Cleanup @@ -321,25 +309,26 @@ chmod +x script.sh ## 📄 Beispiel Ausgabe ``` -→ Prüfe Stack: rss - ├─ read (phpdockerio/readability-js-server) - ├─ merc (wangqiru/mercury-parser-api) - ├─ full-text-rss (heussd/fivefilters-full-text-rss:latest) - ├─ rss-bridge (rssbridge/rss-bridge:latest) +🔍 Prüfe Stack: rss + ├─ read (phpdockerio/readability-js-server) [Mode: 🔄 update] + ├─ merc (wangqiru/mercury-parser-api) [Mode: 🔄 update] + ├─ full-text-rss (heussd/fivefilters-full-text-rss:latest) [Mode: 🔄 update] + ├─ rss-bridge (rssbridge/rss-bridge:latest) [Mode: 🔄 update] ⬆ UPDATE alt: rssbridge/rss-bridge:latest@sha256:55215923cf81b2fa6fbb7ecc1bd2555405f4fc06029ae9876e91164a735c7b9d neu: rssbridge/rss-bridge:latest@sha256:f3f0218c8b075cbc7c559c8e6852888e95fa6d68258436da6195efc5ab98b025 - └─ freshrss (freshrss/freshrss:latest) - 🔄 Stack wird neu deployt (Trigger: rss-bridge) - ⏳ Deploy läuft... - ✔ Stack erfolgreich aktualisiert - ℹ Keine Healthchecks definiert → überspringe warten - ⏱ Dauer: 18s + └─ freshrss (freshrss/freshrss:latest) [Mode: 🔄 update] + ♻️ Stack wird neu deployt (Trigger: rss-bridge) + ⏳ Deploy läuft... + ✅ Stack erfolgreich aktualisiert + 💤 Warte 60s nach Deploy + ℹ️ Keine Healthchecks definiert → überspringe warten + 🕒 Dauer: 18s → Prüfe Stack: tinymediamanager - └─ tinymediamanager (tinymediamanager/tinymediamanager:latest) - ⏱ Dauer: 1s + └─ tinymediamanager [Mode: 🚫 ignore] + 🕒 Dauer: 1s ``` --- diff --git a/shell_docker_compose_update.sh b/shell_docker_compose_update.sh index e61daa2..303a325 100644 --- a/shell_docker_compose_update.sh +++ b/shell_docker_compose_update.sh @@ -18,6 +18,9 @@ level_to_int() { esac } +INDENT=" " +SUBINDENT=" " + should_log() { [ "$(level_to_int "$1")" -ge "$(level_to_int "$LOG_LEVEL")" ] } @@ -46,45 +49,6 @@ fi 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() { - 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 -} - -is_label_excluded() { - local svc="$1" - echo "$compose_json" \ | jq -r --arg svc "$svc" ' .services[$svc].labels // {} @@ -92,11 +56,11 @@ is_label_excluded() { then map(split("=") | {(.[0]): .[1]}) | add else . end) - | .["composeupdater.enable"] // empty - ' \ - | grep -qi "^false$" + | .["composeupdater.mode"] // "update" + ' } + get_image() { local svc="$1" @@ -174,20 +138,20 @@ wait_for_healthy() { local start start=$(date +%s) - # 👉 Containerliste bestimmen + # Containerliste bestimmen if [ ${#services[@]} -gt 0 ]; then mapfile -t cids < <(docker compose ps -aq "${services[@]}") else mapfile -t cids < <(docker compose ps -aq) fi - # 👉 keine Container → nichts zu tun + # keine Container → nichts zu tun if [ ${#cids[@]} -eq 0 ]; then - log INFO " ℹ️ Keine Container gefunden → überspringe Healthcheck" + log INFO "${SUBINDENT}ℹ️ Keine Container gefunden → überspringe Healthcheck" return 0 fi - # 👉 prüfen ob überhaupt Healthchecks existieren + # prüfen ob überhaupt Healthchecks existieren local has_healthcheck=false for cid in "${cids[@]}"; do @@ -199,13 +163,13 @@ wait_for_healthy() { fi done - # 👉 wenn keiner vorhanden → direkt raus + # wenn keiner vorhanden → direkt raus if [ "$has_healthcheck" = false ]; then - log INFO " ℹ️ Keine Healthchecks definiert → überspringe warten" + log INFO "${SUBINDENT}ℹ️ Keine Healthchecks → überspringe warten" return 0 fi - log INFO " ⏳ Warte auf healthy Container (max ${timeout}s)" + log INFO "${SUBINDENT}⏳ Warte auf healthy Container (max ${timeout}s)" while true; do local all_ok=true @@ -220,7 +184,7 @@ wait_for_healthy() { done if [ "$all_ok" = true ]; then - log INFO " ✔️ Alle Container healthy" + log INFO "${SUBINDENT}💚 Alle Container healthy" return 0 fi @@ -228,7 +192,7 @@ wait_for_healthy() { now=$(date +%s) if [ $((now - start)) -ge "$timeout" ]; then - log WARN " ⚠️ Healthcheck Timeout erreicht" + log WARN "${SUBINDENT}⚠️ Healthcheck Timeout erreicht" return 1 fi @@ -288,7 +252,7 @@ while IFS= read -r -d '' file; do stack=$(basename "$dir") log INFO "" - log INFO "→ Prüfe Stack: $stack" + log INFO "🔍 Prüfe Stack: $stack" stack_start=$(date +%s) @@ -296,8 +260,6 @@ while IFS= read -r -d '' file; do compose_json=$(docker compose config --format json) - stack_mode=$(get_stack_mode) - mapfile -t services < <(docker compose config --services) total_services=${#services[@]} current_index=0 @@ -331,33 +293,36 @@ while IFS= read -r -d '' file; do fi # ============================= - # Mode bestimmen (Service > Stack) + # Mode (nur Service!) # ============================= mode=$(get_service_mode "$svc") - if [ -z "$mode" ]; then - mode="$stack_mode" - fi + case "$mode" in + update) mode_label="Mode: 🔄 update" ;; + notify-only) mode_label="Mode: 🔔 notify-only" ;; + ignore) mode_label="Mode: 🚫 ignore" ;; + *) + mode_label="Mode: ❓ unknown" + mode="update" + ;; + esac case "$mode" in - ignore) - log INFO " $prefix $svc (ignore)" + log INFO "${INDENT}$prefix $svc [$mode_label]" continue ;; notify-only) - log INFO " $prefix $svc (notify-only)" + log INFO "${INDENT}$prefix $svc ($image) [$mode_label]" before_id=$(get_container_image_id "$svc") - # 👉 ohne Container kein Vergleich sinnvoll if [ -z "$before_id" ]; then continue fi - # 👉 Pull nur einmal pro Image if [ -z "${pulled_images[$image]:-}" ]; then pull_with_retry "$image" || true pulled_images[$image]=1 @@ -373,21 +338,15 @@ while IFS= read -r -d '' file; do ;; update) - # normal weiterlaufen lassen + log INFO "${INDENT}$prefix $svc ($image) [$mode_label]" ;; + esac - *) - log WARN " $prefix $svc (unbekannter mode: $mode → fallback=update)" - ;; - esac - - log INFO " $prefix $svc ($image)" before_id=$(get_container_image_id "$svc") - # 👉 Skip wenn kein Container und nicht erlaubt if [ -z "$before_id" ] && [ "$UPDATE_INCLUDE_STOPPED" = false ]; then - log INFO " ⏭️ übersprungen (kein Container vorhanden)" + log INFO "${SUBINDENT}⏭️ übersprungen (kein Container vorhanden)" continue fi @@ -397,13 +356,13 @@ while IFS= read -r -d '' file; do if [ -z "${pulled_images[$image]:-}" ]; then if ! pull_with_retry "$image"; then - log ERROR " ❌ Pull fehlgeschlagen" + log ERROR "${INDENT}❌ Pull fehlgeschlagen" error_flag=true continue fi pulled_images[$image]=1 else - log DEBUG " ⏩ Pull übersprungen (bereits gemacht)" + log DEBUG "${SUBINDENT}⏩ Pull übersprungen (bereits gemacht)" fi after_id=$(get_local_image_id "$image") @@ -427,9 +386,9 @@ while IFS= read -r -d '' file; do stack_updated=true changed_services+=("$svc") - log INFO " ⬆️ UPDATE" - log INFO " alt: ${image}@${before_id}" - log INFO " neu: ${image}@${after_id}" + log INFO "${SUBINDENT}⬆️ UPDATE" + log INFO "${SUBINDENT} alt: ${image}@${before_id}" + log INFO "${SUBINDENT} neu: ${image}@${after_id}" short_before="${before_id#sha256:}" short_before="${short_before:0:6}" @@ -448,7 +407,9 @@ while IFS= read -r -d '' file; do if [ "$total_services" -eq 1 ]; then svc="${services[0]}" - log INFO " 🔄 Einzelcontainer-Update: $svc" + + log INFO "" + log INFO "${INDENT}🔄 Einzelcontainer-Update: $svc" if [ "${was_running[$svc]}" = 1 ]; then run_cmd docker compose up -d "$svc" --remove-orphans @@ -460,44 +421,44 @@ while IFS= read -r -d '' file; do fi fi - log INFO " ✔️ Container $svc aktualisiert" + log INFO "${SUBINDENT}✅ Container aktualisiert" - # 👉 feste Wartezeit if [ "${REDEPLOY_WAIT:-0}" -gt 0 ]; then - log INFO " ⏳ Warte ${REDEPLOY_WAIT}s nach Deploy" + log INFO "${SUBINDENT}💤 Warte ${REDEPLOY_WAIT}s nach Deploy" sleep "$REDEPLOY_WAIT" fi - # 👉 optionaler Healthcheck if [ "$REDEPLOY_WAIT_HEALTHY" = true ]; then wait_for_healthy "$REDEPLOY_WAIT_HEALTHY_TIMEOUT" "$svc" + else + log INFO "${SUBINDENT}ℹ️ Keine Healthchecks → überspringe warten" fi else - log INFO " 🔄 Stack wird neu deployt (Trigger: ${changed_services[*]})" - log INFO " ⏳ Deploy läuft..." + log INFO "" + log INFO "${INDENT}♻️ Stack wird neu deployt (Trigger: ${changed_services[*]})" + log INFO "${SUBINDENT}⏳ Deploy läuft..." if ! run_cmd docker compose up -d --remove-orphans >/dev/null 2>&1; then - log ERROR " ❌ Stack Update fehlgeschlagen" + log ERROR "${SUBINDENT}❌ Stack Update fehlgeschlagen" error_flag=true else - log INFO " ✔️ Stack erfolgreich aktualisiert" + log INFO "${SUBINDENT}✅ Stack erfolgreich aktualisiert" - # 👉 feste Wartezeit if [ "${REDEPLOY_WAIT:-0}" -gt 0 ]; then - log INFO " ⏳ Warte ${REDEPLOY_WAIT}s nach Deploy" + log INFO "${SUBINDENT}💤 Warte ${REDEPLOY_WAIT}s nach Deploy" sleep "$REDEPLOY_WAIT" fi - # 👉 optionaler Healthcheck if [ "$REDEPLOY_WAIT_HEALTHY" = true ]; then wait_for_healthy "$REDEPLOY_WAIT_HEALTHY_TIMEOUT" "${changed_services[@]}" + else + log INFO "${SUBINDENT}ℹ️ Keine Healthchecks → überspringe warten" fi - # 👉 gestoppte wieder stoppen for svc in "${services[@]}"; do if [ "${was_running[$svc]}" = 0 ]; then - log INFO " ⏹️ Stoppe $svc (war vorher gestoppt)" + log INFO "${SUBINDENT}⏹️ Stoppe $svc (war vorher gestoppt)" run_cmd docker compose stop "$svc" >/dev/null 2>&1 || true fi done @@ -536,7 +497,7 @@ while IFS= read -r -d '' file; do cd "$PATH_COMPOSE_DIR" stack_end=$(date +%s) - log INFO " ⏱ Dauer: $((stack_end - stack_start))s" + log INFO "${INDENT}🕒 Dauer: $((stack_end - stack_start))s" done < <(find . -name "$PATH_COMPOSE_PATTERN" -print0 | sort -z) @@ -546,6 +507,8 @@ done < <(find . -name "$PATH_COMPOSE_PATTERN" -print0 | sort -z) freed_space="0" +log INFO "" + if [ "$CLEANUP_ENABLED" = true ]; then if [ "$CLEANUP_ONLY_ON_UPDATE" = true ] && \ @@ -554,7 +517,8 @@ if [ "$CLEANUP_ENABLED" = true ]; then else before_size=$(get_docker_disk_usage) - log INFO "🧹 Docker Cleanup läuft..." + log INFO "" + log INFO "${INDENT}🧹 Docker Cleanup läuft..." if [ "$CLEANUP_IMAGES_ENABLED" = true ]; then case "$CLEANUP_IMAGES_MODE" in @@ -578,7 +542,7 @@ if [ "$CLEANUP_ENABLED" = true ]; then after_size=$(get_docker_disk_usage) freed_space=$((after_size < before_size ? before_size - after_size : 0)) - log INFO "✔️ Cleanup abgeschlossen (${freed_space} MB freigegeben)" + log INFO "${INDENT}✅ Cleanup abgeschlossen (${freed_space} MB freigegeben)" fi fi @@ -625,9 +589,9 @@ if [ "$NTFY_ENABLED" = true ]; then fi send_ntfy "$msg" "$PRIORITY" - log INFO "ntfy Nachricht gesendet (prio=$PRIORITY)" + log INFO "📨 ntfy Nachricht gesendet (prio=$PRIORITY)" fi script_end=$(date +%s) -log INFO "⏱ Gesamtzeit: $((script_end - script_start))s" +log INFO "🕒 Gesamtzeit: $((script_end - script_start))s" log INFO "==== Update beendet ====" \ No newline at end of file