From f747083115c5a4461ffae6917a9bf1c59c78e979 Mon Sep 17 00:00:00 2001 From: librelad Date: Wed, 19 Aug 2026 00:15:45 +0100 Subject: [PATCH] fix(updater): find newer versions the tag listing cannot see MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two holes that together left a versioned app reporting "up to date" while a newer release was published. Newer-version discovery enumerated a repo's newest 100 tags. Projects that push a tag per commit drown their own releases in that window — matrixdotorg/synapse's newest 100 hold five version tags, about ten days of history. Once the release we need is older than the window it is simply absent, and the app reports current forever. The failure is silent and lands hardest on the apps furthest behind. Discovery now falls back to PROBING exact tags, most-significant component first, which has no window at all. Listing still runs first, so the common case stays at one call; probing is bounded at 40 lookups. Same reasoning the version ladder already uses, for the same reason. Registry lookups were also throttled purely per-run, so an app installed just after a window carried an empty available_digest until the next one — up to CFG_UPDATER_REGISTRY_INTERVAL (6h) later. Empty means update_available=false, which the UI renders as "up to date", so a new app claimed to be current on no evidence. Seen live: seven apps installed the evening after a 19:31 window all sat at update_available=false, one of them two releases behind. Apps with no prior registry answer are now looked up regardless of the throttle — once each, and interval 0 still means manual-only. Co-Authored-By: Claude Opus 5 --- .../generators/updater/webui_updater_scan.sh | 108 +++++++++++++++++- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/scripts/webui/data/generators/updater/webui_updater_scan.sh b/scripts/webui/data/generators/updater/webui_updater_scan.sh index 50de7aa..9e1878e 100644 --- a/scripts/webui/data/generators/updater/webui_updater_scan.sh +++ b/scripts/webui/data/generators/updater/webui_updater_scan.sh @@ -187,9 +187,9 @@ updaterTagGreater() { return 1 # identical -> not newer } -# The newest tag for $2 (repo) that shares $1's (current tag) shape and is -# numerically greater. Empty when there is nothing newer — the common case. -updaterNewerVersionTag() { +# The newest same-shape, numerically-greater tag found by ENUMERATING the newest +# 100 tags. One cheap call, and right whenever the release we want is recent. +updaterNewerVersionByList() { local cur="$1" repo="$2" local shape; shape="$(updaterTagShape "$cur")" local best="" t @@ -202,6 +202,82 @@ updaterNewerVersionTag() { printf '%s' "$best" } +# Bump the Nth (0-based) numeric component of a tag by one and zero every +# component after it, so a bump means what a version bump means: +# "v1.158.2", 1 -> "v1.159.0" "31-fpm-alpine", 0 -> "32-fpm-alpine" +# Shape is preserved by construction (only the digits change), which is what +# lets the caller compare shapes to reject nonsense candidates. +updaterTagBumpAt() { + printf '%s' "$1" | awk -v idx="$2" '{ + out=""; n=0; s=$0 + while (match(s, /[0-9]+/)) { + pre = substr(s, 1, RSTART-1) + num = substr(s, RSTART, RLENGTH) + 0 + s = substr(s, RSTART+RLENGTH) + if (n == idx) num = num + 1; else if (n > idx) num = 0 + out = out pre num + n++ + } + print out s + }' +} + +# The newest tag reachable from $1 by PROBING for exact tags, most-significant +# component first, climbing each as far as it goes. +# +# This exists because enumeration has a blind spot that gets worse the more +# actively a project publishes. The listing returns the newest 100 tags, and +# repos that push a tag per commit drown their own releases in them — +# matrixdotorg/synapse's newest 100 hold just 5 version tags, roughly ten days +# of history. Once the release we need is older than that window it is simply +# absent, updaterNewerVersionByList returns nothing, and the app reports "up to +# date" forever. The failure is silent and it lands exactly on the apps that are +# furthest behind, which are the ones that most need to be told. +# +# Probing has no window: an exact tag lookup either exists or it does not. This +# is the same reasoning (and the same endpoint) as the version ladder, which +# refuses to build a rung list from a listing for precisely this reason — see +# cli_updater_ladder.sh. Bounded at 40 lookups so a strange tag scheme cannot +# turn one app's scan into a crawl. +updaterNewerVersionByProbe() { + local cur="$1" repo="$2" + declare -F updaterTagExists >/dev/null 2>&1 || return 0 # ladder unavailable + local shape; shape="$(updaterTagShape "$cur")" + local ncomp; ncomp="$(printf '%s' "$cur" | grep -oE '[0-9]+' | wc -l | tr -d ' ')" + [ "${ncomp:-0}" -gt 0 ] 2>/dev/null || return 0 + + local best="$cur" probes=0 i cand + for ((i=0; i/dev/null)" + [ "$_seen" = "null" ] && _seen="" + [ -z "$_seen" ] && app_registry=1 # never answered (new app, or first scan) + fi + # available (registry) digest for the anchor's channel local avail_dig="" - if [ "$do_registry" = "1" ]; then + if [ "$app_registry" = "1" ]; then avail_dig="$(updaterRegistryDigest "$(updaterRepoTag "$anchor")")" fi # reuse the prior value when we didn't (or couldn't) reach the registry @@ -287,7 +383,7 @@ webuiUpdaterScan() { # rest of the registry work: look it up live inside the window, otherwise # carry the previous answer forward so the field never blinks off. local img_updated="" - if [ "$do_registry" = "1" ]; then + if [ "$app_registry" = "1" ]; then img_updated="$(updaterTagLastUpdated "$(updaterRepoTag "$anchor")" "$channel")" fi if [ -z "$img_updated" ] && [ "$have_jq" = "1" ] && [ -f "$prev_json" ]; then @@ -301,7 +397,7 @@ webuiUpdaterScan() { # field does not flicker off on a throttled scan. local newer_ver="" if [ "$vtype" = "versioned" ]; then - if [ "$do_registry" = "1" ]; then + if [ "$app_registry" = "1" ]; then newer_ver="$(updaterNewerVersionTag "$channel" "$(updaterRepoTag "$anchor")")" newer_ver="${newer_ver%%:*}" elif [ "$have_jq" = "1" ] && [ -f "$prev_json" ]; then