网上找别人的镜像太麻烦有时候还不稳定,不如自建(用Cloudflare Workers)
部署代码:
<div>
<div>addEventListener("fetch", (event) => {</div>
<div> event.passThroughOnException();</div>
<div> event.respondWith(handleRequest(event.request));</div>
<div>});</div>
<div>const dockerHub = "https://registry-1.docker.io";</div>
<div>const HTML = `</div>
<div><!DOCTYPE html></div>
<div><html lang="zh-CN"></div>
<div><head></div>
<div> <meta charset="utf-8"></div>
<div> <meta name="viewport" content="width=device-width, initial-scale=1"></div>
<div> <link rel="shortcut icon" href="https://xiaowangye.org/assets/img/favicons/favicon.ico"></div>
<div> <title>Docker 镜像代理使用说明</title></div>
<div> <style></div>
<div> body{font-family:'Roboto',sans-serif;margin:0;padding:0;background-color:#f4f4f4;}.header{background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;padding:.1rem 0;text-align:center;box-shadow:0 2px 4px rgba(0,0,0,0.1);}.container{max-width:800px;margin:40px auto;padding:20px;background-color:#fff;box-shadow:0 4px 8px rgba(0,0,0,0.1);border-radius:10px;}.content{margin-bottom:20px;}.footer{text-align:center;padding:20px 0;background-color:#333;color:#fff;}pre{background-color:#272822;color:#f8f8f2;padding:15px;border-radius:5px;overflow-x:auto;}code{font-family:'Source Code Pro',monospace;}a{font-weight:bold;color:#ffffff;text-decoration:none;}a:hover{text-decoration:underline;}@media (max-width:600px){.container{margin:20px;padding:15px;}.header{padding:15px 0;}}</div>
<div> </style></div>
<div> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet"></div>
<div></head></div>
<div><body></div>
<div><div class="header"></div>
<div><h1>Docker 镜像代理使用说明</h1></div>
<div></div></div>
<div><div class="container"></div>
<div><div class="content"></div>
<div><h3>带镜像仓库地址使用说明</h3><p>使用例子</p></div>
<div><pre><code># 拉取某个官方镜像(不带命名空间)</div>
<div>docker pull stilleshan/frpc:latest</div>
<div># 拉取 代理拉取镜像</div>
<div>docker pull hub.songwqs.top/stilleshan/frpc:latest</div>
<div># 重命名镜像</div>
<div>docker tag hub.songwqs.top/stilleshan/frpc:latest stilleshan/frpc:latest</div>
<div># 删除代理镜像</div>
<div>docker rmi hub.songwqs.top/stilleshan/frpc:latest</div>
<div></code></pre><h3>镜像源方式使用说明</h3><p>1.添加镜像源</p></div>
<div> <pre><code># 添加镜像代理到 Docker 镜像源</div>
<div>sudo tee /etc/docker/daemon.json << EOF</div>
<div>{</div>
<div> "registry-mirrors": ["https://hub.songwqs.top"]</div>
<div>}</div>
<div>EOF</code></pre><p>2.拉取镜像</p></div>
<div><pre><code># 拉取 redis 官方镜像</div>
<div>docker pull redis</div>
<div># 拉取 rabbitmq 非官方镜像</div>
<div>docker pull bitnami/rabbitmq</div>
<div># 拉取 postgresql 官方镜像</div>
<div>docker pull postgresql</code></pre></div>
<div></div></div></div>
<div></body></div>
<div></html></div>
<div>`</div>
<div>const routes = {</div>
<div> // 替换为你的域名</div>
<div> "hub.songwqs.top": dockerHub,</div>
<div>};</div>
<div>function routeByHosts(host) {</div>
<div> if (host in routes) {</div>
<div> return routes[host];</div>
<div> }</div>
<div> return "";</div>
<div>}</div>
<div>async function handleRequest(request) {</div>
<div> const url = new URL(request.url);</div>
<div> if (url.pathname == "/") {</div>
<div> return handleHomeRequest(url.host);</div>
<div> }</div>
<div> const upstream = routeByHosts(url.hostname);</div>
<div> if (!upstream) {</div>
<div> return createNotFoundResponse(routes);</div>
<div> }</div>
<div> const isDockerHub = upstream == dockerHub;</div>
<div> const authorization = request.headers.get("Authorization");</div>
<div> if (url.pathname == "/v2/") {</div>
<div> return handleFirstRequest(upstream, authorization, url.hostname);</div>
<div> }</div>
<div> // get token</div>
<div> if (url.pathname == "/v2/auth") {</div>
<div> return handleAuthRequest(upstream, url, isDockerHub, authorization);</div>
<div> }</div>
<div> // redirect for DockerHub library images</div>
<div> // Example: /v2/busybox/manifests/latest => /v2/library/busybox/manifests/latest</div>
<div> if (isDockerHub) {</div>
<div> const pathParts = url.pathname.split("/");</div>
<div> if (pathParts.length == 5) {</div>
<div> pathParts.splice(2, 0, "library");</div>
<div> const redirectUrl = new URL(url);</div>
<div> redirectUrl.pathname = pathParts.join("/");</div>
<div> return Response.redirect(redirectUrl.toString(), 301);</div>
<div> }</div>
<div> }</div>
<div> return handlePullRequest(upstream, request);</div>
<div>}</div>
<div>function parseAuthenticate(authenticateStr) {</div>
<div> // sample: Bearer realm="https://auth.ipv6.docker.com/token",service="registry.docker.io"</div>
<div> // match strings after =" and before "</div>
<div> const re = /(?<=\=")(?:\\.|[^"\\])*(?=")/g;</div>
<div> const matches = authenticateStr.match(re);</div>
<div> if (matches == null || matches.length < 2) {</div>
<div> throw new Error(`invalid Www-Authenticate Header: ${authenticateStr}`);</div>
<div> }</div>
<div> return {</div>
<div> realm: matches[0],</div>
<div> service: matches[1],</div>
<div> };</div>
<div>}</div>
<div>async function fetchToken(wwwAuthenticate, scope, authorization) {</div>
<div> const url = new URL(wwwAuthenticate.realm);</div>
<div> if (wwwAuthenticate.service.length) {</div>
<div> url.searchParams.set("service", wwwAuthenticate.service);</div>
<div> }</div>
<div> if (scope) {</div>
<div> url.searchParams.set("scope", scope);</div>
<div> }</div>
<div> const headers = new Headers();</div>
<div> if (authorization) {</div>
<div> headers.set("Authorization", authorization);</div>
<div> }</div>
<div> return await fetch(url, { method: "GET", headers: headers });</div>
<div>}</div>
<div>function handleHomeRequest(host) {</div>
<div> // 使用正则表达式将所有{{host}}的出现替换为host变量</div>
<div> const updatedHTML = HTML.replace(/{{host}}/g, host);</div>
<div></div>
<div> return new Response(updatedHTML, {</div>
<div> status: 200,</div>
<div> headers: {</div>
<div> "content-type": "text/html",</div>
<div> }</div>
<div> });</div>
<div>}</div>
<div>async function handlePullRequest(upstream, request) {</div>
<div> const url = new URL(request.url);</div>
<div> const newUrl = new URL(upstream + url.pathname);</div>
<div> const newReq = new Request(newUrl, {</div>
<div> method: request.method,</div>
<div> headers: request.headers,</div>
<div> redirect: "follow",</div>
<div> });</div>
<div> return await fetch(newReq);</div>
<div>}</div>
<div>async function handleFirstRequest(upstream, authorization, hostname) {</div>
<div> const newUrl = new URL(upstream + "/v2/");</div>
<div> const headers = new Headers();</div>
<div> if (authorization) {</div>
<div> headers.set("Authorization", authorization);</div>
<div> }</div>
<div> // check if need to authenticate</div>
<div> const resp = await fetch(newUrl.toString(), {</div>
<div> method: "GET",</div>
<div> headers: headers,</div>
<div> redirect: "follow",</div>
<div> });</div>
<div> if (resp.status === 401) {</div>
<div> headers.set(</div>
<div> "Www-Authenticate",</div>
<div> `Bearer realm="https://${hostname}/v2/auth",service="cloudflare-docker-proxy"`</div>
<div> );</div>
<div> return new Response(JSON.stringify({ message: "Unauthorized" }), {</div>
<div> status: 401,</div>
<div> headers: headers,</div>
<div> });</div>
<div> } else {</div>
<div> return resp;</div>
<div> }</div>
<div>}</div>
<div>async function handleAuthRequest(upstream, url, isDockerHub, authorization) {</div>
<div> const newUrl = new URL(upstream + "/v2/");</div>
<div> const resp = await fetch(newUrl.toString(), {</div>
<div> method: "GET",</div>
<div> redirect: "follow",</div>
<div> });</div>
<div> if (resp.status !== 401) {</div>
<div> return resp;</div>
<div> }</div>
<div> const authenticateStr = resp.headers.get("WWW-Authenticate");</div>
<div> if (authenticateStr === null) {</div>
<div> return resp;</div>
<div> }</div>
<div> const wwwAuthenticate = parseAuthenticate(authenticateStr);</div>
<div> let scope = url.searchParams.get("scope");</div>
<div> // autocomplete repo part into scope for DockerHub library images</div>
<div> // Example: repository:busybox:pull => repository:library/busybox:pull</div>
<div> if (scope && isDockerHub) {</div>
<div> let scopeParts = scope.split(":");</div>
<div> if (scopeParts.length == 3 && !scopeParts[1].includes("/")) {</div>
<div> scopeParts[1] = "library/" + scopeParts[1];</div>
<div> scope = scopeParts.join(":");</div>
<div> }</div>
<div> }</div>
<div> return await fetchToken(wwwAuthenticate, scope, authorization);</div>
<div>}</div>
<div>const createNotFoundResponse = (routes) => new Response(</div>
<div> JSON.stringify({ routes }),</div>
<div> {</div>
<div> status: 404,</div>
<div> headers: {</div>
<div> "Content-Type": "application/json",</div>
<div> },</div>
<div> }</div>
<div>);</div>
</div>