From 728726d5ccac635527c34e7ae1280510d1473abe Mon Sep 17 00:00:00 2001 From: MickLesk Date: Mon, 6 Jul 2026 14:43:25 +0200 Subject: [PATCH] fix(docker): safe, interactive per-container update check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the destructive multi-select container update block with a proper per-container Y/N workflow: - Unattended / non-interactive (DOCKER_NONINTERACTIVE=1 or no tty): skip silently -- no docker pulls, no prompts. - Interactive: stop_spinner() before any prompt to keep the terminal clean, then for each container with a newer image: Compose-managed → prompt Y/N (auto-no after 60 s) → on Y: docker compose pull && docker compose up -d Standalone run → prompt Y/N (auto-no after 60 s) → on Y: docker pull only; no stop/rm; user is told to recreate manually - portainer / portainer_agent are excluded (handled earlier in setup_docker) The old code always stopped and removed the container without recreating it, leaving users with a destroyed service. Standalone containers without a Compose project can never be auto-recreated safely, so only the image is pulled. Fixes #15601 --- misc/tools.func | 84 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/misc/tools.func b/misc/tools.func index 29bf34314..e8e18f4f3 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -4454,7 +4454,9 @@ setup_composer() { # - Uses stable distro packages by default # - Migrates from get.docker.com to repository-based installation # - Updates Docker Engine if newer version available -# - Interactive container update with multi-select +# - Interactive per-container update prompt (Y/N, 60 s auto-no) +# - Compose-managed containers: full restart via docker compose +# - Standalone containers: image pull only, no destructive stop/rm # - Portainer installation and update support # - Set DOCKER_NONINTERACTIVE=1 to skip interactive prompts (CI/unattended) # ------------------------------------------------------------------------------ @@ -4641,6 +4643,86 @@ EOF fi fi + # Container Update Check + # - Skipped entirely when running unattended / non-interactive + # - Compose-managed containers: offered via Y/N → docker compose pull + up -d + # - Standalone containers: offered via Y/N → image pull only (no stop/rm) + if [ "$docker_installed" = true ] && ! _docker_is_noninteractive; then + msg_info "Checking for container updates" + + local name image compose_workdir compose_service current_digest latest_digest + local compose_updates=() + local standalone_updates=() + + while IFS= read -r line; do + name=$(echo "$line" | awk '{print $1}') + image=$(echo "$line" | awk '{print $2}') + + # Portainer containers are handled by the dedicated block above + [[ "$name" == "portainer" || "$name" == "portainer_agent" ]] && continue + + current_digest=$(docker inspect "$name" --format='{{.Image}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12) + docker pull "$image" >/dev/null 2>&1 || continue + latest_digest=$(docker inspect "$image" --format='{{.Id}}' 2>/dev/null | cut -d':' -f2 | cut -c1-12) + [[ -z "$latest_digest" || "$current_digest" == "$latest_digest" ]] && continue + + compose_workdir=$(docker inspect "$name" \ + --format='{{index .Config.Labels "com.docker.compose.project.working_dir"}}' 2>/dev/null || true) + compose_service=$(docker inspect "$name" \ + --format='{{index .Config.Labels "com.docker.compose.service"}}' 2>/dev/null || true) + + if [[ -n "$compose_workdir" && -n "$compose_service" && -d "$compose_workdir" ]]; then + compose_updates+=("${name}|${image}|${compose_workdir}|${compose_service}") + else + standalone_updates+=("${name}|${image}") + fi + done < <(docker ps --format '{{.Names}} {{.Image}}') + + # Stop spinner before any interactive prompt + stop_spinner + + if [[ ${#compose_updates[@]} -eq 0 && ${#standalone_updates[@]} -eq 0 ]]; then + msg_ok "All containers are up-to-date" + else + local reply + for entry in "${compose_updates[@]}"; do + IFS='|' read -r name image compose_workdir compose_service <<<"$entry" + reply="" + if read -r -t 60 -p "${TAB3}Update ${name} (${image}) via Compose? (auto-no in 60s): " reply; then + echo "" + else + echo "" + fi + if [[ "${reply,,}" =~ ^(y|yes)$ ]]; then + msg_info "Updating $name" + if (cd "$compose_workdir" && $STD docker compose pull "$compose_service" && $STD docker compose up -d "$compose_service"); then + msg_ok "Updated $name" + else + msg_warn "Could not update $name — try manually in $compose_workdir" + fi + fi + done + + for entry in "${standalone_updates[@]}"; do + IFS='|' read -r name image <<<"$entry" + reply="" + if read -r -t 60 -p "${TAB3}Pull new image for ${name} (${image})? (auto-no in 60s): " reply; then + echo "" + else + echo "" + fi + if [[ "${reply,,}" =~ ^(y|yes)$ ]]; then + msg_info "Pulling new image for $name" + if $STD docker pull "$image"; then + msg_ok "New image available for $name — recreate the container to apply the update" + else + msg_warn "Failed to pull image for $name" + fi + fi + done + fi + fi + msg_ok "Docker setup completed" }