From 699a3b3a8c8e1b7c90835c5ebdf65159ea7dceb7 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Wed, 17 Dec 2025 10:04:54 +0100 Subject: [PATCH] merge from VE --- misc/tools.func | 333 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 239 insertions(+), 94 deletions(-) diff --git a/misc/tools.func b/misc/tools.func index 716a74d8a..a122317f3 100644 --- a/misc/tools.func +++ b/misc/tools.func @@ -72,15 +72,19 @@ stop_all_services() { local service_patterns=("$@") for pattern in "${service_patterns[@]}"; do - # Find all matching services - systemctl list-units --type=service --all 2>/dev/null | - grep -oE "${pattern}[^ ]*\.service" | - sort -u | + # Find all matching services (grep || true to handle no matches) + local services + services=$(systemctl list-units --type=service --all 2>/dev/null | + grep -oE "${pattern}[^ ]*\.service" 2>/dev/null | sort -u) || true + + if [[ -n "$services" ]]; then while read -r service; do $STD systemctl stop "$service" 2>/dev/null || true $STD systemctl disable "$service" 2>/dev/null || true - done + done <<<"$services" + fi done + } # ------------------------------------------------------------------------------ @@ -188,6 +192,8 @@ install_packages_with_retry() { if [[ $retry -le $max_retries ]]; then msg_warn "Package installation failed, retrying ($retry/$max_retries)..." sleep 2 + # Fix any interrupted dpkg operations before retry + $STD dpkg --configure -a 2>/dev/null || true $STD apt update 2>/dev/null || true fi done @@ -213,6 +219,8 @@ upgrade_packages_with_retry() { if [[ $retry -le $max_retries ]]; then msg_warn "Package upgrade failed, retrying ($retry/$max_retries)..." sleep 2 + # Fix any interrupted dpkg operations before retry + $STD dpkg --configure -a 2>/dev/null || true $STD apt update 2>/dev/null || true fi done @@ -1178,6 +1186,12 @@ cleanup_orphaned_sources() { # This should be called at the start of any setup function # ------------------------------------------------------------------------------ ensure_apt_working() { + # Fix interrupted dpkg operations first + # This can happen if a previous installation was interrupted (e.g., by script error) + if [[ -f /var/lib/dpkg/lock-frontend ]] || dpkg --audit 2>&1 | grep -q "interrupted"; then + $STD dpkg --configure -a 2>/dev/null || true + fi + # Clean up orphaned sources first cleanup_orphaned_sources @@ -1208,6 +1222,7 @@ setup_deb822_repo() { local suite="$4" local component="${5:-main}" local architectures="${6-}" # optional + local enabled="${7-}" # optional: "true" or "false" # Validate required parameters if [[ -z "$name" || -z "$gpg_url" || -z "$repo_url" || -z "$suite" ]]; then @@ -1235,9 +1250,13 @@ setup_deb822_repo() { echo "Types: deb" echo "URIs: $repo_url" echo "Suites: $suite" - echo "Components: $component" + # Flat repositories (suite="./" or absolute path) must not have Components + if [[ "$suite" != "./" && -n "$component" ]]; then + echo "Components: $component" + fi [[ -n "$architectures" ]] && echo "Architectures: $architectures" echo "Signed-By: /etc/apt/keyrings/${name}.gpg" + [[ -n "$enabled" ]] && echo "Enabled: $enabled" } >/etc/apt/sources.list.d/${name}.sources $STD apt update @@ -1439,15 +1458,32 @@ check_for_gh_release() { ensure_dependencies jq - # Fetch releases and exclude drafts/prereleases - local releases_json - releases_json=$(curl -fsSL --max-time 20 \ - -H 'Accept: application/vnd.github+json' \ - -H 'X-GitHub-Api-Version: 2022-11-28' \ - "https://api.github.com/repos/${source}/releases") || { - msg_error "Unable to fetch releases for ${app}" - return 1 - } + # Try /latest endpoint for non-pinned versions (most efficient) + local releases_json="" + + if [[ -z "$pinned_version_in" ]]; then + releases_json=$(curl -fsSL --max-time 20 \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "https://api.github.com/repos/${source}/releases/latest" 2>/dev/null) + + if [[ $? -eq 0 ]] && [[ -n "$releases_json" ]]; then + # Wrap single release in array for consistent processing + releases_json="[$releases_json]" + fi + fi + + # If no releases yet (pinned version OR /latest failed), fetch up to 100 + if [[ -z "$releases_json" ]]; then + # Fetch releases and exclude drafts/prereleases + releases_json=$(curl -fsSL --max-time 20 \ + -H 'Accept: application/vnd.github+json' \ + -H 'X-GitHub-Api-Version: 2022-11-28' \ + "https://api.github.com/repos/${source}/releases?per_page=100") || { + msg_error "Unable to fetch releases for ${app}" + return 1 + } + fi mapfile -t raw_tags < <(jq -r '.[] | select(.draft==false and .prerelease==false) | .tag_name' <<<"$releases_json") if ((${#raw_tags[@]} == 0)); then @@ -1721,12 +1757,13 @@ function fetch_and_deploy_gh_release() { ### Tarball Mode ### if [[ "$mode" == "tarball" || "$mode" == "source" ]]; then - url=$(echo "$json" | jq -r '.tarball_url // empty') - [[ -z "$url" ]] && url="https://github.com/$repo/archive/refs/tags/v$version.tar.gz" + # GitHub API's tarball_url/zipball_url can return HTTP 300 Multiple Choices + # when a branch and tag share the same name. Use explicit refs/tags/ URL instead. + local direct_tarball_url="https://github.com/$repo/archive/refs/tags/$tag_name.tar.gz" filename="${app_lc}-${version}.tar.gz" - curl $download_timeout -fsSL -o "$tmpdir/$filename" "$url" || { - msg_error "Download failed: $url" + curl $download_timeout -fsSL -o "$tmpdir/$filename" "$direct_tarball_url" || { + msg_error "Download failed: $direct_tarball_url" rm -rf "$tmpdir" return 1 } @@ -2548,93 +2585,200 @@ function setup_hwaccel() { fi # Detect GPU vendor (Intel, AMD, NVIDIA) - local gpu_vendor - gpu_vendor=$(lspci 2>/dev/null | grep -Ei 'vga|3d|display' | grep -Eo 'Intel|AMD|NVIDIA' | head -n1 || echo "") + local gpu_vendor gpu_info + gpu_info=$(lspci 2>/dev/null | grep -Ei 'vga|3d|display' || echo "") + gpu_vendor=$(echo "$gpu_info" | grep -Eo 'Intel|AMD|NVIDIA' | head -n1 || echo "") # Detect CPU vendor (relevant for AMD APUs) local cpu_vendor cpu_vendor=$(lscpu 2>/dev/null | grep -i 'Vendor ID' | awk '{print $3}' || echo "") if [[ -z "$gpu_vendor" && -z "$cpu_vendor" ]]; then - msg_error "No GPU or CPU vendor detected (missing lspci/lscpu output)" - return 1 + msg_warn "No GPU or CPU vendor detected - skipping hardware acceleration setup" + msg_ok "Setup Hardware Acceleration (skipped - no GPU detected)" + return 0 fi # Detect OS with fallbacks - local os_id os_codename - os_id=$(grep -oP '(?<=^ID=).+' /etc/os-release 2>/dev/null | tr -d '"' || grep '^ID=' /etc/os-release 2>/dev/null | cut -d'=' -f2 | tr -d '"' || echo "debian") - os_codename=$(grep -oP '(?<=^VERSION_CODENAME=).+' /etc/os-release 2>/dev/null | tr -d '"' || grep '^VERSION_CODENAME=' /etc/os-release 2>/dev/null | cut -d'=' -f2 | tr -d '"' || echo "unknown") + local os_id os_codename os_version + os_id=$(grep -oP '(?<=^ID=).+' /etc/os-release 2>/dev/null | tr -d '"' || echo "debian") + os_codename=$(grep -oP '(?<=^VERSION_CODENAME=).+' /etc/os-release 2>/dev/null | tr -d '"' || echo "unknown") + os_version=$(grep -oP '(?<=^VERSION_ID=).+' /etc/os-release 2>/dev/null | tr -d '"' || echo "") - # Validate os_id - if [[ -z "$os_id" ]]; then - os_id="debian" - fi + [[ -z "$os_id" ]] && os_id="debian" - # Determine if we are on a VM or LXC + # Determine if we are in a privileged LXC container local in_ct="${CTTYPE:-0}" case "$gpu_vendor" in Intel) - if [[ "$os_id" == "ubuntu" ]]; then - $STD apt -y install intel-opencl-icd || { - msg_error "Failed to install intel-opencl-icd" - return 1 - } - else - # For Debian: fetch Intel GPU drivers from GitHub - fetch_and_deploy_gh_release "" "intel/intel-graphics-compiler" "binary" "latest" "" "intel-igc-core-2_*_amd64.deb" || { - msg_warn "Failed to deploy Intel IGC core 2" - } - fetch_and_deploy_gh_release "" "intel/intel-graphics-compiler" "binary" "latest" "" "intel-igc-opencl-2_*_amd64.deb" || { - msg_warn "Failed to deploy Intel IGC OpenCL 2" - } - fetch_and_deploy_gh_release "" "intel/compute-runtime" "binary" "latest" "" "libigdgmm12_*_amd64.deb" || { - msg_warn "Failed to deploy Intel GDGMM12" - } - fetch_and_deploy_gh_release "" "intel/compute-runtime" "binary" "latest" "" "intel-opencl-icd_*_amd64.deb" || { - msg_warn "Failed to deploy Intel OpenCL ICD" - } + # Detect Intel GPU generation for driver selection + # Gen 9+ (Skylake and newer) benefit from non-free drivers + local intel_gen="" + local needs_nonfree=false + + # Check for specific Intel GPU models that need non-free drivers + if echo "$gpu_info" | grep -Ei 'HD Graphics [56][0-9]{2}|UHD Graphics|Iris|Arc|DG[12]' &>/dev/null; then + needs_nonfree=true + intel_gen="gen9+" fi - $STD apt -y install va-driver-all ocl-icd-libopencl1 vainfo intel-gpu-tools || { - msg_error "Failed to install Intel GPU dependencies" - return 1 - } + if [[ "$os_id" == "ubuntu" ]]; then + # Ubuntu: Use packages from Ubuntu repos + $STD apt -y install \ + va-driver-all \ + ocl-icd-libopencl1 \ + intel-opencl-icd \ + vainfo \ + intel-gpu-tools || { + msg_error "Failed to install Intel GPU dependencies" + return 1 + } + # Try to install intel-media-va-driver for newer GPUs + $STD apt -y install intel-media-va-driver 2>/dev/null || true + + elif [[ "$os_id" == "debian" ]]; then + # Debian: Check version and install appropriate drivers + if [[ "$needs_nonfree" == true ]]; then + # Add non-free repo for intel-media-va-driver-non-free + if [[ "$os_codename" == "bookworm" ]]; then + # Debian 12 Bookworm + if [[ ! -f /etc/apt/sources.list.d/non-free.list && ! -f /etc/apt/sources.list.d/non-free.sources ]]; then + cat </etc/apt/sources.list.d/non-free.sources +Types: deb +URIs: http://deb.debian.org/debian +Suites: bookworm bookworm-updates +Components: non-free non-free-firmware +EOF + $STD apt update + fi + $STD apt -y install \ + intel-media-va-driver-non-free \ + ocl-icd-libopencl1 \ + intel-opencl-icd \ + vainfo \ + intel-gpu-tools || { + msg_warn "Non-free driver install failed, falling back to open drivers" + needs_nonfree=false + } + + elif [[ "$os_codename" == "trixie" || "$os_codename" == "sid" ]]; then + # Debian 13 Trixie / Sid + if [[ ! -f /etc/apt/sources.list.d/non-free.sources ]]; then + cat <<'EOF' >/etc/apt/sources.list.d/non-free.sources +Types: deb +URIs: http://deb.debian.org/debian +Suites: trixie trixie-updates +Components: non-free non-free-firmware + +Types: deb +URIs: http://deb.debian.org/debian-security +Suites: trixie-security +Components: non-free non-free-firmware +EOF + $STD apt update + fi + $STD apt -y install \ + intel-media-va-driver-non-free \ + ocl-icd-libopencl1 \ + mesa-opencl-icd \ + mesa-va-drivers \ + libvpl2 \ + vainfo \ + intel-gpu-tools 2>/dev/null || { + msg_warn "Non-free driver install failed, falling back to open drivers" + needs_nonfree=false + } + fi + fi + + # Fallback to open drivers or older Intel GPUs + if [[ "$needs_nonfree" == false ]]; then + # Fetch latest Intel drivers from GitHub for Debian + fetch_and_deploy_gh_release "" "intel/intel-graphics-compiler" "binary" "latest" "" "intel-igc-core-2_*_amd64.deb" || { + msg_warn "Failed to deploy Intel IGC core 2" + } + fetch_and_deploy_gh_release "" "intel/intel-graphics-compiler" "binary" "latest" "" "intel-igc-opencl-2_*_amd64.deb" || { + msg_warn "Failed to deploy Intel IGC OpenCL 2" + } + fetch_and_deploy_gh_release "" "intel/compute-runtime" "binary" "latest" "" "libigdgmm12_*_amd64.deb" || { + msg_warn "Failed to deploy Intel GDGMM12" + } + fetch_and_deploy_gh_release "" "intel/compute-runtime" "binary" "latest" "" "intel-opencl-icd_*_amd64.deb" || { + msg_warn "Failed to deploy Intel OpenCL ICD" + } + + $STD apt -y install \ + va-driver-all \ + ocl-icd-libopencl1 \ + mesa-opencl-icd \ + mesa-va-drivers \ + vainfo \ + intel-gpu-tools || { + msg_error "Failed to install Intel GPU dependencies" + return 1 + } + fi + fi ;; + AMD) - $STD apt -y install mesa-va-drivers mesa-vdpau-drivers mesa-opencl-icd vainfo clinfo || { + $STD apt -y install \ + mesa-va-drivers \ + mesa-vdpau-drivers \ + mesa-opencl-icd \ + ocl-icd-libopencl1 \ + vainfo \ + clinfo 2>/dev/null || { msg_error "Failed to install AMD GPU dependencies" return 1 } - # For AMD CPUs without discrete GPU (APUs) - if [[ "$cpu_vendor" == "AuthenticAMD" && -n "$gpu_vendor" ]]; then - $STD apt -y install libdrm-amdgpu1 firmware-amd-graphics || true + # AMD firmware for better GPU support + if [[ "$os_id" == "debian" ]]; then + $STD apt -y install firmware-amd-graphics 2>/dev/null || true fi + $STD apt -y install libdrm-amdgpu1 2>/dev/null || true ;; + NVIDIA) - # NVIDIA needs manual driver setup - skip for now - msg_info "NVIDIA GPU detected - manual driver setup required" + # NVIDIA needs manual driver setup or passthrough from host + msg_warn "NVIDIA GPU detected - driver must be installed manually or passed through from host" + # Install basic VA-API support for potential hybrid setups + $STD apt -y install va-driver-all vainfo 2>/dev/null || true ;; + *) - # If no discrete GPU, but AMD CPU (e.g., Ryzen APU) + # No discrete GPU detected - check for AMD APU if [[ "$cpu_vendor" == "AuthenticAMD" ]]; then - $STD apt -y install mesa-opencl-icd ocl-icd-libopencl1 clinfo || { - msg_error "Failed to install Mesa OpenCL stack" - return 1 - } + $STD apt -y install \ + mesa-va-drivers \ + mesa-vdpau-drivers \ + mesa-opencl-icd \ + ocl-icd-libopencl1 \ + vainfo 2>/dev/null || true else - msg_warn "No supported GPU vendor detected - skipping GPU acceleration" + msg_warn "No supported GPU vendor detected - skipping GPU driver installation" fi ;; esac - if [[ -d /dev/dri ]]; then + # Set permissions for /dev/dri (only in privileged containers and if /dev/dri exists) + if [[ "$in_ct" == "0" && -d /dev/dri ]]; then chgrp video /dev/dri 2>/dev/null || true chmod 755 /dev/dri 2>/dev/null || true chmod 660 /dev/dri/* 2>/dev/null || true - $STD adduser "$(id -u -n)" video - $STD adduser "$(id -u -n)" render + $STD adduser "$(id -u -n)" video 2>/dev/null || true + $STD adduser "$(id -u -n)" render 2>/dev/null || true + + # Sync GID for video/render groups between host and container + local host_video_gid host_render_gid + host_video_gid=$(getent group video | cut -d: -f3) + host_render_gid=$(getent group render | cut -d: -f3) + if [[ -n "$host_video_gid" && -n "$host_render_gid" ]]; then + sed -i "s/^video:x:[0-9]*:/video:x:$host_video_gid:/" /etc/group 2>/dev/null || true + sed -i "s/^render:x:[0-9]*:/render:x:$host_render_gid:/" /etc/group 2>/dev/null || true + fi fi cache_installed_version "hwaccel" "1.0" @@ -2780,12 +2924,19 @@ function setup_java() { INSTALLED_VERSION=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | grep -oP 'temurin-\K[0-9]+' | head -n1 || echo "") fi - # Validate INSTALLED_VERSION is not empty if matched + # Validate INSTALLED_VERSION is not empty if JDK package found local JDK_COUNT=0 JDK_COUNT=$(dpkg -l 2>/dev/null | grep -c "temurin-.*-jdk" || true) if [[ -z "$INSTALLED_VERSION" && "${JDK_COUNT:-0}" -gt 0 ]]; then - msg_warn "Found Temurin JDK but cannot determine version" - INSTALLED_VERSION="0" + msg_warn "Found Temurin JDK but cannot determine version - attempting reinstall" + # Try to get actual package name for purge + local OLD_PACKAGE + OLD_PACKAGE=$(dpkg -l 2>/dev/null | awk '/temurin-.*-jdk/{print $2}' | head -n1 || echo "") + if [[ -n "$OLD_PACKAGE" ]]; then + msg_info "Removing existing package: $OLD_PACKAGE" + $STD apt purge -y "$OLD_PACKAGE" || true + fi + INSTALLED_VERSION="" # Reset to trigger fresh install fi # Scenario 1: Already at correct version @@ -3234,7 +3385,6 @@ function setup_mongodb() { return 1 } - # Verify MongoDB was installed correctly if ! command -v mongod >/dev/null 2>&1; then msg_error "MongoDB binary not found after installation" return 1 @@ -3410,12 +3560,12 @@ EOF # - Optionally installs or updates global npm modules # # Variables: -# NODE_VERSION - Node.js version to install (default: 22) +# NODE_VERSION - Node.js version to install (default: 24 LTS) # NODE_MODULE - Comma-separated list of global modules (e.g. "yarn,@vue/cli@5.0.0") # ------------------------------------------------------------------------------ function setup_nodejs() { - local NODE_VERSION="${NODE_VERSION:-22}" + local NODE_VERSION="${NODE_VERSION:-24}" local NODE_MODULE="${NODE_MODULE:-}" # ALWAYS clean up legacy installations first (nvm, etc.) to prevent conflicts @@ -3477,14 +3627,11 @@ function setup_nodejs() { return 1 } - # CRITICAL: Force APT cache refresh AFTER repository setup - # This ensures NodeSource is the only nodejs source in APT cache + # Force APT cache refresh after repository setup $STD apt update - # Install dependencies (NodeSource is now the only nodejs source) ensure_dependencies curl ca-certificates gnupg - # Install Node.js from NodeSource install_packages_with_retry "nodejs" || { msg_error "Failed to install Node.js ${NODE_VERSION} from NodeSource" return 1 @@ -3635,7 +3782,7 @@ function setup_php() { local CURRENT_PHP="" CURRENT_PHP=$(is_tool_installed "php" 2>/dev/null) || true - # CRITICAL: If wrong version is installed, remove it FIRST before any pinning + # Remove conflicting PHP version before pinning if [[ -n "$CURRENT_PHP" && "$CURRENT_PHP" != "$PHP_VERSION" ]]; then msg_info "Removing conflicting PHP ${CURRENT_PHP} (need ${PHP_VERSION})" stop_all_services "php.*-fpm" @@ -3782,7 +3929,6 @@ EOF local INSTALLED_VERSION=$(php -v 2>/dev/null | awk '/^PHP/{print $2}' | cut -d. -f1,2) - # Critical: if major.minor doesn't match, fail and cleanup if [[ "$INSTALLED_VERSION" != "$PHP_VERSION" ]]; then msg_error "PHP version mismatch: requested ${PHP_VERSION} but got ${INSTALLED_VERSION}" msg_error "This indicates a critical package installation issue" @@ -3862,11 +4008,14 @@ function setup_postgresql() { local SUITE case "$DISTRO_CODENAME" in trixie | forky | sid) + if verify_repo_available "https://apt.postgresql.org/pub/repos/apt" "trixie-pgdg"; then SUITE="trixie-pgdg" + else SUITE="bookworm-pgdg" fi + ;; *) SUITE=$(get_fallback_suite "$DISTRO_ID" "$DISTRO_CODENAME" "https://apt.postgresql.org/pub/repos/apt") @@ -4387,7 +4536,7 @@ function setup_rust() { # Get currently installed version local CURRENT_VERSION="" if command -v rustc &>/dev/null; then - CURRENT_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}') + CURRENT_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true fi # Scenario 1: Rustup not installed - fresh install @@ -4406,7 +4555,8 @@ function setup_rust() { return 1 fi - local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}') + local RUST_VERSION + RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true if [[ -z "$RUST_VERSION" ]]; then msg_error "Failed to determine Rust version" return 1 @@ -4437,7 +4587,8 @@ function setup_rust() { # Ensure PATH is updated for current shell session export PATH="$CARGO_BIN:$PATH" - local RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}') + local RUST_VERSION + RUST_VERSION=$(rustc --version 2>/dev/null | awk '{print $2}' 2>/dev/null) || true if [[ -z "$RUST_VERSION" ]]; then msg_error "Failed to determine Rust version after update" return 1 @@ -4647,6 +4798,7 @@ function setup_uv() { if [[ -d /usr/share/zsh/site-functions ]]; then $STD uv generate-shell-completion zsh >/usr/share/zsh/site-functions/_uv 2>/dev/null || true fi + # Optional: Install specific Python version if requested if [[ -n "${PYTHON_VERSION:-}" ]]; then msg_info "Installing Python $PYTHON_VERSION via uv" @@ -4805,6 +4957,7 @@ function setup_docker() { # Cleanup old repository configurations if [ -f /etc/apt/sources.list.d/docker.list ]; then + msg_info "Migrating from old Docker repository format" rm -f /etc/apt/sources.list.d/docker.list rm -f /etc/apt/keyrings/docker.asc fi @@ -4818,7 +4971,6 @@ function setup_docker() { "$(get_os_info codename)" \ "stable" \ "$(dpkg --print-architecture)" - msg_ok "Set up Docker Repository" # Install or upgrade Docker if [ "$docker_installed" = true ]; then @@ -4826,8 +4978,7 @@ function setup_docker() { DOCKER_LATEST_VERSION=$(apt-cache policy docker-ce | grep Candidate | awk '{print $2}' | cut -d':' -f2 | cut -d'-' -f1) if [ "$DOCKER_CURRENT_VERSION" != "$DOCKER_LATEST_VERSION" ]; then - msg_ok "Docker update available ($DOCKER_CURRENT_VERSION → $DOCKER_LATEST_VERSION)" - msg_info "Updating Docker" + msg_info "Updating Docker $DOCKER_CURRENT_VERSION → $DOCKER_LATEST_VERSION" $STD apt install -y --only-upgrade \ docker-ce \ docker-ce-cli \ @@ -4873,8 +5024,7 @@ EOF PORTAINER_LATEST=$(curl -fsSL https://registry.hub.docker.com/v2/repositories/portainer/portainer-ce/tags?page_size=100 | grep -oP '"name":"\K[0-9]+\.[0-9]+\.[0-9]+"' | head -1 | tr -d '"') if [ "$PORTAINER_CURRENT" != "$PORTAINER_LATEST" ]; then - msg_ok "Portainer update available ($PORTAINER_CURRENT → $PORTAINER_LATEST)" - read -r -p "${TAB3}Update Portainer? " prompt + read -r -p "${TAB3}Update Portainer $PORTAINER_CURRENT → $PORTAINER_LATEST? " prompt if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then msg_info "Updating Portainer" docker stop portainer @@ -4889,8 +5039,6 @@ EOF -v portainer_data:/data \ portainer/portainer-ce:latest msg_ok "Updated Portainer to $PORTAINER_LATEST" - else - msg_ok "Skipped Portainer update" fi else msg_ok "Portainer is up-to-date ($PORTAINER_CURRENT)" @@ -4938,7 +5086,6 @@ EOF done < <(docker ps --format '{{.Names}} {{.Image}}') if [ ${#containers_with_updates[@]} -gt 0 ]; then - msg_ok "Found ${#containers_with_updates[@]} container(s) with updates" echo "" echo "${TAB3}Container updates available:" for info in "${container_info[@]}"; do @@ -4967,8 +5114,6 @@ EOF msg_ok "Stopped and removed $container (please recreate with updated image)" fi done - else - msg_ok "Skipped container updates" fi else msg_ok "All containers are up-to-date"