Created
June 24, 2024 06:33
-
-
Save thesadabc/5c8e00793cc28c1b8f7c1d570abdf392 to your computer and use it in GitHub Desktop.
Cloudflare Workers上运行的Docker镜像代理服务。Docker proxy for Cloudflare Workers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 代码改自 https://github.com/ciiiii/cloudflare-docker-proxy/tree/master | |
* 到 https://dash.cloudflare.com/?to=/:account/workers-and-pages 创建worker | |
* 将本代码修改参数后贴到worker里,点击部署即可使用 | |
**/ | |
addEventListener("fetch", (event) => { | |
event.passThroughOnException(); | |
event.respondWith(handleRequest(event.request)); | |
}); | |
const dockerHub = "https://registry-1.docker.io"; | |
const routes = { | |
// 这里要改成你用来访问的域名,可以用它 | |
"dhub.xjp.in": dockerHub, | |
}; | |
function routeByHosts(host) { | |
if (host in routes) { | |
return routes[host]; | |
} | |
return ""; | |
} | |
async function handleRequest(request) { | |
const url = new URL(request.url); | |
const upstream = routeByHosts(url.hostname); | |
if (upstream === "") { | |
return new Response( | |
JSON.stringify({ | |
routes: routes, | |
}), | |
{ | |
status: 404, | |
} | |
); | |
} | |
const isDockerHub = upstream == dockerHub; | |
const authorization = request.headers.get("Authorization"); | |
if (url.pathname == "/v2/") { | |
const newUrl = new URL(upstream + "/v2/"); | |
const headers = new Headers(); | |
if (authorization) { | |
headers.set("Authorization", authorization); | |
} | |
// check if need to authenticate | |
const resp = await fetch(newUrl.toString(), { | |
method: "GET", | |
headers: headers, | |
redirect: "follow", | |
}); | |
if (resp.status === 401) { | |
headers.set( | |
"Www-Authenticate", | |
`Bearer realm="https://${url.hostname}/v2/auth",service="cloudflare-docker-proxy"` | |
); | |
return new Response(JSON.stringify({ message: "UNAUTHORIZED" }), { | |
status: 401, | |
headers: headers, | |
}); | |
} else { | |
return resp; | |
} | |
} | |
// get token | |
if (url.pathname == "/v2/auth") { | |
const newUrl = new URL(upstream + "/v2/"); | |
const resp = await fetch(newUrl.toString(), { | |
method: "GET", | |
redirect: "follow", | |
}); | |
if (resp.status !== 401) { | |
return resp; | |
} | |
const authenticateStr = resp.headers.get("WWW-Authenticate"); | |
if (authenticateStr === null) { | |
return resp; | |
} | |
const wwwAuthenticate = parseAuthenticate(authenticateStr); | |
let scope = url.searchParams.get("scope"); | |
// autocomplete repo part into scope for DockerHub library images | |
// Example: repository:busybox:pull => repository:library/busybox:pull | |
if (scope && isDockerHub) { | |
let scopeParts = scope.split(":"); | |
if (scopeParts.length == 3 && !scopeParts[1].includes("/")) { | |
scopeParts[1] = "library/" + scopeParts[1]; | |
scope = scopeParts.join(":"); | |
} | |
} | |
return await fetchToken(wwwAuthenticate, scope, authorization); | |
} | |
// redirect for DockerHub library images | |
// Example: /v2/busybox/manifests/latest => /v2/library/busybox/manifests/latest | |
if (isDockerHub) { | |
const pathParts = url.pathname.split("/"); | |
if (pathParts.length == 5) { | |
pathParts.splice(2, 0, "library"); | |
const redirectUrl = new URL(url); | |
redirectUrl.pathname = pathParts.join("/"); | |
return Response.redirect(redirectUrl, 301); | |
} | |
} | |
// foward requests | |
const newUrl = new URL(upstream + url.pathname); | |
const newReq = new Request(newUrl, { | |
method: request.method, | |
headers: request.headers, | |
redirect: "follow", | |
}); | |
return await fetch(newReq); | |
} | |
function parseAuthenticate(authenticateStr) { | |
// sample: Bearer realm="https://auth.ipv6.docker.com/token",service="registry.docker.io" | |
// match strings after =" and before " | |
const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g; | |
const matches = authenticateStr.match(re); | |
if (matches == null || matches.length < 2) { | |
throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`); | |
} | |
return { | |
realm: matches[0], | |
service: matches[1], | |
}; | |
} | |
async function fetchToken(wwwAuthenticate, scope, authorization) { | |
const url = new URL(wwwAuthenticate.realm); | |
if (wwwAuthenticate.service.length) { | |
url.searchParams.set("service", wwwAuthenticate.service); | |
} | |
if (scope) { | |
url.searchParams.set("scope", scope); | |
} | |
const headers = new Headers(); | |
if (authorization) { | |
headers.set("Authorization", authorization); | |
} | |
return await fetch(url, { method: "GET", headers: headers }); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment