From 4e22f5d1f7cd849406907b5bb0f576a8fe472d85 Mon Sep 17 00:00:00 2001 From: Gmer4Lfe Date: Fri, 8 May 2026 19:13:50 -0400 Subject: [PATCH] feat(docker): add docker_update_remaining.sh for weekly catch-all image pulls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls the latest image for every running container not already covered by the daily update (DAILY_RESTART_CONTAINERS) or weekly restart (WEEKLY_RESTART_CONTAINERS) lists. Runs at the end of WEEKLY_MAINTENANCE_SCRIPTS. Exclusion set is derived automatically — no list to maintain. Together with docker_update.sh, every deployed container gets at least one image pull per week without relying on the CA Update Applications plugin. Toggle: WEEKLY_REMAINING_UPDATES=true/false in master.conf. Co-Authored-By: Claude Sonnet 4.6 --- Docker_Essentials/docker_update_remaining.sh | 181 +++++++++++++++++++ master.conf | 8 +- 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 Docker_Essentials/docker_update_remaining.sh diff --git a/Docker_Essentials/docker_update_remaining.sh b/Docker_Essentials/docker_update_remaining.sh new file mode 100644 index 0000000..becdbab --- /dev/null +++ b/Docker_Essentials/docker_update_remaining.sh @@ -0,0 +1,181 @@ +#!/bin/bash +# ============================================================================================== +# ============================= Docker Update — Remaining ====================================== +# ============================================================================================== +# Pulls the latest image for every running container NOT already covered by the daily or +# weekly update/restart cycles. Runs at the end of the weekly maintenance window. +# +# ── WHAT THIS COVERS ────────────────────────────────────────────────────────────────────────── +# Daily update: DAILY_RESTART_CONTAINERS — auth stack, NPM, Dispatcharr, etc. +# Weekly update: WEEKLY_RESTART_CONTAINERS — NextCloud, AdGuard, Immich, etc. +# This script: everything else running on the system (media stack, utilities, etc.) +# +# Together the three scripts ensure every deployed container receives at least one image +# pull per week, with no container list to maintain here — it derives the remainder +# automatically from `docker ps` minus the two managed lists. +# +# ── EXCLUSION LOGIC ─────────────────────────────────────────────────────────────────────────── +# Exclusion set = DAILY_RESTART_CONTAINERS + WEEKLY_RESTART_CONTAINERS (aliased by detect_hosts) +# Only running containers are targeted — stopped containers are intentionally excluded +# (stopped = likely paused intentionally; pulling while stopped adds no value). +# +# ── TOGGLE ──────────────────────────────────────────────────────────────────────────────────── +# WEEKLY_REMAINING_UPDATES=false in master.conf — skips all pulls, exits cleanly +# +# ── CONFIGURATION ───────────────────────────────────────────────────────────────────────────── +# master.conf: WEEKLY_REMAINING_UPDATES — enable/disable (default: true) +# master_host*.conf: HOST*_DAILY_RESTART_CONTAINERS — excluded from this script +# HOST*_WEEKLY_RESTART_CONTAINERS — excluded from this script +# +# ── USAGE ───────────────────────────────────────────────────────────────────────────────────── +# docker_update_remaining.sh — normal run +# docker_update_remaining.sh --dry-run — show which containers would be pulled +# docker_update_remaining.sh --log — verbose output +# docker_update_remaining.sh --status — show config and exit +# ============================================================================================== + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +source "$SCRIPT_DIR/../load_config.sh" + +parse_args "$@" + +# ============================================================================================== +# ━━━ Setup ━━━ +# ============================================================================================== +if [[ "$EUID" -ne 0 ]]; then + error "Must be run as root" + exit 1 +fi + +if ! command -v docker &>/dev/null; then + error "Docker command not found" + exit 1 +fi + +detect_hosts + +if [[ "${WEEKLY_REMAINING_UPDATES:-true}" != "true" ]]; then + log "WEEKLY_REMAINING_UPDATES=false — skipping remaining container updates" + exit 0 +fi + +# ── Build exclusion set from daily + weekly managed lists ───────────────────────────────────── +declare -A EXCLUDED +for c in "${DAILY_RESTART_CONTAINERS[@]}" "${WEEKLY_RESTART_CONTAINERS[@]}"; do + [[ -n "$c" ]] && EXCLUDED["$c"]=1 +done + +# ── Get all running containers ──────────────────────────────────────────────────────────────── +mapfile -t ALL_RUNNING < <(docker ps --format '{{.Names}}' 2>/dev/null | sort) + +# ── Derive remainder: running minus excluded ────────────────────────────────────────────────── +REMAINING=() +for c in "${ALL_RUNNING[@]}"; do + [[ -z "$c" ]] && continue + [[ -n "${EXCLUDED[$c]:-}" ]] && continue + REMAINING+=("$c") +done + +# ============================================================================================== +# ━━━ Status ━━━ +# ============================================================================================== +if [[ "$SHOW_STATUS" == true ]]; then + echo "" + echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━" + echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" + echo "$ICON_GEAR Enabled: ${WEEKLY_REMAINING_UPDATES:-true}" + echo "$ICON_CONTAINERS All running: ${#ALL_RUNNING[@]}" + echo "$ICON_CONTAINERS Excluded: ${!EXCLUDED[*]}" + echo "$ICON_CONTAINERS Remaining: ${REMAINING[*]:-none}" + echo "━━━━━━━━━━━━━━━━━━━━━━━" + exit 0 +fi + +if [[ ${#REMAINING[@]} -eq 0 ]]; then + log "No remaining containers to update — all running containers are covered by daily/weekly lists" + exit 0 +fi + +[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no images will be pulled" + +# ============================================================================================== +# ━━━ Pull Updates ━━━ +# ============================================================================================== +echo "" +echo "━━━ $ICON_CONTAINERS Docker Update (Remaining) — $(date '+%Y-%m-%d %H:%M:%S') ━━━" +echo "$ICON_CONTAINERS Containers: ${REMAINING[*]}" +echo "$ICON_CONTAINERS Excluded (managed elsewhere): ${!EXCLUDED[*]}" +echo "" + +START=$(date +%s) +UPDATED=() +UP_TO_DATE=() +FAILED=() + +for container in "${REMAINING[@]}"; do + [[ -z "$container" ]] && continue + echo "━━━ $ICON_CONTAINERS $container ━━━" + + IMAGE=$(docker inspect --format='{{.Config.Image}}' "$container" 2>/dev/null) + if [[ -z "$IMAGE" ]]; then + warn "$container — could not determine image, skipping" + FAILED+=("$container") + echo "" + continue + fi + + log "$container — image: $IMAGE" + + if [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — would pull: $IMAGE" + UPDATED+=("$container") + echo "" + continue + fi + + OLD_ID=$(docker image inspect "$IMAGE" --format='{{.Id}}' 2>/dev/null || echo "") + + echo "$ICON_SYNC Pulling $IMAGE..." + if docker pull "$IMAGE" 2>&1 | grep -E "^(Status:|Digest:|Error|error)" | sed 's/^/ /'; then + NEW_ID=$(docker image inspect "$IMAGE" --format='{{.Id}}' 2>/dev/null || echo "") + + if [[ -n "$OLD_ID" ]] && [[ "$OLD_ID" != "$NEW_ID" ]]; then + echo "$ICON_DONE $container — updated ✅" + UPDATED+=("$container") + else + log "$container — already up to date" + UP_TO_DATE+=("$container") + fi + else + warn "$container — pull failed ($IMAGE)" + FAILED+=("$container") + fi + + echo "" +done + +END=$(date +%s) + +# ============================================================================================== +# ━━━ Summary ━━━ +# ============================================================================================== +echo "━━━━━ $ICON_SUMMARY DOCKER UPDATE (REMAINING) SUMMARY ━━━━━" +echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)" +echo "$ICON_TIME Duration: $(format_duration $(( END - START )))" +echo "$ICON_CONTAINERS Scope: ${#ALL_RUNNING[@]} running — ${#EXCLUDED[@]} managed = ${#REMAINING[@]} checked" +[[ ${#UPDATED[@]} -gt 0 ]] && echo "$ICON_DONE Updated: ${UPDATED[*]}" +[[ ${#UP_TO_DATE[@]} -gt 0 ]] && echo "$ICON_RUNNING Up to date: ${UP_TO_DATE[*]}" +[[ ${#FAILED[@]} -gt 0 ]] && echo "$ICON_ERROR Failed: ${FAILED[*]}" + +if [[ "$DRY_RUN" == true ]]; then + warn "DRY RUN — no images pulled" +elif [[ ${#FAILED[@]} -eq 0 ]]; then + log "$ICON_DONE Status: done ✅ — ${#UPDATED[@]} updated, ${#UP_TO_DATE[@]} current" +else + warn "Status: ${#FAILED[@]} pull(s) failed — containers continue on existing images" +fi +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + +# Pull failures are non-fatal +exit 0 diff --git a/master.conf b/master.conf index 25b19d9..e3770ee 100644 --- a/master.conf +++ b/master.conf @@ -295,9 +295,15 @@ # syncs WEEKLY_SYNC_SHARES → restarts → then iterates WEEKLY_MAINTENANCE_SCRIPTS. # Schedule: 30 2 * * 0 (Sunday 2:30am) WEEKLY_MAINTENANCE_SCRIPTS=( - "Docker_Essentials/docker_weekly_restart.sh" # weekly container restarts after sync + "Docker_Essentials/docker_weekly_restart.sh" # weekly container restarts after sync + "Docker_Essentials/docker_update_remaining.sh" # pull updates for all other containers ) +# Pull updates for all running containers NOT in daily/weekly restart lists. +# Ensures every deployed container receives at least one image pull per week. +# Set false to skip — docker_weekly_restart.sh still runs regardless. + WEEKLY_REMAINING_UPDATES=true + # Shares synced during the weekly maintenance window — defined per host in master_host*.conf. # HOST1_WEEKLY_SYNC_SHARES / HOST2_WEEKLY_SYNC_SHARES # Containers stopped both sides before sync — full clean state guaranteed.