From 20e48d9e5afea2554a1e4d702449b7a3c2673911 Mon Sep 17 00:00:00 2001 From: MickLesk <47820557+MickLesk@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:50:44 +0200 Subject: [PATCH] system.func: fix mongodb version comparison, guard apt purge against removing dependents --- lib/db.func | 20 ++++++++++++++++---- lib/runtime.func | 10 ++++++++-- lib/system.func | 44 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/lib/db.func b/lib/db.func index ee5347f..eb3dd44 100644 --- a/lib/db.func +++ b/lib/db.func @@ -65,7 +65,10 @@ setup_clickhouse() { if [[ -n "$CURRENT_VERSION" && "$CURRENT_VERSION" != "$CLICKHOUSE_VERSION" ]]; then msg_info "Upgrade ClickHouse from $CURRENT_VERSION to $CLICKHOUSE_VERSION" stop_all_services "clickhouse-server" - remove_old_tool_version "clickhouse" + remove_old_tool_version "clickhouse" || { + msg_error "Aborting ClickHouse upgrade: another package depends on it" + return 1 + } else msg_info "Setup ClickHouse $CLICKHOUSE_VERSION" fi @@ -288,7 +291,10 @@ EOF # Scenario 2b: Different version installed - clean upgrade if [[ -n "$CURRENT_VERSION" ]] && ! version_matches_spec "$CURRENT_VERSION" "$MARIADB_VERSION"; then msg_info "Upgrade MariaDB from $CURRENT_VERSION to $MARIADB_VERSION" - remove_old_tool_version "mariadb" + remove_old_tool_version "mariadb" || { + msg_error "Aborting MariaDB upgrade: another package depends on it" + return 1 + } fi # Scenario 3: Fresh install or version change with specific version @@ -818,7 +824,10 @@ setup_mongodb() { # Scenario 2: Different version installed - clean upgrade if [[ -n "$INSTALLED_VERSION" && "$INSTALLED_VERSION" != "$MONGO_VERSION" ]]; then msg_info "Upgrade MongoDB from $INSTALLED_VERSION to $MONGO_VERSION" - remove_old_tool_version "mongodb" + remove_old_tool_version "mongodb" || { + msg_error "Aborting MongoDB upgrade: another package depends on it (e.g. an app using it)" + return 1 + } else msg_info "Setup MongoDB $MONGO_VERSION" fi @@ -1008,7 +1017,10 @@ setup_mysql() { # Scenario 2: Different version installed - clean upgrade if [[ -n "$CURRENT_VERSION" ]] && ! version_matches_spec "$CURRENT_VERSION" "$MYSQL_VERSION"; then msg_info "Upgrade MySQL from $CURRENT_VERSION to $MYSQL_VERSION" - remove_old_tool_version "mysql" + remove_old_tool_version "mysql" || { + msg_error "Aborting MySQL upgrade: another package depends on it" + return 1 + } else msg_info "Setup MySQL $MYSQL_VERSION" fi diff --git a/lib/runtime.func b/lib/runtime.func index 6c2043e..b558385 100644 --- a/lib/runtime.func +++ b/lib/runtime.func @@ -875,7 +875,10 @@ setup_go() { # Scenario 2: Different version or not installed if [[ -n "$CURRENT_VERSION" && "$CURRENT_VERSION" != "$GO_VERSION" ]]; then msg_info "Upgrade Go from $CURRENT_VERSION to $GO_VERSION" - remove_old_tool_version "go" + remove_old_tool_version "go" || { + msg_error "Aborting Go upgrade: another package depends on it" + return 1 + } else msg_info "Setup Go $GO_VERSION" fi @@ -1253,7 +1256,10 @@ setup_nodejs() { if [[ -n "$CURRENT_NODE_VERSION" && "$CURRENT_NODE_VERSION" != "$NODE_VERSION" ]]; then msg_info "Upgrade Node.js from $CURRENT_NODE_VERSION to $NODE_VERSION" node_setup_ok_msg="Upgrade Node.js to $NODE_VERSION" - remove_old_tool_version "nodejs" + remove_old_tool_version "nodejs" || { + msg_error "Aborting Node.js upgrade: another package depends on it" + return 1 + } else msg_info "Setup Node.js $NODE_VERSION" node_setup_ok_msg="Setup Node.js $NODE_VERSION" diff --git a/lib/system.func b/lib/system.func index c03d113..143efee 100644 --- a/lib/system.func +++ b/lib/system.func @@ -605,7 +605,7 @@ is_tool_installed() { ;; mongodb | mongod) if command -v mongod >/dev/null 2>&1; then - installed_version=$(mongod --version 2>/dev/null | awk '/db version/{print $3}' | cut -d. -f1,2) + installed_version=$(mongod --version 2>/dev/null | awk '/db version/{print $3}' | sed 's/^v//' | cut -d. -f1,2) fi ;; node | nodejs) @@ -658,6 +658,39 @@ is_tool_installed() { return 0 # Installed and version matches (if specified) } +# ------------------------------------------------------------------------------ +# Checks whether purging the given package glob(s) would also remove packages +# outside those globs (i.e. reverse dependents, such as an app depending on +# mongodb-org-server). Uses `apt-get -s` (simulate) with the stable apt-get +# output format, not `apt`, whose output is explicitly unstable for scripting. +# Returns 0 (safe to purge) or 1 (unsafe - prints the unexpected packages). +# Usage: _purge_is_safe 'mongodb*' ['other-glob*' ...] +# ------------------------------------------------------------------------------ +_purge_is_safe() { + local -a globs=("$@") + local -a collateral=() + local pkg glob matched + local sim_out + sim_out=$(apt-get -s purge -y "${globs[@]}" 2>/dev/null) || return 0 + while IFS= read -r pkg; do + [[ -z "$pkg" ]] && continue + matched=0 + for glob in "${globs[@]}"; do + # shellcheck disable=SC2053 + [[ "$pkg" == $glob ]] && { + matched=1 + break + } + done + ((matched)) || collateral+=("$pkg") + done < <(awk '/^(Remv|Purg) /{print $2}' <<<"$sim_out") + if ((${#collateral[@]} > 0)); then + msg_warn "Refusing purge of '${globs[*]}': would also remove ${collateral[*]}" + return 1 + fi + return 0 +} + # ------------------------------------------------------------------------------ # Remove old tool version completely (purge + cleanup repos) # Usage: remove_old_tool_version "mariadb" "repository-name" @@ -669,11 +702,13 @@ remove_old_tool_version() { case "$tool_name" in mariadb) stop_all_services "mariadb" + _purge_is_safe 'mariadb*' || return 1 $STD apt purge -y 'mariadb*' >/dev/null 2>&1 || true cleanup_tool_keyrings "mariadb" ;; mysql) stop_all_services "mysql" + _purge_is_safe 'mysql*' || return 1 $STD apt purge -y 'mysql*' >/dev/null 2>&1 || true # Keep data directory for safety (remove manually if needed) # rm -rf /var/lib/mysql 2>/dev/null || true @@ -681,12 +716,14 @@ remove_old_tool_version() { ;; mongodb) stop_all_services "mongod" + _purge_is_safe 'mongodb*' || return 1 $STD apt purge -y 'mongodb*' >/dev/null 2>&1 || true # Keep data directory for safety (remove manually if needed) # rm -rf /var/lib/mongodb 2>/dev/null || true cleanup_tool_keyrings "mongodb" ;; node | nodejs) + _purge_is_safe nodejs npm || return 1 $STD apt purge -y nodejs npm >/dev/null 2>&1 || true # Clean up npm global modules if command -v npm >/dev/null 2>&1; then @@ -699,23 +736,27 @@ remove_old_tool_version() { ;; php) stop_all_services "php.*-fpm" + _purge_is_safe 'php*' || return 1 $STD apt purge -y 'php*' >/dev/null 2>&1 || true rm -rf /etc/php 2>/dev/null || true cleanup_tool_keyrings "deb.sury.org-php" "php" ;; postgresql) stop_all_services "postgresql" + _purge_is_safe 'postgresql*' || return 1 $STD apt purge -y 'postgresql*' >/dev/null 2>&1 || true # Keep data directory for safety (can be removed manually if needed) # rm -rf /var/lib/postgresql 2>/dev/null || true cleanup_tool_keyrings "postgresql" "pgdg" ;; java) + _purge_is_safe 'temurin*' 'adoptium*' 'openjdk*' || return 1 $STD apt purge -y 'temurin*' 'adoptium*' 'openjdk*' >/dev/null 2>&1 || true cleanup_tool_keyrings "adoptium" ;; ruby) cleanup_legacy_install "ruby" + _purge_is_safe 'ruby*' || return 1 $STD apt purge -y 'ruby*' >/dev/null 2>&1 || true ;; rust) @@ -727,6 +768,7 @@ remove_old_tool_version() { ;; clickhouse) stop_all_services "clickhouse-server" + _purge_is_safe 'clickhouse*' || return 1 $STD apt purge -y 'clickhouse*' >/dev/null 2>&1 || true # Keep data directory for safety (remove manually if needed) # rm -rf /var/lib/clickhouse 2>/dev/null || true