feat(admin): surface the registry move through the update check (#993)

Relates to #985 — does NOT close it.

Adds registryMigrationRequired to the update-check payload (stable channel below
3.45.0) and an amber block in UpdateNotification explaining that the retired
registry path still responds, so `docker compose pull` appears to succeed while
serving the same frozen build.

Known limitation, established in review and merged deliberately: this cannot
reach the operators #985 describes. PicPeak is self-hosted, so the update-check
code runs inside the operator's own image — a v3.44.0 install runs v3.44.0's
backend forever, and the only external call returns release metadata, not logic.
Every build containing this predicate is >= 3.45.0, where it is false by
definition. The release-notes fallback fails too: the changelog modal shipped
2026-05-29, two days after the freeze.

Correct for any future rename, no runtime cost, but #985 stays open — the
population it describes still has no in-app channel. Viable routes are external
(retired GHCR package description, repo README, docs).

'0.0.0' is excluded from the predicate: that is getCurrentVersion's fallback for
an unreadable package.json, i.e. a broken install, not a pre-rename one.
This commit is contained in:
Paul Nothaft
2026-08-04 16:36:50 +02:00
committed by GitHub
parent 0c8ad6bbed
commit 137a42f259
3 changed files with 115 additions and 0 deletions
@@ -151,6 +151,37 @@ async function fetchAvailableVersions() {
/**
* Check for available updates
*/
// First stable release published to the org registry only. v3.44.0 shipped
// 2026-05-27, the same day ghcr.io/the-luap/picpeak/* stopped receiving images;
// v3.45.0 followed on 2026-07-09 on ghcr.io/picpeak/picpeak/* alone. Nothing
// was published in between, so "stable below this" is an exact detector for an
// install still pulling the retired path — not a heuristic.
const REGISTRY_RENAME_STABLE_FLOOR = '3.45.0';
/**
* Is this install running a pre-rename image, i.e. still pulling from the
* retired `ghcr.io/the-luap/picpeak/*` path? (#985)
*
* The in-app MigrationBanner cannot reach these operators: it shipped
* 2026-06-29, a month after the old registry froze, so their build predates the
* banner itself. The update check is the one channel that does reach them —
* their instance is demonstrably still talking to GitHub, which is how they see
* "update available" at all.
*
* Stable channel only, deliberately. The beta boundary is inferred rather than
* clean (3.59.0-beta.0 landed two days after the freeze), and a false positive
* here tells a correctly-configured operator their registry is retired.
*/
function isPreRenameStable(currentVersion, channel) {
if (channel !== 'stable') return false;
// getCurrentVersion() falls back to '0.0.0' when package.json is unreadable.
// That is a broken install, not a pre-rename one — it sorts below the floor,
// so guard it explicitly rather than sending that operator to change their
// image path.
if (!currentVersion || currentVersion === '0.0.0') return false;
return compareVersions(currentVersion, REGISTRY_RENAME_STABLE_FLOOR) < 0;
}
async function checkForUpdates(forceRefresh = false) {
const now = Date.now();
@@ -197,6 +228,7 @@ async function checkForUpdates(forceRefresh = false) {
},
updateAvailable,
newerBetaAvailable,
registryMigrationRequired: isPreRenameStable(currentVersion, currentChannel),
lastChecked: new Date().toISOString()
};
@@ -240,5 +272,7 @@ module.exports = {
getReleasesSince,
compareVersions,
parseVersion,
isPreRenameStable,
REGISTRY_RENAME_STABLE_FLOOR,
clearCache
};