From 2a7801f430d36c2018162813a2d9f5d04f31166a Mon Sep 17 00:00:00 2001 From: Syco21 Date: Sat, 16 May 2026 17:28:06 +0200 Subject: [PATCH] Add lscr.io support to Docker update checker --- server/routes/updates.ts | 27 ++++++++++++++----- .../widgets/DockerUpdatesWidget.tsx | 5 ++-- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/server/routes/updates.ts b/server/routes/updates.ts index 30125b9..17dfc7e 100644 --- a/server/routes/updates.ts +++ b/server/routes/updates.ts @@ -86,25 +86,26 @@ async function getLatestDigest(image: string, tag: string): Promise { - const cacheKey = `ghcr:${image}:${tag}` +async function getGenericRegistryDigest( + registry: string, image: string, tag: string, bearerToken?: string +): Promise { + const cacheKey = `${registry}:${image}:${tag}` const cached = fromCache(cacheKey, HUB_TTL) if (cached) return cached try { - const ghToken = process.env.GITHUB_TOKEN - const authHeaders: Record = ghToken - ? { Authorization: `Bearer ${ghToken}` } + const authHeaders: Record = bearerToken + ? { Authorization: `Bearer ${bearerToken}` } : {} const tokenRes = await axios.get( - `https://ghcr.io/token?service=ghcr.io&scope=repository:${image}:pull`, + `https://${registry}/token?service=${registry}&scope=repository:${image}:pull`, { headers: authHeaders, timeout: 8000 } ) const token = tokenRes.data.token as string const res = await axios.head( - `https://ghcr.io/v2/${image}/manifests/${tag}`, + `https://${registry}/v2/${image}/manifests/${tag}`, { headers: { Authorization: `Bearer ${token}`, Accept: MANIFEST_ACCEPT }, timeout: 10000 } ) const digest = res.headers['docker-content-digest'] as string | undefined @@ -113,6 +114,14 @@ async function getGhcrLatestDigest(image: string, tag: string): Promise { + return getGenericRegistryDigest('ghcr.io', image, tag, process.env.GITHUB_TOKEN) +} + +async function getLscrLatestDigest(image: string, tag: string): Promise { + return getGenericRegistryDigest('lscr.io', image, tag) +} + router.get('/docker', async (_req, res) => { const host = process.env.PORTAINER_HOST if (!host) { @@ -168,6 +177,7 @@ router.get('/docker', async (_req, res) => { const { registry, name, tag } = parseImage(rawImage) const isDockerHub = registry === 'docker.io' const isGhcr = registry === 'ghcr.io' + const isLscr = registry === 'lscr.io' let upToDate: boolean | null = null if (isDockerHub && repoDigest) { @@ -176,6 +186,9 @@ router.get('/docker', async (_req, res) => { } else if (isGhcr && repoDigest) { const latest = await getGhcrLatestDigest(name, tag) if (latest) upToDate = latest === repoDigest + } else if (isLscr && repoDigest) { + const latest = await getLscrLatestDigest(name, tag) + if (latest) upToDate = latest === repoDigest } containers.push({ diff --git a/src/components/widgets/DockerUpdatesWidget.tsx b/src/components/widgets/DockerUpdatesWidget.tsx index 8092853..055eaad 100644 --- a/src/components/widgets/DockerUpdatesWidget.tsx +++ b/src/components/widgets/DockerUpdatesWidget.tsx @@ -26,7 +26,8 @@ export function DockerUpdatesWidget() { }, []) const outdated = containers.filter(c => c.upToDate === false).length - const unknown = containers.filter(c => c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io').length + const knownRegistries = ['docker.io', 'ghcr.io', 'lscr.io'] + const unknown = containers.filter(c => c.upToDate === null && !knownRegistries.includes(c.registry)).length return (
@@ -58,7 +59,7 @@ export function DockerUpdatesWidget() { {c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint}
- {c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io' ? ( + {c.upToDate === null && !knownRegistries.includes(c.registry) ? ( ext ) : c.upToDate === true ? (