feat(docker): add docker_update.sh for daily container image pulls

Pulls the latest image for each container in DAILY_RESTART_CONTAINERS
before docker_daily_restart.sh runs. Containers stay running during the
pull — no extra downtime. Mirrors the weekly update pattern exactly.

Toggle: DAILY_CONTAINER_UPDATES=true/false in master.conf.
Pull failures are non-fatal — the daily restart proceeds regardless.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gmer4Lfe
2026-05-08 19:12:00 -04:00
co-authored by Claude Sonnet 4.6
parent 69464ba385
commit 0adfe04173
2 changed files with 177 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
#!/bin/bash
# ==============================================================================================
# ================================= Docker Update ==============================================
# ==============================================================================================
# Pulls the latest image for each container in HOST*_DAILY_RESTART_CONTAINERS.
# Called by daily_sync_maintenance.sh before docker_daily_restart.sh — containers stay
# running during the pull, so there is no extra downtime.
#
# ── WHY SAME LIST AS DAILY RESTART ───────────────────────────────────────────────────────────
# Containers that restart daily (auth stack: Authelia, NPM, Mariadb, Redis, etc.) are
# exactly the containers that benefit from staying current. Reusing DAILY_RESTART_CONTAINERS
# means no second list to maintain — add/remove a container once and both update + restart
# reflect the change automatically.
#
# ── WHAT THIS DOES ────────────────────────────────────────────────────────────────────────────
# docker pull <image> — fetches the latest digest from the registry
# Old vs new image ID comparison — distinguishes "updated" from "already current"
# Containers keep running — pull does not affect the live container
# docker_daily_restart.sh runs after — containers restart on the fresh image
#
# ── TOGGLE ────────────────────────────────────────────────────────────────────────────────────
# DAILY_CONTAINER_UPDATES=false in master.conf — skips all pulls, exits cleanly
# docker_daily_restart.sh still runs regardless — update and restart are independent
#
# ── CONFIGURATION ─────────────────────────────────────────────────────────────────────────────
# master.conf: DAILY_CONTAINER_UPDATES — enable/disable (default: true)
# master_host*.conf: HOST*_DAILY_RESTART_CONTAINERS — containers to update
#
# ── USAGE ─────────────────────────────────────────────────────────────────────────────────────
# docker_update.sh — normal run
# docker_update.sh --dry-run — show what would be pulled
# docker_update.sh --log — verbose output
# docker_update.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 [[ "${DAILY_CONTAINER_UPDATES:-true}" != "true" ]]; then
log "DAILY_CONTAINER_UPDATES=false — skipping container updates"
exit 0
fi
if [[ ${#DAILY_RESTART_CONTAINERS[@]} -eq 0 ]]; then
warn "DAILY_RESTART_CONTAINERS is empty for $MY_ID — nothing to update"
warn "Check HOST${MY_ID#HOST}_DAILY_RESTART_CONTAINERS in master_host*.conf"
exit 0
fi
# ==============================================================================================
# ━━━ Status ━━━
# ==============================================================================================
if [[ "$SHOW_STATUS" == true ]]; then
echo ""
echo "━━━━━ $ICON_SUMMARY STATUS ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_CONTAINERS Containers: ${DAILY_RESTART_CONTAINERS[*]}"
echo "$ICON_GEAR Enabled: ${DAILY_CONTAINER_UPDATES:-true}"
echo "$ICON_GEAR Dry Run: $DRY_RUN"
echo "━━━━━━━━━━━━━━━━━━━━━━━"
exit 0
fi
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no images will be pulled"
# ==============================================================================================
# ━━━ Pull Updates ━━━
# ==============================================================================================
echo ""
echo "━━━ $ICON_CONTAINERS Docker Update — $(date '+%Y-%m-%d %H:%M:%S') ━━━"
echo "$ICON_CONTAINERS Containers: ${DAILY_RESTART_CONTAINERS[*]}"
echo ""
START=$(date +%s)
UPDATED=()
UP_TO_DATE=()
FAILED=()
SKIPPED=()
for container in "${DAILY_RESTART_CONTAINERS[@]}"; do
[[ -z "$container" ]] && continue
echo "━━━ $ICON_CONTAINERS $container ━━━"
if ! docker inspect "$container" &>/dev/null; then
warn "$container — not found, skipping"
SKIPPED+=("$container")
echo ""
continue
fi
IMAGE=$(docker inspect --format='{{.Config.Image}}' "$container" 2>/dev/null)
if [[ -z "$IMAGE" ]]; then
warn "$container — could not determine image, skipping"
SKIPPED+=("$container")
echo ""
continue
fi
log "$container — image: $IMAGE"
if [[ "$DRY_RUN" == true ]]; then
warn "DRY RUN — would pull: $IMAGE"
UPDATED+=("$container")
echo ""
continue
fi
# Capture image ID before pull to detect whether an update landed
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 SUMMARY ━━━━━"
echo "$ICON_HOST Identity: $MY_ID ($LOCAL_SERVER_NAME)"
echo "$ICON_TIME Duration: $(format_duration $(( END - START )))"
[[ ${#UPDATED[@]} -gt 0 ]] && echo "$ICON_DONE Updated: ${UPDATED[*]}"
[[ ${#UP_TO_DATE[@]} -gt 0 ]] && echo "$ICON_RUNNING Up to date: ${UP_TO_DATE[*]}"
[[ ${#SKIPPED[@]} -gt 0 ]] && echo "$ICON_WARN Skipped: ${SKIPPED[*]}"
[[ ${#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 — restart will proceed with existing images"
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Pull failures are non-fatal — restart proceeds regardless
exit 0
+6
View File
@@ -269,9 +269,15 @@
#"Media/lidarr_cleanup.sh" # remove orphaned music files — enable when ready #"Media/lidarr_cleanup.sh" # remove orphaned music files — enable when ready
#"Media/sonarr_cleanup.sh" # remove orphaned TV files — enable when ready #"Media/sonarr_cleanup.sh" # remove orphaned TV files — enable when ready
#"Media/radarr_cleanup.sh" # remove orphaned movie files — enable when ready #"Media/radarr_cleanup.sh" # remove orphaned movie files — enable when ready
"Docker_Essentials/docker_update.sh" # pull container image updates before restart
"Docker_Essentials/docker_daily_restart.sh" # daily container restarts — runs last "Docker_Essentials/docker_daily_restart.sh" # daily container restarts — runs last
) )
# Pull latest images for DAILY_RESTART_CONTAINERS before the daily restart.
# Containers keep running during pull — no extra downtime.
# Set false to skip updates while still running the daily restart.
DAILY_CONTAINER_UPDATES=true
# Media shares synced daily by daily_sync_maintenance.sh. # Media shares synced daily by daily_sync_maintenance.sh.
# Defined per-host in master_host*.conf — HOST1_DAILY_SYNC_SHARES and HOST2_DAILY_SYNC_SHARES. # Defined per-host in master_host*.conf — HOST1_DAILY_SYNC_SHARES and HOST2_DAILY_SYNC_SHARES.
# Each server syncs only the shares it owns — direction is automatic. # Each server syncs only the shares it owns — direction is automatic.