Files
docker-registry-aio/app/server/utils/registry.ts
T
ysj7705andCursor 0fd00bfa37 Add Docker Registry AIO with Nuxt Token Auth and admin UI.
Compose brings up registry:3 plus a Prisma/SQLite Nuxt app for docker login, ACL management, and image browsing with shadcn-vue.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 16:12:12 +09:00

98 lines
3.0 KiB
TypeScript

const DEFAULT_REGISTRY_URL = 'http://registry:5000'
export function registryBaseUrl(): string {
return (process.env.REGISTRY_INTERNAL_URL || DEFAULT_REGISTRY_URL).replace(/\/$/, '')
}
async function serviceToken(access: RegistryAccess[]): Promise<string> {
const { token } = await signRegistryToken({
subject: 'registry-aio-service',
access,
expiresInSeconds: 120,
})
return token
}
export async function registryFetch(path: string, init: RequestInit = {}, access?: RegistryAccess[]) {
const url = `${registryBaseUrl()}${path.startsWith('/') ? path : `/${path}`}`
const headers = new Headers(init.headers)
if (!headers.has('Accept')) {
headers.set(
'Accept',
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/json',
)
}
const granted =
access ??
([
{ type: 'registry', name: 'catalog', actions: ['*'] },
{ type: 'repository', name: '**', actions: ['*'] },
] satisfies RegistryAccess[])
// Distribution matches exact repository names in token access — for service calls
// we mint tokens with requested repo scopes per operation instead of '**'.
const token = await serviceToken(granted)
headers.set('Authorization', `Bearer ${token}`)
return fetch(url, { ...init, headers })
}
export async function listRepositories(): Promise<string[]> {
const res = await registryFetch(
'/v2/_catalog?n=1000',
{},
[{ type: 'registry', name: 'catalog', actions: ['*'] }],
)
if (!res.ok) {
throw createError({
statusCode: 502,
statusMessage: `Registry catalog failed: ${res.status}`,
})
}
const data = (await res.json()) as { repositories?: string[] }
return data.repositories ?? []
}
export async function listTags(name: string): Promise<string[]> {
const res = await registryFetch(
`/v2/${encodeURI(name)}/tags/list`,
{},
[{ type: 'repository', name, actions: ['pull'] }],
)
if (res.status === 404) return []
if (!res.ok) {
throw createError({
statusCode: 502,
statusMessage: `Registry tags failed: ${res.status}`,
})
}
const data = (await res.json()) as { tags?: string[] | null }
return data.tags ?? []
}
export async function getManifestDigest(name: string, reference: string): Promise<string | null> {
const res = await registryFetch(
`/v2/${encodeURI(name)}/manifests/${encodeURIComponent(reference)}`,
{ method: 'HEAD' },
[{ type: 'repository', name, actions: ['pull'] }],
)
if (!res.ok) return null
return res.headers.get('docker-content-digest')
}
export async function deleteManifest(name: string, digest: string): Promise<void> {
const res = await registryFetch(
`/v2/${encodeURI(name)}/manifests/${encodeURIComponent(digest)}`,
{ method: 'DELETE' },
[{ type: 'repository', name, actions: ['delete', 'pull'] }],
)
if (!res.ok && res.status !== 404) {
throw createError({
statusCode: 502,
statusMessage: `Registry delete failed: ${res.status}`,
})
}
}