Add lscr.io support to Docker update checker
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-05-16 17:28:06 +02:00
parent fdf616d5e8
commit 2a7801f430
2 changed files with 23 additions and 9 deletions
+20 -7
View File
@@ -86,25 +86,26 @@ async function getLatestDigest(image: string, tag: string): Promise<string | nul
} catch { return null } } catch { return null }
} }
async function getGhcrLatestDigest(image: string, tag: string): Promise<string | null> { async function getGenericRegistryDigest(
const cacheKey = `ghcr:${image}:${tag}` registry: string, image: string, tag: string, bearerToken?: string
): Promise<string | null> {
const cacheKey = `${registry}:${image}:${tag}`
const cached = fromCache<string>(cacheKey, HUB_TTL) const cached = fromCache<string>(cacheKey, HUB_TTL)
if (cached) return cached if (cached) return cached
try { try {
const ghToken = process.env.GITHUB_TOKEN const authHeaders: Record<string, string> = bearerToken
const authHeaders: Record<string, string> = ghToken ? { Authorization: `Bearer ${bearerToken}` }
? { Authorization: `Bearer ${ghToken}` }
: {} : {}
const tokenRes = await axios.get( 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 } { headers: authHeaders, timeout: 8000 }
) )
const token = tokenRes.data.token as string const token = tokenRes.data.token as string
const res = await axios.head( 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 } { headers: { Authorization: `Bearer ${token}`, Accept: MANIFEST_ACCEPT }, timeout: 10000 }
) )
const digest = res.headers['docker-content-digest'] as string | undefined const digest = res.headers['docker-content-digest'] as string | undefined
@@ -113,6 +114,14 @@ async function getGhcrLatestDigest(image: string, tag: string): Promise<string |
} catch { return null } } catch { return null }
} }
async function getGhcrLatestDigest(image: string, tag: string): Promise<string | null> {
return getGenericRegistryDigest('ghcr.io', image, tag, process.env.GITHUB_TOKEN)
}
async function getLscrLatestDigest(image: string, tag: string): Promise<string | null> {
return getGenericRegistryDigest('lscr.io', image, tag)
}
router.get('/docker', async (_req, res) => { router.get('/docker', async (_req, res) => {
const host = process.env.PORTAINER_HOST const host = process.env.PORTAINER_HOST
if (!host) { if (!host) {
@@ -168,6 +177,7 @@ router.get('/docker', async (_req, res) => {
const { registry, name, tag } = parseImage(rawImage) const { registry, name, tag } = parseImage(rawImage)
const isDockerHub = registry === 'docker.io' const isDockerHub = registry === 'docker.io'
const isGhcr = registry === 'ghcr.io' const isGhcr = registry === 'ghcr.io'
const isLscr = registry === 'lscr.io'
let upToDate: boolean | null = null let upToDate: boolean | null = null
if (isDockerHub && repoDigest) { if (isDockerHub && repoDigest) {
@@ -176,6 +186,9 @@ router.get('/docker', async (_req, res) => {
} else if (isGhcr && repoDigest) { } else if (isGhcr && repoDigest) {
const latest = await getGhcrLatestDigest(name, tag) const latest = await getGhcrLatestDigest(name, tag)
if (latest) upToDate = latest === repoDigest if (latest) upToDate = latest === repoDigest
} else if (isLscr && repoDigest) {
const latest = await getLscrLatestDigest(name, tag)
if (latest) upToDate = latest === repoDigest
} }
containers.push({ containers.push({
@@ -26,7 +26,8 @@ export function DockerUpdatesWidget() {
}, []) }, [])
const outdated = containers.filter(c => c.upToDate === false).length 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 ( return (
<div className="card"> <div className="card">
@@ -58,7 +59,7 @@ export function DockerUpdatesWidget() {
{c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint} {c.image}:{c.tag.startsWith('sha256:') ? c.tag.slice(0, 15) + '…' : c.tag} · {c.endpoint}
</span> </span>
</div> </div>
{c.upToDate === null && c.registry !== 'docker.io' && c.registry !== 'ghcr.io' ? ( {c.upToDate === null && !knownRegistries.includes(c.registry) ? (
<span className="pill pill-blue">ext</span> <span className="pill pill-blue">ext</span>
) : c.upToDate === true ? ( ) : c.upToDate === true ? (
<span className="pill pill-green"></span> <span className="pill pill-green"></span>