diff --git a/scripts/source/fetch.sh b/scripts/source/fetch.sh index 4464ad4..c98bb90 100644 --- a/scripts/source/fetch.sh +++ b/scripts/source/fetch.sh @@ -68,6 +68,27 @@ lpVerifyMinisig() { # latter is greater, a plain manager-run update can't apply it (it can't rewrite # the root-owned helpers/wrapper/unit) — a root re-install is required. lpInstalledFootprintVersion() { local v; v=$(cat /usr/local/lib/libreportal/.footprint_version 2>/dev/null | tr -d ' \t\n\r'); echo "${v:-0}"; } + +# The footprint version the CODE IN THE INSTALL TREE would bake if init.sh were +# re-run. This is the only comparison available to a git or local install: those +# have no channel to ask, so lpReleaseLatestFootprint cannot answer and the +# staleness never surfaced — the root helpers silently stayed behind the code +# that expects them. Reading init.sh directly works for every mode, because +# init.sh is what bakes the marker in the first place. +lpInstallTreeFootprintVersion() { + local f="${script_dir%/}/init.sh" v="" + [[ -f "$f" ]] && v=$(grep -m1 -oE '^footprint_version=[0-9]+' "$f" 2>/dev/null | cut -d= -f2) + echo "${v:-0}" +} + +# True when what is baked into /usr/local/lib/libreportal is older than what the +# install tree carries, i.e. a root re-install (`sudo /init.sh`) is owed. +lpFootprintStale() { + local installed tree + installed=$(lpInstalledFootprintVersion) + tree=$(lpInstallTreeFootprintVersion) + [[ "${tree:-0}" -gt "${installed:-0}" ]] +} lpReleaseLatestFootprint() { local m; m="$(_lpDownload "$(lpReleaseBaseUrl)/$(lpReleaseChannel)/latest.json" - 2>/dev/null)" || return 1 local f; f=$(_lpJsonNum "$m" footprint_version); echo "${f:-0}" diff --git a/scripts/source/files/arrays/function_manifest.sh b/scripts/source/files/arrays/function_manifest.sh index 2c1fac1..e9b9eed 100644 --- a/scripts/source/files/arrays/function_manifest.sh +++ b/scripts/source/files/arrays/function_manifest.sh @@ -658,9 +658,11 @@ declare -gA LP_FN_MAP=( [lpFetchRelease]="source/fetch.sh" [lpFetchSource]="source/fetch.sh" [_lpFetchTool]="source/fetch.sh" + [lpFootprintStale]="source/fetch.sh" [lpIndexArtifactIds]="source/artifacts.sh" [lpIndexTop]="source/artifacts.sh" [lpInstalledFootprintVersion]="source/fetch.sh" + [lpInstallTreeFootprintVersion]="source/fetch.sh" [_lpJsonEsc]="source/fetch.sh" [_lpJsonNum]="source/fetch.sh" [_lpJsonStr]="source/fetch.sh" @@ -1803,9 +1805,11 @@ declare -gA LP_FN_ROOT=( [lpFetchRelease]="scripts" [lpFetchSource]="scripts" [_lpFetchTool]="scripts" + [lpFootprintStale]="scripts" [lpIndexArtifactIds]="scripts" [lpIndexTop]="scripts" [lpInstalledFootprintVersion]="scripts" + [lpInstallTreeFootprintVersion]="scripts" [_lpJsonEsc]="scripts" [_lpJsonNum]="scripts" [_lpJsonStr]="scripts" @@ -2984,9 +2988,11 @@ lpFetchIndexInto() { unset -f lpFetchIndexInto; __lpAutoload "${install_scripts_ lpFetchRelease() { unset -f lpFetchRelease; __lpAutoload "${install_scripts_dir}source/fetch.sh"; lpFetchRelease "$@"; } lpFetchSource() { unset -f lpFetchSource; __lpAutoload "${install_scripts_dir}source/fetch.sh"; lpFetchSource "$@"; } _lpFetchTool() { unset -f _lpFetchTool; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpFetchTool "$@"; } +lpFootprintStale() { unset -f lpFootprintStale; __lpAutoload "${install_scripts_dir}source/fetch.sh"; lpFootprintStale "$@"; } lpIndexArtifactIds() { unset -f lpIndexArtifactIds; __lpAutoload "${install_scripts_dir}source/artifacts.sh"; lpIndexArtifactIds "$@"; } lpIndexTop() { unset -f lpIndexTop; __lpAutoload "${install_scripts_dir}source/artifacts.sh"; lpIndexTop "$@"; } lpInstalledFootprintVersion() { unset -f lpInstalledFootprintVersion; __lpAutoload "${install_scripts_dir}source/fetch.sh"; lpInstalledFootprintVersion "$@"; } +lpInstallTreeFootprintVersion() { unset -f lpInstallTreeFootprintVersion; __lpAutoload "${install_scripts_dir}source/fetch.sh"; lpInstallTreeFootprintVersion "$@"; } _lpJsonEsc() { unset -f _lpJsonEsc; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpJsonEsc "$@"; } _lpJsonNum() { unset -f _lpJsonNum; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpJsonNum "$@"; } _lpJsonStr() { unset -f _lpJsonStr; __lpAutoload "${install_scripts_dir}source/fetch.sh"; _lpJsonStr "$@"; } diff --git a/scripts/update/check_update.sh b/scripts/update/check_update.sh index 9d99a32..d61796b 100755 --- a/scripts/update/check_update.sh +++ b/scripts/update/check_update.sh @@ -22,6 +22,14 @@ webuiRunUpdate() if [[ "$install_mode" == "local" ]]; then isError "This is a local installation — updates are managed manually, nothing to pull." + # The code tree is synced by hand here, so the root-owned footprint can + # fall behind it without anything saying so — there is no channel + # manifest to trigger the usual footprint_update_needed path. + if declare -f lpFootprintStale >/dev/null 2>&1 && lpFootprintStale; then + isNotice "Root footprint is stale: installed v$(lpInstalledFootprintVersion), install tree carries v$(lpInstallTreeFootprintVersion)." + isNotice "The helpers in /usr/local/lib/libreportal are older than the code that calls them. Re-bake as root:" + isNotice " sudo ${script_dir%/}/init.sh" + fi return 1 fi if [[ "$git_updates" != "true" ]]; then @@ -139,6 +147,15 @@ checkUpdates() # Skip Git updates if installation mode is local or Git updates are disabled if [[ $CFG_INSTALL_MODE == "local" ]]; then isNotice "Local installation detected - Git updates are disabled." + # A local install syncs its code tree by hand, so the root-owned + # footprint can fall behind that code with nothing to say so — there is + # no channel manifest here to drive the usual footprint_update_needed + # path. This is the one place a local operator reliably looks. + if declare -f lpFootprintStale >/dev/null 2>&1 && lpFootprintStale; then + isNotice "Root footprint is stale: installed v$(lpInstalledFootprintVersion), install tree carries v$(lpInstallTreeFootprintVersion)." + isNotice "The helpers in /usr/local/lib/libreportal are older than the code calling them. Re-bake as root:" + isNotice " sudo ${script_dir%/}/init.sh" + fi if [[ $init_run_flag == "true" ]]; then startLoad; fi diff --git a/scripts/webui/data/generators/system/webui_system_update.sh b/scripts/webui/data/generators/system/webui_system_update.sh index 6e733e4..5ab2705 100755 --- a/scripts/webui/data/generators/system/webui_system_update.sh +++ b/scripts/webui/data/generators/system/webui_system_update.sh @@ -34,6 +34,21 @@ webuiSystemUpdate() { # Pass "force" as $1 to bypass the fetch throttle (used by the manual # "Check for updates" action: `libreportal update check`). webuiSystemUpdateCheck() { + # Same hazard webuiGenerateAppsToolsConfig documents: the WebUI task service + # is a long-lived bash process that sources this file once at startup, so an + # updated function on disk is ignored until the service restarts. That is + # not cosmetic here — it silently kept writing the OLD update_status.json, + # which is how the footprint check below appeared to do nothing after it was + # added. Re-source ourselves and dispatch to the fresh copy; the guard stops + # the recursion, and a missing file (briefly, mid-update) falls through to + # the in-memory version rather than failing. + if [[ -z "$_WEBUI_SYSUPDATE_RELOADED" && -f "${BASH_SOURCE[0]}" ]]; then + local _WEBUI_SYSUPDATE_RELOADED=1 + source "${BASH_SOURCE[0]}" + webuiSystemUpdateCheck "$@" + return $? + fi + local force_flag="$1" local repo_dir="${script_dir}" @@ -153,9 +168,14 @@ EOF # Not a git working copy, or a deliberately local install: we can't compare # against an upstream, so report "managed manually" rather than an error. if [[ ! -d "$repo_dir/.git" || "$install_mode" == "local" ]]; then + # There is no channel to compare against, but the install tree's own + # init.sh still says what the root footprint should be — so a stale + # footprint is detectable here, and used to go unreported entirely. + local _fp_local="false" + declare -f lpFootprintStale >/dev/null 2>&1 && lpFootprintStale && _fp_local="true" _webuiWriteUpdateStatus "false" "false" \ "$current_version" "$current_version" \ - "" "" "0" "0" "" "local" "" + "" "" "0" "0" "" "local" "" "$_fp_local" return 0 fi @@ -219,10 +239,16 @@ EOF local can_update="false" [[ "$install_mode" == "git" && "$git_updates" == "true" ]] && can_update="true" + # Same for a git install: no channel manifest, but init.sh in the working + # copy is authoritative about what the footprint should be. + local _fp_git="false" + declare -f lpFootprintStale >/dev/null 2>&1 && lpFootprintStale && _fp_git="true" + [[ "$_fp_git" == "true" ]] && can_update="false" + _webuiWriteUpdateStatus "$update_available" "$can_update" \ "$current_version" "$latest_version" \ "$current_commit" "$latest_commit" \ - "$behind" "$ahead" "$branch" "git" "$fetch_error" + "$behind" "$ahead" "$branch" "git" "$fetch_error" "$_fp_git" } # ---------------------------------------------------------------------------