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
@@ -16,6 +16,10 @@ interface UpdateInfo {
};
updateAvailable: boolean;
newerBetaAvailable?: boolean;
/** Running a pre-rename stable build, i.e. still pulling the retired
* ghcr.io/the-luap/picpeak/* path (#985). Such installs predate
* MigrationBanner and can never render it, so the notice rides here. */
registryMigrationRequired?: boolean;
lastChecked: string;
error?: string;
message?: string;
@@ -83,6 +87,31 @@ export const UpdateNotification: React.FC<UpdateNotificationProps> = ({ onDismis
channel: channelLabel
})}
</p>
{/* Pre-rename install (#985): pulling the retired registry path means
`docker compose pull` succeeds against a frozen tag and the update
never actually arrives. These builds predate MigrationBanner, so
this is the only place the instruction can reach them. */}
{updateInfo.registryMigrationRequired && (
<div className="mt-2 rounded-md bg-amber-50 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-800 p-2">
<p className="text-xs font-semibold text-amber-800 dark:text-amber-200">
{t('admin.updates.registryMoved.title', 'Pulling from the retired image registry')}
</p>
<p className="text-xs text-amber-700 dark:text-amber-300 mt-0.5">
{t('admin.updates.registryMoved.body', {
defaultValue: 'This update will not arrive until you change the image path in docker-compose.yml to {{newPath}}. The old path still responds, so `docker compose pull` appears to succeed while serving the same frozen build.',
newPath: 'ghcr.io/picpeak/picpeak/{backend,frontend}',
})}{' '}
<a
href="https://github.com/PicPeak/picpeak/blob/main/docs/migration-to-org.md"
target="_blank"
rel="noreferrer"
className="underline hover:no-underline"
>
{t('admin.updates.registryMoved.link', 'See migration notes')}
</a>
</p>
</div>
)}
{Array.isArray(updateInfo.latestHighlights) && updateInfo.latestHighlights.length > 0 && (
<div className="mt-2">
<p className="text-xs font-medium text-blue-700 dark:text-blue-300">