From 5df1124ee8dc83992bbf221cbe8386b179e5b872 Mon Sep 17 00:00:00 2001 From: Dushnilin Date: Thu, 17 Sep 2026 11:16:28 +0300 Subject: [PATCH] fix(apk): sanitize orphaned sing-box packages in apk world and add force-overwrite --- fe-app-tachyon/locales/tachyon.pot | 4 +-- fe-app-tachyon/locales/tachyon.ru.po | 4 +-- install.sh | 41 ++++++++++++++++++++-- luci-app-tachyon/po/ru/tachyon.po | 4 +-- luci-app-tachyon/po/templates/tachyon.pot | 4 +-- tachyon/files/usr/lib/components/action.uc | 30 +++++++++++++--- uninstall.sh | 22 ++++++++++++ 7 files changed, 95 insertions(+), 14 deletions(-) diff --git a/fe-app-tachyon/locales/tachyon.pot b/fe-app-tachyon/locales/tachyon.pot index 5c2eadc0..849830c3 100644 --- a/fe-app-tachyon/locales/tachyon.pot +++ b/fe-app-tachyon/locales/tachyon.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 07:46+0300\n" -"PO-Revision-Date: 2026-09-17 07:46+0300\n" +"POT-Creation-Date: 2026-09-17 08:16+0300\n" +"PO-Revision-Date: 2026-09-17 08:16+0300\n" "Last-Translator: Dushnilin \n" "Language-Team: LANGUAGE \n" "Language: \n" diff --git a/fe-app-tachyon/locales/tachyon.ru.po b/fe-app-tachyon/locales/tachyon.ru.po index 178cb5e9..a672667f 100644 --- a/fe-app-tachyon/locales/tachyon.ru.po +++ b/fe-app-tachyon/locales/tachyon.ru.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 10:46+0300\n" -"PO-Revision-Date: 2026-09-17 10:46+0300\n" +"POT-Creation-Date: 2026-09-17 11:16+0300\n" +"PO-Revision-Date: 2026-09-17 11:16+0300\n" "Last-Translator: Dushnilin\n" "Language-Team: none\n" "Language: ru\n" diff --git a/install.sh b/install.sh index 94405cf1..4a356649 100755 --- a/install.sh +++ b/install.sh @@ -423,12 +423,29 @@ run_logged_timeout() { return "$_rc" } +# Clean up orphaned sing-box package references in /etc/apk/world that prevent apk from resolving dependencies +sanitize_apk_world() { + [ "$PKG_IS_APK" -eq 1 ] || return 0 + [ -f /etc/apk/world ] || return 0 + + local _orphaned_pkg + for _orphaned_pkg in sing-box-extended sing-box sing-box-tiny sing-box-lx; do + if grep -E -q "^${_orphaned_pkg}([><= ].*)?$" /etc/apk/world 2>/dev/null; then + if ! apk info -e "$_orphaned_pkg" >/dev/null 2>&1; then + log_line "WARN removing orphaned $_orphaned_pkg from /etc/apk/world" + sed -i -E "/^${_orphaned_pkg}([><= ].*)?$/d" /etc/apk/world 2>/dev/null || true + fi + fi + done +} + # Retry an APK command up to 10 times if the database is locked (exit 227). # Usage: apk_with_lock_retry apk_with_lock_retry() { local _tag="$1" _secs="$2" shift 2 local _attempt=0 _rc=227 + sanitize_apk_world while [ "$_attempt" -lt 10 ] && [ "$_rc" -eq 227 ]; do if [ "$_attempt" -gt 0 ]; then log_line "WARN [$_tag] APK database locked (exit 227), retrying in 3s (attempt $((_attempt+1))/10)..." @@ -462,6 +479,11 @@ warn() { fail() { log_line "FAIL $1" + if [ "${TACHYON_WAS_RUNNING:-0}" -eq 1 ] && [ -x /etc/init.d/tachyon ]; then + log_line "WARN restoring Tachyon service after failed install" + /etc/init.d/tachyon enable 2>/dev/null || true + /etc/init.d/tachyon restart 2>/dev/null || true + fi if command -v tui_err >/dev/null 2>&1; then tui_err "$1" else @@ -1138,11 +1160,18 @@ function installer_package_installed(name) { function installer_remove_package(name) { name = as_string(name); - if (name == "" || !installer_package_installed(name)) + if (name == "") return true; - if (installer_package_manager() == "apk") + if (installer_package_manager() == "apk") { + if (file_exists("/etc/apk/world")) + command_success("sed -i -E " + shell_quote("/^" + name + "([><= ].*)?$/d") + " /etc/apk/world 2>/dev/null"); + if (!installer_package_installed(name)) + return true; return run_args([ "timeout", "120", "apk", "del", name ]); + } + if (!installer_package_installed(name)) + return true; return run_args([ "timeout", "120", "opkg", "remove", "--force-depends", name ]); } @@ -1442,6 +1471,13 @@ function installer_cleanup_legacy() { remove_path("/etc/netshift"); } + if (installer_package_manager() == "apk" && file_exists("/etc/apk/world")) { + for (let orphaned_pkg in [ "sing-box-extended", "sing-box", "sing-box-tiny", "sing-box-lx" ]) { + if (!installer_package_installed(orphaned_pkg)) + command_success("sed -i -E " + shell_quote("/^" + orphaned_pkg + "([><= ].*)?$/d") + " /etc/apk/world 2>/dev/null"); + } + } + if (!installer_remove_package_prefix("luci-i18n-tachyon")) packages_removed = false; if (!installer_remove_package("luci-app-tachyon")) @@ -2826,6 +2862,7 @@ download_tachyon_packages() { } install_backend_package() { + sanitize_apk_world msg "$(installer_text kmod_check)" for kmod in kmod-inet-diag kmod-netlink-diag kmod-tun kmod-nft-tproxy kmod-nft-nat; do if ! pkg_is_installed "$kmod"; then diff --git a/luci-app-tachyon/po/ru/tachyon.po b/luci-app-tachyon/po/ru/tachyon.po index 178cb5e9..a672667f 100644 --- a/luci-app-tachyon/po/ru/tachyon.po +++ b/luci-app-tachyon/po/ru/tachyon.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 10:46+0300\n" -"PO-Revision-Date: 2026-09-17 10:46+0300\n" +"POT-Creation-Date: 2026-09-17 11:16+0300\n" +"PO-Revision-Date: 2026-09-17 11:16+0300\n" "Last-Translator: Dushnilin\n" "Language-Team: none\n" "Language: ru\n" diff --git a/luci-app-tachyon/po/templates/tachyon.pot b/luci-app-tachyon/po/templates/tachyon.pot index 5c2eadc0..849830c3 100644 --- a/luci-app-tachyon/po/templates/tachyon.pot +++ b/luci-app-tachyon/po/templates/tachyon.pot @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: TACHYON\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-09-17 07:46+0300\n" -"PO-Revision-Date: 2026-09-17 07:46+0300\n" +"POT-Creation-Date: 2026-09-17 08:16+0300\n" +"PO-Revision-Date: 2026-09-17 08:16+0300\n" "Last-Translator: Dushnilin \n" "Language-Team: LANGUAGE \n" "Language: \n" diff --git a/tachyon/files/usr/lib/components/action.uc b/tachyon/files/usr/lib/components/action.uc index 41060923..70d71379 100644 --- a/tachyon/files/usr/lib/components/action.uc +++ b/tachyon/files/usr/lib/components/action.uc @@ -459,7 +459,7 @@ function pkg_install_files_command(files, force_reinstall) { let add_args = [ "apk", "add", "--allow-untrusted" ]; for (let file in files) push(add_args, file); - let extract_args = [ "apk", "extract", "--allow-untrusted", "--destination", "/" ]; + let extract_args = [ "apk", "extract", "--allow-untrusted", "--force-overwrite", "--destination", "/" ]; for (let file in files) push(extract_args, file); return "(" + command_from_args(add_args) + " <= ].*)?$/d") + " /etc/apk/world 2>/dev/null"); + } +} + function pkg_install_files(files, force_reinstall) { + sanitize_apk_world(); return command_success(pkg_install_files_command(files, force_reinstall)); } // Like run_logged but retries up to 10 times on package manager database lock (exit code 227 for APK, lock file contention for opkg) function run_logged_retrying(description, command) { init_tmp_dir(); + sanitize_apk_world(); let output_file = make_tmp_file("command"); if (output_file == "") output_file = "/tmp/tachyon-updates-command." + owner_pid(); @@ -498,7 +509,8 @@ function run_logged_retrying(description, command) { } status = command_status(as_string(command) + " >" + shell_quote(output_file) + " 2>&1"); let output_text = read_file(output_file); - let is_locked = status == 227 || (status == 255 && match(output_text, /Could not lock|opkg\.lock|Resource temporarily unavailable/i) != null); + let is_locked = (status == 227 || (status == 255 && match(output_text, /Could not lock|opkg\.lock|Resource temporarily unavailable/i) != null)) && + match(output_text, /unable to select packages|no such package/i) == null; if (!is_locked) break; } @@ -517,14 +529,22 @@ function run_logged_retrying(description, command) { function pkg_remove_sing_box_conflict(package_name) { package_name = as_string(package_name); + if (is_apk()) { + if (file_exists("/etc/apk/world")) + command_success("sed -i -E " + shell_quote("/^" + package_name + "([><= ].*)?$/d") + " /etc/apk/world 2>/dev/null"); + if (!pkg_is_installed(package_name)) + return true; + return command_success(command_from_args([ "apk", "del", "--force-broken-world", package_name ]) + " <= ].*)?$/d") + " /etc/apk/world 2>/dev/null"); + if (!pkg_is_installed(package_name)) { updates_log(description); return true; @@ -2189,6 +2209,8 @@ function write_sing_box_variant_state(marker, version) { updates_log("Failed to write sing-box variant marker", "warn"); if (!sing_box_runtime_success("write-version-state", [ version ])) updates_log("Failed to write sing-box version state", "warn"); + if (marker != "extended") + sanitize_apk_world(); } function restore_sing_box_variant_state(previous_marker, previous_version_state) { diff --git a/uninstall.sh b/uninstall.sh index c1347045..99347e62 100644 --- a/uninstall.sh +++ b/uninstall.sh @@ -331,10 +331,32 @@ fi if command -v apk >/dev/null 2>&1 && [ -d "/lib/apk/db" ]; then for _pkg in luci-i18n-tachyon-ru luci-app-tachyon tachyon; do + if [ -f /etc/apk/world ]; then + sed -i -E "/^${_pkg}([><= ].*)?$/d" /etc/apk/world 2>/dev/null || true + fi if apk info -e "$_pkg" >/dev/null 2>&1; then apk del "$_pkg" >/dev/null 2>&1 || true fi done + if [ "$OPT_KEEP_BINARIES" -eq 0 ]; then + for _pkg in sing-box-extended sing-box-tiny; do + if [ -f /etc/apk/world ]; then + sed -i -E "/^${_pkg}([><= ].*)?$/d" /etc/apk/world 2>/dev/null || true + fi + if apk info -e "$_pkg" >/dev/null 2>&1; then + apk del "$_pkg" >/dev/null 2>&1 || true + fi + done + if [ ! -f /etc/init.d/sing-box ] || grep -q "Tachyon managed sing-box" /etc/init.d/sing-box 2>/dev/null; then + if [ -f /etc/apk/world ]; then + sed -i -E '/^sing-box([><= ].*)?$/d' /etc/apk/world 2>/dev/null || true + fi + if apk info -e "sing-box" >/dev/null 2>&1; then + apk del "sing-box" >/dev/null 2>&1 || true + fi + rm -f /usr/bin/sing-box 2>/dev/null || true + fi + fi tui_ok "Пакеты удалены через apk-tools" elif command -v opkg >/dev/null 2>&1; then _wait=0