miller
发布于

docker pull镜像 代理 (持续维护)

方法0: 通过deno配置个ts 转发代理--推荐!!!

https://linux.do/t/topic/432248

const baseUrl = "your-cutom.domain"; // 这就是 deno 上建的项目名字。 起个好用、好记的名字

const routes = {
  [baseUrl]: "https://registry-1.docker.io",
  [`docker.${baseUrl}`]: "https://registry-1.docker.io",
  [`quay.${baseUrl}`]: "https://quay.io",
  [`gcr.${baseUrl}`]: "https://gcr.io",
  [`k8s-gcr.${baseUrl}`]: "https://k8s.gcr.io",
  [`k8s.${baseUrl}`]: "https://registry.k8s.io",
  [`ghcr.${baseUrl}`]: "https://ghcr.io",
  [`cloudsmith.${baseUrl}`]: "https://docker.cloudsmith.io",
};

function routeByHosts(host: string): string {
  return routes[host] || "https://registry-1.docker.io";
}

/**
 * 兼容官方镜像的匹配
 * Docker official images like "golang" actually live under "/library/golang".
 * So if the user’s request is /v2/golang/... we rewrite to /v2/library/golang/...
 */
function adjustDockerPath(pathname: string): string {
  console.log(`[adjustDockerPath] Original path: ${pathname}`);

  // 匹配 /v2/*/{manifests,blobs}/* 格式的路径
  const regex = /^\/v2\/([^\/]+)\/(manifests|blobs)\/(.+)$/;
  const match = pathname.match(regex);

  if (!match) {
    // console.log(`[adjustDockerPath] Path does not match pattern: ${pathname}`);
    return pathname;
  }

  const imageName = match[1];
  const type = match[2]; // manifests 或 blobs
  const suffix = match[3]; // 标签或哈希值
  // console.log(`[adjustDockerPath] Extracted image name: ${imageName}, type: ${type}, suffix: ${suffix}`);

  // 判断是否为官方镜像
  if (!imageName.includes('/')) {
    const adjustedPath = `/v2/library/${imageName}/${type}/${suffix}`;
    // console.log(`[adjustDockerPath] Adjusted path (official image): ${adjustedPath}`);
    return adjustedPath;
  }

  // console.log(`[adjustDockerPath] Adjusted path (non-official image): ${pathname}`);
  return pathname;
}



async function fetchToken(repository: string, authorization: string | null): Promise<Response> {
   // Add "library/" prefix for official images
  if (!repository.includes('/')) {
    repository = `library/${repository}`;
  }
  // Always talk to Docker Hub auth server
  const tokenUrl = new URL("https://auth.docker.io/token");
  tokenUrl.searchParams.set("service", "registry.docker.io");
  tokenUrl.searchParams.set("scope", `repository:${repository}:pull`);

  // console.log(`Token request URL: ${tokenUrl}`);
  // console.log(`Requesting token for repository: ${repository}`);

  const headers = new Headers();
  if (authorization && authorization.startsWith("Basic ")) {
    // Only forward Basic credentials
    headers.set("Authorization", authorization);
    // console.log("Using Basic authorization for token request");
  } else if (authorization && authorization.startsWith("Bearer ")) {
    // We ignore existing Bearer tokens, requesting a fresh token from Docker Hub
    // console.log("Bearer token received, requesting new token without forwarding it");
  }

  const tokenResp = await fetch(tokenUrl.toString(), { method: "GET", headers });
  if (!tokenResp.ok) {
    // console.error(`Token fetch failed: ${tokenResp.status}`);
    const errorBody = await tokenResp.text();
    // console.error(`Token error: ${errorBody}`);
  } else {
    const tokenData = await tokenResp.clone().json();
    // console.log(`Token received successfully for ${repository}`);
  }
  return tokenResp;
}

/**
 * For /v2/auth calls, parse `scope=...` to figure out the repository name,
 * then fetch a token with "pull" scope from Docker Hub.
 */
async function handleV2AuthPath(request: Request): Promise<Response> {
  console.log(`\n=== Handling auth request ===`);
  const url = new URL(request.url);
  const authorization = request.headers.get("Authorization");

  let repository = "";
  const scopeParam = url.searchParams.get("scope");
  if (scopeParam) {
    // e.g. "repository:library/golang:pull"
    const match = scopeParam.match(/repository:([^:]+):/);
    if (match) {
      repository = match[1];
    }
  }

  // fallback if somehow no repository was extracted
  if (!repository) {
    repository = "library/unknown";
  }

  return fetchToken(repository, authorization);
}

/**
 * When Docker calls /v2/, it might get a 401 from upstream Docker Hub
 * with a `WWW-Authenticate` header. We typically want to pass that along.
 */
async function handleV2Path(url: URL, authorization: string | null): Promise<Response> {
  // console.log(`\n=== Handling /v2/ request ===`);
  const upstream = routeByHosts(url.hostname);
  const upstreamUrl = new URL(upstream + "/v2/");
  const headers = new Headers();

  if (authorization) {
    headers.set("Authorization", authorization);
  }

  const resp = await fetch(upstreamUrl.toString(), { method: "GET", headers, redirect: "follow" });
  // console.log(`V2 response status: ${resp.status}`);
  // console.log("V2 response headers:", Object.fromEntries(resp.headers.entries()));

  // If upstream returns 401, we should respond with a 401 to Docker
  // that includes the same "WWW-Authenticate" scope.
  if (resp.status === 401) {
    const upstreamAuth = resp.headers.get("WWW-Authenticate");
    if (upstreamAuth) {
      // Just pass it along to Docker so Docker can request the correct scope
      const outHeaders = new Headers(resp.headers);
      // We could override realm= to point to our own /v2/auth, but typically
      // you can just pass the entire upstreamAuth back. Or you can rewrite
      // the realm to your own /v2/auth. But importantly, keep the scope the same.
      outHeaders.set("WWW-Authenticate", rewriteAuthHeaderToSelf(url, upstreamAuth));
      return new Response(await resp.text(), { status: 401, headers: outHeaders });
    }
  }

  return resp;
}

/**
 * The main proxy for regular registry requests, e.g. /v2/library/golang/manifests/latest
 */
async function handleRegistryRequest(request: Request, url: URL, upstream: string, authorization: string | null): Promise<Response> {
  console.log(`\n=== Handling registry request ===`);
  let adjustedPath = url.pathname;

  if (upstream === "https://registry-1.docker.io") {
    // console.log(`Original path: ${url.pathname}`);
    adjustedPath = adjustDockerPath(url.pathname);
    // console.log(`Adjusted path: ${adjustedPath}`);
  }

  const newUrl = new URL(upstream + adjustedPath + url.search);
  // console.log(`Final URL: ${newUrl}`);

  const headers = new Headers(request.headers);
  headers.set("Host", new URL(upstream).host);

  if (authorization) {
    headers.set("Authorization", authorization);
  }

  const resp = await fetch(newUrl.toString(), {
    method: request.method,
    headers,
    body: request.body,
    redirect: "follow",
  });

  // console.log(`Response status: ${resp.status}`);
  if (resp.status === 401) {
    // **IMPORTANT**: If upstream returns 401 for pulling, pass
    // the `WWW-Authenticate` header back to Docker, so Docker
    // knows exactly which scope is needed. 
    const upstreamAuth = resp.headers.get("WWW-Authenticate");
    if (upstreamAuth) {
      // We rewrite the realm= so that Docker calls our /v2/auth instead of docker.io,
      // but keep the same scope= value from upstream.
      const outHeaders = new Headers(resp.headers);
      outHeaders.set("WWW-Authenticate", rewriteAuthHeaderToSelf(url, upstreamAuth));
      // Return 401 with the updated header.
      return new Response(await resp.text(), { status: 401, headers: outHeaders });
    }
  }

  if (!resp.ok) {
    // console.error(`Error response status: ${resp.status}`);
    // console.error("Error response headers:", Object.fromEntries(resp.headers.entries()));
    const errorBody = await resp.clone().text();
    // console.error(`Error body: ${errorBody}`);
  }

  return resp;
}

/**
 * Utility: Docker expects the realm to point to your /v2/auth, but the scope from upstream remains intact.
 * If upstream says:
 *   WWW-Authenticate: Bearer realm="https://auth.docker.io/token",service="registry.docker.io",scope="repository:library/golang:pull"
 * We want something like:
 *   Bearer realm="https://your-proxy-host/v2/auth",service="registry.docker.io",scope="repository:library/golang:pull"
 */
function rewriteAuthHeaderToSelf(url: URL, upstreamHeader: string): string {
  // Typically the upstreamHeader is something like:
  // "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\",scope=\"repository:library/golang:pull\",error=\"insufficient_scope\""
  // We'll replace the realm= with your own /v2/auth endpoint
  const yourAuthEndpoint = `https://${url.hostname}/v2/auth`;
  return upstreamHeader.replace(
    /realm="[^"]+"/,
    `realm="${yourAuthEndpoint}"`
  );
}

async function handleRequest(request: Request): Promise<Response> {
  try {
    const url = new URL(request.url);
    const upstream = routeByHosts(url.hostname);
    const authorization = request.headers.get("Authorization");

    // console.log(`\n=== Request Info ===`);
    // console.log(`Path: ${url.pathname}`);
    // console.log(`Auth present: ${!!authorization}`);

    if (url.pathname === "/v2/") {
      return handleV2Path(url, authorization);
    }

    if (url.pathname === "/v2/auth") {
      return handleV2AuthPath(request);
    }

    // All other /v2/<...> calls get proxied to upstream
    return handleRegistryRequest(request, url, upstream, authorization);

  } catch (err) {
    console.error("Error handling request:", err);
    return new Response("Internal Server Error", { status: 500 });
  }
}

Deno.serve(handleRequest);

方法一: 经过github action 推送到阿里云自己仓库

https://github.com/qbmiller/docker_image_pusher
重命名下: docker tag docker.m.xxx.io/nginx/nginx:latest nginx/nginx:latest

方法二: cloudfare worker . 不过提示有封号风险...

https://github.com/cmliu/CF-Workers-docker.io

方法三: https://github.com/developer-fuck/socks2trojan

没试过,可以试试。
一个高效的协议转换工具,支持将 SOCKS5/HTTP 代理转换为 Trojan 协议。

方法四: https://github.com/dqzboy/Docker-Proxy

自己有阿里云 腾讯与 服务器的话,可以搭建一个 docker proxy 。

以下都已经失效-------------

参考方法自己搞 https://github.com/DaoCloud/public-image-mirror


[root@devtest-service mydata]# docker pull docker.m.daocloud.io/grafana/loki:3.0.0
[root@devtest-service ~]# docker tag docker.m.daocloud.io/grafana/loki:3.0.0 grafana/loki:3.0.0
[root@devtest-service ~]# docker push grafana/loki:3.0.0 //这是推送到仓库,不一定支持 

拉下来名字是 docker.m.daocloud.io/grafana/loki 所以重命名下,方便用
举例:搭建个nginx
docker pull docker.m.daocloud.io/nginx/nginx:latest # 加了前缀
重命名下: docker tag docker.m.daocloud.io/nginx/nginx:latest nginx/nginx:latest

https://github.com/DaoCloud/public-image-mirror

老的不能删。只是别名alias

另一个: https://ghcr.dockerhub.icu/

https://github.com/cmliu/CF-Workers-docker.io

浏览 (904)
点赞
收藏
评论