From f3fcdba1528a465ea4ef0719a5273b7374a9548d Mon Sep 17 00:00:00 2001 From: Robert Kirkman Date: Thu, 29 May 2025 13:20:12 -0500 Subject: [PATCH 1/2] termux-tools: pkg: never implicitly update package cache without upgrading all packages > [!IMPORTANT] > These changes are not intended to be considered absolutely necessary or mandatory to merge. > They are changes that would alter behavior users might have come to expect from Termux `pkg`, that has been different from how other distros behave for a long time. > These changes are instead meant to serve an example of how Termux's `pkg` command **could** have been designed in retrospect, in order to prevent accidental partial upgrades, in a way that is familiar to users of other rolling-release distributions that existed for a long time before Termux existed. - Fixes https://github.com/termux/termux-tools/issues/137 - Never skip updating the package cache based on a timeout or the presence of metadata files. - Instead, always try directly installing packages first, then interpret the output message from the package manager to determine the appropriate action. - If the package installation was successful despite the package cache not having been updated, then everything is actually OK, and no further action is required - If the package installation failed with an error indicating failure to download the package **or** an error indicating the package was not found (which could happen in three cases: the package cache has never been generated, or the package is new and the package cache has not been updated since the package became available, or the package name has a typo and does not actually exist), then automatically update package cache **and** upgrade all packages, and **then** attempt to install the package again. - This change of behavior prevents the accidental partial upgrades that currently happen mainly because of the current `pkg install` command updating package cache without upgrading all packages simultaneously. - If the package installation failed, but one of the errors handled here was not not detected in the output of the package manager, then exit and do nothing since in that case, there is no reason that updating the package cache or upgrading all packages would help. - Do not implicitly update package cache during `pkg search` before running the package manager search command, because it could result in accidentally updating the package cache without upgrading all packages, which could result in accidental partial upgrades. --- scripts/pkg.in | 95 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/scripts/pkg.in b/scripts/pkg.in index 8fc9ea8..31a386b 100644 --- a/scripts/pkg.in +++ b/scripts/pkg.in @@ -1,5 +1,5 @@ #!/bin/bash -set -eu +set -euo pipefail # Root Check if [[ "$(id -u)" == "0" ]]; then @@ -393,45 +393,64 @@ select_mirror() { ) } -update_apt_cache() { - local current_host - if [ -f "@TERMUX_PREFIX@/etc/apt/sources.list.d/main.sources" ]; then - current_host=$(head -n 1 <(sed -nE 's|^\s*URIs:\s+https?://(.+)$|\1|p' "@TERMUX_PREFIX@/etc/apt/sources.list.d/main.sources") || :) - elif [ -f "@TERMUX_PREFIX@/etc/apt/sources.list" ]; then - current_host=$(head -n 1 <(sed -nE 's|^\s*deb\s+https?://(.+)\s+stable\s+main$|\1|p' "@TERMUX_PREFIX@/etc/apt/sources.list") || :) - fi - - if [ -z "$current_host" ]; then - # No primary repositories configured? - apt update +handle_install_apt() { + local install_log install_command + # pipe the fd 3 to the current stdout, + # which will make anything printed to /dev/fd/3 print to the current shell's stdout + exec 3>&1 + # attempt package installation without updating package cache + install_command="apt install $@" + if install_log="$(script -q -e -c "$install_command" /dev/null | tee /dev/fd/3)"; then + # close fd 3 + exec 3>&- + # If the package installation was successful, then no further action is required return fi - - local metadata_file - metadata_file=$( - list_prefix=$(echo "$current_host" | sed 's|/|_|g') - arch=$(dpkg --print-architecture) - echo "@TERMUX_PREFIX@/var/lib/apt/lists/${list_prefix}_dists_stable_main_binary-${arch}_Packages" | sed 's|__|_|g' - ) - - if [ ! -e "@TERMUX_CACHE_DIR@/apt/pkgcache.bin" ] || [ ! -e "$metadata_file" ]; then - apt update - return + # close fd 3 + exec 3>&- + if ! grep -q -e 'Failed to fetch' -e 'Unable to locate package' <<< "$install_log"; then + # if apt did not print "Failed to fetch" or "Unable to locate package", + # then an error not handled here occurred, so exit. + # The original error message is still displayed to the user above. + exit 1 fi + select_mirror + # since package installation failed because of failure to download a package, + # updating the package cache is always necessary + apt update + # perform full upgrade interactively. + apt full-upgrade + # reattempt installing the package separately so that if one or more of the + # packages named does not exist, that failure does not prevent upgrading all packages. + apt install "$@" +} - local cache_modified - cache_modified=$(last_modified "@TERMUX_CACHE_DIR@/apt/pkgcache.bin") - - local sources_modified - if [ -f "@TERMUX_PREFIX@/etc/apt/sources.list.d/main.sources" ]; then - sources_modified=$(last_modified "@TERMUX_PREFIX@/etc/apt/sources.list.d/main.sources") - elif [ -f "@TERMUX_PREFIX@/etc/apt/sources.list" ]; then - sources_modified=$(last_modified "@TERMUX_PREFIX@/etc/apt/sources.list") +handle_install_pacman() { + local install_log install_command + # pipe the fd 3 to the current stdout, + # which will make anything printed to /dev/fd/3 print to the current shell's stdout + exec 3>&1 + # attempt package installation without updating package cache + install_command="pacman -S --needed $@" + if install_log="$(script -q -e -c "$install_command" /dev/null | tee /dev/fd/3)"; then + # close fd 3 + exec 3>&- + # If the package installation was successful, then no further action is required + return fi - - if (( sources_modified <= cache_modified )) || (( cache_modified > 1200 )); then - apt update + # close fd 3 + exec 3>&- + if ! grep -q -e 'failed retrieving file' -e 'target not found' <<< "$install_log"; then + # if pacman did not print "failed retrieving file" or "target not found", + # then an error not handled here occurred, so exit. + # The original error message is still displayed to the user above. + exit 1 fi + # perform full upgrade interactively. + pacman -Syu + # reattempt installing the package separately so that if one or more of the + # packages named does not exist, that failure does not prevent upgrading all packages. + pacman -S --needed "$@" } force_check_mirror=false @@ -453,13 +472,13 @@ case "$TERMUX_APP_PACKAGE_MANAGER" in case "$CMD" in f*) dpkg -L "$@";; sh*|inf*) apt show "$@";; - add|i*) select_mirror; update_apt_cache; apt install "$@";; + add|i*) handle_install_apt "$@";; autoc*) apt autoclean;; cl*) apt clean;; list-a*) apt list "$@";; list-i*) apt list --installed "$@";; rei*) apt install --reinstall "$@";; - se*) select_mirror; update_apt_cache; apt search "$@";; + se*) apt search "$@";; un*|rem*|rm|del*) apt remove "$@";; upd*) select_mirror; apt update;; up|upg*) select_mirror; apt update; apt full-upgrade "$@";; @@ -469,13 +488,13 @@ case "$TERMUX_APP_PACKAGE_MANAGER" in case "$CMD" in f*) pacman -Ql "$@";; sh*|inf*) pacman -Qi "$@";; - add|i*) pacman -Sy --needed "$@";; + add|i*) handle_install_pacman "$@";; autoc*) pacman -Sc;; cl*) pacman -Scc;; list-a*) pacman -Sl "$@";; list-i*) pacman -Q "$@";; rei*) pacman -S "$@";; - se*) pacman -Sys "$@";; + se*) pacman -Ss "$@";; un*|rem*|rm|del*) pacman -Rcns "$@";; upd*) pacman -Sy "$@";; up|upg*) pacman -Syu "$@";; From 351a46c7c5e8689aa73aea11aee5171dbd5541b4 Mon Sep 17 00:00:00 2001 From: Robert Kirkman Date: Thu, 3 Jul 2025 17:06:31 -0500 Subject: [PATCH 2/2] tstein's idea: for apt, always ensure mirrors are rotated if they have not for (if the package cache has not been updated since) 6 hours --- scripts/pkg.in | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/scripts/pkg.in b/scripts/pkg.in index 31a386b..bbcbb69 100644 --- a/scripts/pkg.in +++ b/scripts/pkg.in @@ -394,13 +394,19 @@ select_mirror() { } handle_install_apt() { - local install_log install_command + local install_log install_command pkgcache # pipe the fd 3 to the current stdout, # which will make anything printed to /dev/fd/3 print to the current shell's stdout exec 3>&1 # attempt package installation without updating package cache + # only if it's not time for mirrors to be rotated install_command="apt install $@" - if install_log="$(script -q -e -c "$install_command" /dev/null | tee /dev/fd/3)"; then + pkgcache="@TERMUX_CACHE_DIR@/apt/pkgcache.bin" + if [ ! -e "$pkgcache" ] || \ + (( $(last_modified "$pkgcache") > 6 * 3600 )) || \ + [ "$force_check_mirror" = "true" ]; then + install_log="Mirrors have not been rotated within the last 6 hours" + elif install_log="$(script -q -e -c "$install_command" /dev/null | tee /dev/fd/3)"; then # close fd 3 exec 3>&- # If the package installation was successful, then no further action is required @@ -408,8 +414,13 @@ handle_install_apt() { fi # close fd 3 exec 3>&- - if ! grep -q -e 'Failed to fetch' -e 'Unable to locate package' <<< "$install_log"; then + if ! grep -q \ + -e 'Failed to fetch' \ + -e 'Unable to locate package' \ + -e 'Mirrors have not been rotated within the last 6 hours' \ + <<< "$install_log"; then # if apt did not print "Failed to fetch" or "Unable to locate package", + # and mirrors have been rotated within the last 6 hours, # then an error not handled here occurred, so exit. # The original error message is still displayed to the user above. exit 1