From 4f651ffed57882671a2103328090364972dd3bb3 Mon Sep 17 00:00:00 2001 From: pbr Date: Wed, 30 Sep 2026 22:55:05 +0200 Subject: [PATCH 1/2] fix(post-install): divert gzip instead of replacing it for pigz A gzip package update overwrote the pigz wrapper, and uninstall could restore a stale gzip.original over a newer binary. Use dpkg-divert (--no-rename, gzip is Essential), migrate existing installs, and also divert the /usr path on bookworm so a PVE 8 -> 9 upgrade keeps working. --- .../post_install/customizable_post_install.sh | 38 +++++++++++++------ scripts/post_install/uninstall-tools.sh | 23 ++++++++++- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/scripts/post_install/customizable_post_install.sh b/scripts/post_install/customizable_post_install.sh index 24c44e5e..cd79bc20 100644 --- a/scripts/post_install/customizable_post_install.sh +++ b/scripts/post_install/customizable_post_install.sh @@ -1682,20 +1682,34 @@ EOF msg_ok "$(translate "pigz wrapper script created")" fi - # Replace gzip with pigz wrapper + # Replace gzip with pigz wrapper. gzip is diverted so package updates + # land in gzip.distrib instead of overwriting the wrapper. msg_info "$(translate "Replacing gzip with pigz wrapper...")" - if [ ! -f /bin/gzip.original ]; then - mv -f /bin/gzip /bin/gzip.original && \ - pmx_write_file /bin/gzip < /bin/pigzwrapper && \ - chmod +x /bin/gzip - msg_ok "$(translate "gzip replaced with pigz wrapper successfully")" - elif ! cmp -s /bin/gzip /bin/pigzwrapper; then - pmx_write_file /bin/gzip < /bin/pigzwrapper && \ - chmod +x /bin/gzip - msg_ok "$(translate "gzip replaced with pigz wrapper successfully")" - else - msg_ok "$(translate "gzip replaced with pigz wrapper successfully")" + local gz src + gz=$(dpkg -L gzip 2>/dev/null | grep -m1 -xE '(/usr)?/bin/gzip') + gz=${gz:-/usr/bin/gzip} + if [ -z "$(dpkg-divert --listpackage "$gz")" ]; then + # Older versions swapped gzip by hand and kept it as gzip.original. + # Use that as the real binary, unless a gzip update already put a + # newer one back. + src="$gz" + [ -f /bin/gzip.original ] && [ "$(head -c 2 "$gz")" = "#!" ] && src=/bin/gzip.original + pmx_write_file "$gz.distrib" < "$src" && chmod 755 "$gz.distrib" + pmx_remove_file /bin/gzip.original + pmx_record_execution "Divert gzip" "dpkg-divert --local --no-rename --divert $gz.distrib --add $gz" + dpkg-divert --local --no-rename --divert "$gz.distrib" --add "$gz" >/dev/null + # Bookworm ships /bin/gzip, trixie /usr/bin/gzip. Divert the /usr + # path too so a PVE 8 -> 9 upgrade doesn't overwrite the wrapper. + # It needs its own target: /bin/gzip.distrib is the same file, and + # dpkg deletes it when it drops the old /bin/gzip (DEP17). + [ "$gz" = /bin/gzip ] && dpkg-divert --local --no-rename --divert /usr/bin/gzip.distrib-usr --add /usr/bin/gzip >/dev/null fi + # Left over from the bookworm diversion after upgrading to trixie. + [ "$gz" = /usr/bin/gzip ] && [ -n "$(dpkg-divert --listpackage /bin/gzip)" ] && dpkg-divert --local --no-rename --remove /bin/gzip >/dev/null + if ! cmp -s "$gz" /bin/pigzwrapper; then + pmx_write_file "$gz" < /bin/pigzwrapper + fi + msg_ok "$(translate "gzip replaced with pigz wrapper successfully")" msg_success "$(translate "pigz configuration completed")" register_tool "pigz" true "$FUNC_VERSION" diff --git a/scripts/post_install/uninstall-tools.sh b/scripts/post_install/uninstall-tools.sh index 3605c7a7..289b2dcd 100644 --- a/scripts/post_install/uninstall-tools.sh +++ b/scripts/post_install/uninstall-tools.sh @@ -1068,11 +1068,30 @@ uninstall_pigz() { local FUNC_VERSION="1.0" pmx_journal_context "uninstall_pigz" "$FUNC_VERSION" msg_info2 "$(translate 'Reverting pigz wrapper...')" - if [[ -f /bin/gzip.original ]]; then - pmx_write_file /bin/gzip < /bin/gzip.original + local gz p real + gz=$(dpkg -L gzip 2>/dev/null | grep -m1 -xE '(/usr)?/bin/gzip') + gz=${gz:-/usr/bin/gzip} + real=$(dpkg-divert --truename "$gz") + if [[ "$real" != "$gz" ]]; then + pmx_write_file "$gz" < "$real" + pmx_record_execution "Remove gzip diversion" "dpkg-divert --local --no-rename --remove $gz" + dpkg-divert --local --no-rename --remove "$gz" >/dev/null + pmx_remove_file "$real" + msg_ok "$(translate 'Restored original /bin/gzip')" + elif [[ -f /bin/gzip.original ]]; then + # Older, non-diverted install. A gzip update may already have put a + # newer binary back; don't overwrite that with the stale copy. + [[ "$(head -c 2 "$gz")" == "#!" ]] && pmx_write_file "$gz" < /bin/gzip.original pmx_remove_file /bin/gzip.original msg_ok "$(translate 'Restored original /bin/gzip')" fi + # Second diversion added on bookworm (see configure_pigz). + for p in /bin/gzip /usr/bin/gzip; do + real=$(dpkg-divert --truename "$p") + [[ "$real" == "$p" ]] && continue + dpkg-divert --local --no-rename --remove "$p" >/dev/null + rm -f "$real" + done pmx_remove_file /bin/pigzwrapper pmx_edit_file /etc/vzdump.conf 's/^pigz: 1/#pigz: 1/' 2>/dev/null || true pmx_record_execution "Purge pigz package" "apt-get purge -y pigz" From 07f88659ea5797b812a8c0f5a1250d366d9cd378 Mon Sep 17 00:00:00 2001 From: pbr Date: Thu, 1 Oct 2026 19:38:06 +0200 Subject: [PATCH 2/2] fix(post-install): verify gzip before diverting or restoring it Only use a file as the real gzip if it is a working binary, otherwise reinstall the gzip package; bail out without touching gzip if that fails. Check the .distrib and wrapper writes, and never purge pigz while gzip still depends on it. --- .../post_install/customizable_post_install.sh | 40 +++++++++++++++---- scripts/post_install/uninstall-tools.sh | 24 ++++++++++- 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/scripts/post_install/customizable_post_install.sh b/scripts/post_install/customizable_post_install.sh index cd79bc20..a8da7e26 100644 --- a/scripts/post_install/customizable_post_install.sh +++ b/scripts/post_install/customizable_post_install.sh @@ -1633,6 +1633,19 @@ disable_rpc() { +# True if $1 is a working gzip binary rather than the pigz wrapper script. +gzip_is_binary() { + [ -f "$1" ] && [ "$(head -c 2 "$1")" != "#!" ] && "$1" --version >/dev/null 2>&1 +} + +# Makes sure $1 is a working gzip, reinstalling the package if it isn't. +ensure_gzip_binary() { + gzip_is_binary "$1" && return 0 + pmx_record_execution "Reinstall gzip" "apt-get install --reinstall -y gzip" + apt-get install --reinstall -y gzip >/dev/null 2>&1 + gzip_is_binary "$1" +} + configure_pigz() { local FUNC_VERSION="1.0" pmx_journal_context "configure_pigz" "$FUNC_VERSION" @@ -1685,16 +1698,23 @@ EOF # Replace gzip with pigz wrapper. gzip is diverted so package updates # land in gzip.distrib instead of overwriting the wrapper. msg_info "$(translate "Replacing gzip with pigz wrapper...")" - local gz src + local gz src real gz=$(dpkg -L gzip 2>/dev/null | grep -m1 -xE '(/usr)?/bin/gzip') gz=${gz:-/usr/bin/gzip} if [ -z "$(dpkg-divert --listpackage "$gz")" ]; then # Older versions swapped gzip by hand and kept it as gzip.original. - # Use that as the real binary, unless a gzip update already put a - # newer one back. + # Prefer whichever is a real gzip; reinstall if neither is. src="$gz" - [ -f /bin/gzip.original ] && [ "$(head -c 2 "$gz")" = "#!" ] && src=/bin/gzip.original - pmx_write_file "$gz.distrib" < "$src" && chmod 755 "$gz.distrib" + gzip_is_binary "$src" || src=/bin/gzip.original + if ! gzip_is_binary "$src"; then + ensure_gzip_binary "$gz" || { msg_error "$(translate "gzip could not be verified, leaving it unchanged")"; return 1; } + src="$gz" + fi + if ! pmx_write_file "$gz.distrib" < "$src" || ! chmod 755 "$gz.distrib" || ! gzip_is_binary "$gz.distrib"; then + rm -f "$gz.distrib" + msg_error "$(translate "gzip could not be verified, leaving it unchanged")" + return 1 + fi pmx_remove_file /bin/gzip.original pmx_record_execution "Divert gzip" "dpkg-divert --local --no-rename --divert $gz.distrib --add $gz" dpkg-divert --local --no-rename --divert "$gz.distrib" --add "$gz" >/dev/null @@ -1706,8 +1726,14 @@ EOF fi # Left over from the bookworm diversion after upgrading to trixie. [ "$gz" = /usr/bin/gzip ] && [ -n "$(dpkg-divert --listpackage /bin/gzip)" ] && dpkg-divert --local --no-rename --remove /bin/gzip >/dev/null - if ! cmp -s "$gz" /bin/pigzwrapper; then - pmx_write_file "$gz" < /bin/pigzwrapper + # Never put the wrapper in place without a real gzip behind it. With the + # diversion active, a reinstall writes straight into gzip.distrib. + real=$(dpkg-divert --truename "$gz") + ensure_gzip_binary "$real" || { msg_error "$(translate "gzip could not be verified, leaving it unchanged")"; return 1; } + if ! cmp -s "$gz" /bin/pigzwrapper && ! pmx_write_file "$gz" < /bin/pigzwrapper; then + pmx_write_file "$gz" < "$real" + msg_error "$(translate "gzip could not be verified, leaving it unchanged")" + return 1 fi msg_ok "$(translate "gzip replaced with pigz wrapper successfully")" diff --git a/scripts/post_install/uninstall-tools.sh b/scripts/post_install/uninstall-tools.sh index 289b2dcd..a5daa9a3 100644 --- a/scripts/post_install/uninstall-tools.sh +++ b/scripts/post_install/uninstall-tools.sh @@ -1064,6 +1064,19 @@ uninstall_ovh_rtm() { register_tool "ovh_rtm" false } +# True if $1 is a working gzip binary rather than the pigz wrapper script. +gzip_is_binary() { + [ -f "$1" ] && [ "$(head -c 2 "$1")" != "#!" ] && "$1" --version >/dev/null 2>&1 +} + +# Makes sure $1 is a working gzip, reinstalling the package if it isn't. +ensure_gzip_binary() { + gzip_is_binary "$1" && return 0 + pmx_record_execution "Reinstall gzip" "apt-get install --reinstall -y gzip" + apt-get install --reinstall -y gzip >/dev/null 2>&1 + gzip_is_binary "$1" +} + uninstall_pigz() { local FUNC_VERSION="1.0" pmx_journal_context "uninstall_pigz" "$FUNC_VERSION" @@ -1073,7 +1086,12 @@ uninstall_pigz() { gz=${gz:-/usr/bin/gzip} real=$(dpkg-divert --truename "$gz") if [[ "$real" != "$gz" ]]; then - pmx_write_file "$gz" < "$real" + # With the diversion still active, a reinstall writes into $real. + if ! ensure_gzip_binary "$real" || ! pmx_write_file "$gz" < "$real"; then + pmx_write_file "$gz" < /bin/pigzwrapper + msg_error "$(translate "gzip could not be verified, leaving it unchanged")" + return 1 + fi pmx_record_execution "Remove gzip diversion" "dpkg-divert --local --no-rename --remove $gz" dpkg-divert --local --no-rename --remove "$gz" >/dev/null pmx_remove_file "$real" @@ -1081,7 +1099,7 @@ uninstall_pigz() { elif [[ -f /bin/gzip.original ]]; then # Older, non-diverted install. A gzip update may already have put a # newer binary back; don't overwrite that with the stale copy. - [[ "$(head -c 2 "$gz")" == "#!" ]] && pmx_write_file "$gz" < /bin/gzip.original + gzip_is_binary "$gz" || { gzip_is_binary /bin/gzip.original && pmx_write_file "$gz" < /bin/gzip.original; } pmx_remove_file /bin/gzip.original msg_ok "$(translate 'Restored original /bin/gzip')" fi @@ -1092,6 +1110,8 @@ uninstall_pigz() { dpkg-divert --local --no-rename --remove "$p" >/dev/null rm -f "$real" done + # pigz is still what gzip runs if anything above went wrong. + ensure_gzip_binary "$gz" || { msg_error "$(translate "gzip could not be verified, leaving it unchanged")"; return 1; } pmx_remove_file /bin/pigzwrapper pmx_edit_file /etc/vzdump.conf 's/^pigz: 1/#pigz: 1/' 2>/dev/null || true pmx_record_execution "Purge pigz package" "apt-get purge -y pigz"