Repository: cmliu/CF-Workers-docker.io
Branch: main
Commit: 059ff30b89e5
Files: 3
Total size: 27.4 KB
Directory structure:
gitextract_w56m1j5c/
├── .github/
│ └── workflows/
│ └── sync.yml
├── README.md
└── _worker.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/sync.yml
================================================
name: Upstream Sync
permissions:
contents: write
on:
schedule:
- cron: "0 0 * * *" # every day
workflow_dispatch:
jobs:
sync_latest_from_upstream:
name: Sync latest commits from upstream repo
runs-on: ubuntu-latest
if: ${{ github.event.repository.fork }}
steps:
# Step 1: run a standard checkout action
- name: Checkout target repo
uses: actions/checkout@v3
# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4
with:
upstream_sync_repo: cmliu/CF-Workers-docker.io
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }} # automatically generated, no need to set
# Set test_mode true to run tests instead of the true action!!
test_mode: false
- name: Sync check
if: failure()
run: |
echo "[Error] 由于上游仓库的 workflow 文件变更,导致 GitHub 自动暂停了本次自动更新,你需要手动 Sync Fork 一次,详细教程请查看项目README.md "
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork. Please refer to the project README.md for instructions. "
exit 1
================================================
FILE: README.md
================================================
[**第三方 DockerHub 镜像服务列表**](https://github.com/cmliu/CF-Workers-docker.io?tab=readme-ov-file#%EF%B8%8F-%E7%AC%AC%E4%B8%89%E6%96%B9-dockerhub-%E9%95%9C%E5%83%8F%E6%9C%8D%E5%8A%A1)

# 🐳 CF-Workers-docker.io:Docker仓库镜像代理工具
这个项目是一个基于 Cloudflare Workers 的 Docker 镜像代理工具。它能够中转对 Docker 官方镜像仓库的请求,解决一些访问限制和加速访问的问题。
> [!CAUTION]
> **docker.fxxk.dedyn.io 已被GFW污染,需自行部署使用。**
> [!WARNING]
> 根据 [Cloudflare 协议](https://www.cloudflare.com/zh-cn/terms/) 中,2.2.1 第 (j) use the Services to provide a virtual private network or other similar proxy services.
>
> 使用本服务可能存在被 Cloudflare 封号的潜在风险,请自行斟酌使用风险。
>
> 如果你选择了“根据主机名选择对应的上游地址”方式部署,你可能会:
>
> 被 Netcraft 扫描到,收到警告邮件
>
> 被 Netcraft 同步到 Google Safe Browsing 标记为钓鱼网站
>
> 被 Netcraft 投诉到 Cloudflare 标记为钓鱼网站, 无法正常 pull 镜像
>
> 收到律师函
## 🚀 部署方式
- **Workers** 部署:复制 [_worker.js](https://github.com/cmliu/CF-Workers-docker.io/blob/main/_worker.js) 代码,`保存并部署`即可
- **Pages** 部署:`Fork` 后 `连接GitHub` 一键部署即可
## ⚙️ 如何使用? [视频教程](https://www.youtube.com/watch?v=l2jwq9CagNQ)
例如您的Workers项目域名为:`docker.fxxk.dedyn.io`;
### 1.官方镜像路径前面加域名
```shell
docker pull docker.fxxk.dedyn.io/stilleshan/frpc:latest
```
```shell
docker pull docker.fxxk.dedyn.io/library/nginx:stable-alpine3.19-perl
```
### 2.一键设置镜像加速
修改文件 `/etc/docker/daemon.json`(如果不存在则创建)
```shell
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://docker.fxxk.dedyn.io"] # 请替换为您自己的Worker自定义域名
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
```
### 3. 配置常见仓库的镜像加速
#### 3.1 配置
`Containerd` 较简单,它支持任意 `registry` 的 `mirror`,只需要修改配置文件 `/etc/containerd/config.toml`,添加如下的配置:
```yaml
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://xxxx.xx.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint = ["https://xxxx.xx.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
endpoint = ["https://xxxx.xx.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
endpoint = ["https://xxxx.xx.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."ghcr.io"]
endpoint = ["https://xxxx.xx.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."quay.io"]
endpoint = ["https://xxxx.xx.com"]
```
`Podman` 同样支持任意 `registry` 的 `mirror`,修改配置文件 `/etc/containers/registries.conf`,添加配置:
```yaml
unqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']
[[registry]]
prefix = "docker.io"
insecure = true
location = "registry-1.docker.io"
[[registry.mirror]]
location = "xxxx.xx.com"
[[registry]]
prefix = "registry.k8s.io"
insecure = true
location = "registry.k8s.io"
[[registry.mirror]]
location = "xxxx.xx.com"
[[registry]]
prefix = "k8s.gcr.io"
insecure = true
location = "k8s.gcr.io"
[[registry.mirror]]
location = "xxxx.xx.com"
[[registry]]
prefix = "gcr.io"
insecure = true
location = "gcr.io"
[[registry.mirror]]
location = "xxxx.xx.com"
[[registry]]
prefix = "ghcr.io"
insecure = true
location = "ghcr.io"
[[registry.mirror]]
location = "xxxx.xx.com"
[[registry]]
prefix = "quay.io"
insecure = true
location = "quay.io"
[[registry.mirror]]
location = "xxxx.xx.com"
```
#### 3.3 使用
对于以上配置,k8s 在使用的时候,就可以直接 `pull` 外部无法 pull 的镜像了。
```shell
# 手动可以直接pull配置了mirror的仓库
crictl pull registry.k8s.io/kube-proxy:v1.28.4
docker pull nginx:1.21
```
## 🔧 变量说明
| 变量名 | 示例 | 必填 | 备注 |
|--|--|--|--|
| URL302 | `https://t.me/CMLiussss` |❌| 主页302跳转 |
| URL | `https://www.baidu.com/` |❌| 主页伪装(设为`nginx`则伪装为nginx默认页面) |
| UA | `netcraft` |❌| 支持多元素, 元素之间使用空格或换行作间隔 |
# 🛠️ 第三方 DockerHub 镜像服务
**注意:**
- 以下内容仅做镜像服务的整理与搜集,未做任何安全性检测和验证。
- 使用前请自行斟酌,并根据实际需求进行必要的安全审查。
- 本列表中的任何服务都不做任何形式的安全承诺或保证。
| DockerHub 镜像仓库 | 镜像加地址 |
| ------------------ | ----------- |
| [bestcfipas 镜像服务](https://t.me/bestcfipas/4018) | `https://docker.registry.cyou` |
| | `https://docker-cf.registry.cyou` |
| | `https://registry.lfree.org` |
| [zero_free 镜像服务](https://t.me/zero_free/80) | `https://docker.jsdelivr.fyi` |
| | `https://docker.aeko.cn` |
| [mingyu 镜像服务](https://github.com/ymyuuu/HubP) | `https://hubp.de` |
| [Docker 镜像加速站](https://docker.1panel.live) | `https://docker.1panel.live` |
| [Hub Proxy](https://hub.rat.dev) | `https://hub.rat.dev` |
| [DaoCloud 镜像站](https://github.com/DaoCloud/public-image-mirror) | `https://docker.m.daocloud.io` |
# 🙏 鸣谢
### 💖 赞助支持 - 提供云服务器
- [](https://url.cmliussss.com/dv)
### 🛠 开源代码引用
- [muzihuaner](https://github.com/muzihuaner)
- [V2ex网友](https://global.v2ex.com/t/1007922)
- [ciiiii](https://github.com/ciiiii/cloudflare-docker-proxy)
- [ChatGPT](https://chatgpt.com/)
- [白嫖哥](https://t.me/bestcfipas/1900)
- [zero_free频道](https://t.me/zero_free/80)
- [dongyubin](https://github.com/cmliu/CF-Workers-docker.io/issues/8)
- [kiko923](https://github.com/cmliu/CF-Workers-docker.io/issues/5)
================================================
FILE: _worker.js
================================================
// _worker.js
// Docker镜像仓库主机地址
let hub_host = 'registry-1.docker.io';
// Docker认证服务器地址
const auth_url = 'https://auth.docker.io';
let 屏蔽爬虫UA = ['netcraft'];
// 根据主机名选择对应的上游地址
function routeByHosts(host) {
// 定义路由表
const routes = {
// 生产环境
"quay": "quay.io",
"gcr": "gcr.io",
"k8s-gcr": "k8s.gcr.io",
"k8s": "registry.k8s.io",
"ghcr": "ghcr.io",
"cloudsmith": "docker.cloudsmith.io",
"nvcr": "nvcr.io",
// 测试环境
"test": "registry-1.docker.io",
};
if (host in routes) return [routes[host], false];
else return [hub_host, true];
}
/** @type {RequestInit} */
const PREFLIGHT_INIT = {
// 预检请求配置
headers: new Headers({
'access-control-allow-origin': '*', // 允许所有来源
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法
'access-control-max-age': '1728000', // 预检请求的缓存时间
}),
}
/**
* 构造响应
* @param {any} body 响应体
* @param {number} status 响应状态码
* @param {Object<string, string>} headers 响应头
*/
function makeRes(body, status = 200, headers = {}) {
headers['access-control-allow-origin'] = '*' // 允许所有来源
return new Response(body, { status, headers }) // 返回新构造的响应
}
/**
* 构造新的URL对象
* @param {string} urlStr URL字符串
* @param {string} base URL base
*/
function newUrl(urlStr, base) {
try {
console.log(`Constructing new URL object with path ${urlStr} and base ${base}`);
return new URL(urlStr, base); // 尝试构造新的URL对象
} catch (err) {
console.error(err);
return null // 构造失败返回null
}
}
async function nginx() {
const text = `
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
`
return text;
}
async function searchInterface() {
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Docker Hub 镜像搜索</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
--github-color: rgb(27,86,198);
--github-bg-color: #ffffff;
--primary-color: #0066ff;
--primary-dark: #0052cc;
--gradient-start: #1a90ff;
--gradient-end: #003eb3;
--text-color: #ffffff;
--shadow-color: rgba(0,0,0,0.1);
--transition-time: 0.3s;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%);
padding: 20px;
color: var(--text-color);
overflow-x: hidden;
}
.container {
text-align: center;
width: 100%;
max-width: 800px;
padding: 20px;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 60vh;
animation: fadeIn 0.8s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.github-corner {
position: fixed;
top: 0;
right: 0;
z-index: 999;
transition: transform var(--transition-time) ease;
}
.github-corner:hover {
transform: scale(1.08);
}
.github-corner svg {
fill: var(--github-bg-color);
color: var(--github-color);
position: absolute;
top: 0;
border: 0;
right: 0;
width: 80px;
height: 80px;
filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.2));
}
.logo {
margin-bottom: 20px;
transition: transform var(--transition-time) ease;
animation: float 6s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.logo:hover {
transform: scale(1.08) rotate(5deg);
}
.logo svg {
filter: drop-shadow(0 5px 15px rgba(0, 0, 0, 0.2));
}
.title {
color: var(--text-color);
font-size: 2.3em;
margin-bottom: 10px;
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
font-weight: 700;
letter-spacing: -0.5px;
animation: slideInFromTop 0.5s ease-out 0.2s both;
}
@keyframes slideInFromTop {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
.subtitle {
color: rgba(255, 255, 255, 0.9);
font-size: 1.1em;
margin-bottom: 25px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
line-height: 1.4;
animation: slideInFromTop 0.5s ease-out 0.4s both;
}
.search-container {
display: flex;
align-items: stretch;
width: 100%;
max-width: 600px;
margin: 0 auto;
height: 55px;
position: relative;
animation: slideInFromBottom 0.5s ease-out 0.6s both;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
border-radius: 12px;
overflow: hidden;
}
@keyframes slideInFromBottom {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
#search-input {
flex: 1;
padding: 0 20px;
font-size: 16px;
border: none;
outline: none;
transition: all var(--transition-time) ease;
height: 100%;
}
#search-input:focus {
padding-left: 25px;
}
#search-button {
width: 60px;
background-color: var(--primary-color);
border: none;
cursor: pointer;
transition: all var(--transition-time) ease;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
#search-button svg {
transition: transform 0.3s ease;
stroke: white;
}
#search-button:hover {
background-color: var(--primary-dark);
}
#search-button:hover svg {
transform: translateX(2px);
}
#search-button:active svg {
transform: translateX(4px);
}
.tips {
color: rgba(255, 255, 255, 0.8);
margin-top: 20px;
font-size: 0.9em;
animation: fadeIn 0.5s ease-out 0.8s both;
transition: transform var(--transition-time) ease;
}
.tips:hover {
transform: translateY(-2px);
}
@media (max-width: 768px) {
.container {
padding: 20px 15px;
min-height: 60vh;
}
.title {
font-size: 2em;
}
.subtitle {
font-size: 1em;
margin-bottom: 20px;
}
.search-container {
height: 50px;
}
}
@media (max-width: 480px) {
.container {
padding: 15px 10px;
min-height: 60vh;
}
.github-corner svg {
width: 60px;
height: 60px;
}
.search-container {
height: 45px;
}
#search-input {
padding: 0 15px;
}
#search-button {
width: 50px;
}
#search-button svg {
width: 18px;
height: 18px;
}
.title {
font-size: 1.7em;
margin-bottom: 8px;
}
.subtitle {
font-size: 0.95em;
margin-bottom: 18px;
}
}
</style>
</head>
<body>
<a href="https://github.com/cmliu/CF-Workers-docker.io" target="_blank" class="github-corner" aria-label="View source on Github">
<svg viewBox="0 0 250 250" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
</a>
<div class="container">
<div class="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 18" fill="#ffffff" width="110" height="85">
<path d="M23.763 6.886c-.065-.053-.673-.512-1.954-.512-.32 0-.659.03-1.01.087-.248-1.703-1.651-2.533-1.716-2.57l-.345-.2-.227.328a4.596 4.596 0 0 0-.611 1.433c-.23.972-.09 1.884.403 2.666-.596.331-1.546.418-1.744.42H.752a.753.753 0 0 0-.75.749c-.007 1.456.233 2.864.692 4.07.545 1.43 1.355 2.483 2.409 3.13 1.181.725 3.104 1.14 5.276 1.14 1.016 0 2.03-.092 2.93-.266 1.417-.273 2.705-.742 3.826-1.391a10.497 10.497 0 0 0 2.61-2.14c1.252-1.42 1.998-3.005 2.553-4.408.075.003.148.005.221.005 1.371 0 2.215-.55 2.68-1.01.505-.5.685-.998.704-1.053L24 7.076l-.237-.19Z"></path>
<path d="M2.216 8.075h2.119a.186.186 0 0 0 .185-.186V6a.186.186 0 0 0-.185-.186H2.216A.186.186 0 0 0 2.031 6v1.89c0 .103.083.186.185.186Zm2.92 0h2.118a.185.185 0 0 0 .185-.186V6a.185.185 0 0 0-.185-.186H5.136A.185.185 0 0 0 4.95 6v1.89c0 .103.083.186.186.186Zm2.964 0h2.118a.186.186 0 0 0 .185-.186V6a.186.186 0 0 0-.185-.186H8.1A.185.185 0 0 0 7.914 6v1.89c0 .103.083.186.186.186Zm2.928 0h2.119a.185.185 0 0 0 .185-.186V6a.185.185 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm-5.892-2.72h2.118a.185.185 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186H5.136a.186.186 0 0 0-.186.186v1.89c0 .103.083.186.186.186Zm2.964 0h2.118a.186.186 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186H8.1a.186.186 0 0 0-.186.186v1.89c0 .103.083.186.186.186Zm2.928 0h2.119a.185.185 0 0 0 .185-.186V3.28a.186.186 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm0-2.72h2.119a.186.186 0 0 0 .185-.186V.56a.185.185 0 0 0-.185-.186h-2.119a.186.186 0 0 0-.185.186v1.89c0 .103.083.186.185.186Zm2.955 5.44h2.118a.185.185 0 0 0 .186-.186V6a.185.185 0 0 0-.186-.186h-2.118a.185.185 0 0 0-.185.186v1.89c0 .103.083.186.185.186Z"></path>
</svg>
</div>
<h1 class="title">Docker Hub 镜像搜索</h1>
<p class="subtitle">快速查找、下载和部署 Docker 容器镜像</p>
<div class="search-container">
<input type="text" id="search-input" placeholder="输入关键词搜索镜像,如: nginx, mysql, redis...">
<button id="search-button" title="搜索">
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path d="M13 5l7 7-7 7M5 5l7 7-7 7" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
</button>
</div>
<p class="tips">基于 Cloudflare Workers / Pages 构建,利用全球边缘网络实现毫秒级响应。</p>
</div>
<script>
function performSearch() {
const query = document.getElementById('search-input').value;
if (query) {
window.location.href = '/search?q=' + encodeURIComponent(query);
}
}
document.getElementById('search-button').addEventListener('click', performSearch);
document.getElementById('search-input').addEventListener('keypress', function(event) {
if (event.key === 'Enter') {
performSearch();
}
});
// 添加焦点在搜索框
window.addEventListener('load', function() {
document.getElementById('search-input').focus();
});
</script>
</body>
</html>
`;
return html;
}
export default {
async fetch(request, env, ctx) {
const getReqHeader = (key) => request.headers.get(key); // 获取请求头
let url = new URL(request.url); // 解析请求URL
const userAgentHeader = request.headers.get('User-Agent');
const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null";
if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA));
const workers_url = `https://${url.hostname}`;
// 获取请求参数中的 ns
const ns = url.searchParams.get('ns');
const hostname = url.searchParams.get('hubhost') || url.hostname;
const hostTop = hostname.split('.')[0]; // 获取主机名的第一部分
let checkHost; // 在这里定义 checkHost 变量
// 如果存在 ns 参数,优先使用它来确定 hub_host
if (ns) {
if (ns === 'docker.io') {
hub_host = 'registry-1.docker.io'; // 设置上游地址为 registry-1.docker.io
} else {
hub_host = ns; // 直接使用 ns 作为 hub_host
}
} else {
checkHost = routeByHosts(hostTop);
hub_host = checkHost[0]; // 获取上游地址
}
const fakePage = checkHost ? checkHost[1] : false; // 确保 fakePage 不为 undefined
console.log(`域名头部: ${hostTop} 反代地址: ${hub_host} searchInterface: ${fakePage}`);
// 更改请求的主机名
url.hostname = hub_host;
const hubParams = ['/v1/search', '/v1/repositories'];
if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0) {
// 首页改成一个nginx伪装页
return new Response(await nginx(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
} else if ((userAgent && userAgent.includes('mozilla')) || hubParams.some(param => url.pathname.includes(param))) {
if (url.pathname == '/') {
if (env.URL302) {
return Response.redirect(env.URL302, 302);
} else if (env.URL) {
if (env.URL.toLowerCase() == 'nginx') {
//首页改成一个nginx伪装页
return new Response(await nginx(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
} else return fetch(new Request(env.URL, request));
} else {
if (fakePage) return new Response(await searchInterface(), {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
},
});
}
} else {
// 新增逻辑:/v1/ 路径特殊处理
if (url.pathname.startsWith('/v1/')) {
url.hostname = 'index.docker.io';
} else if (fakePage) {
url.hostname = 'hub.docker.com';
}
if (url.searchParams.get('q')?.includes('library/') && url.searchParams.get('q') != 'library/') {
const search = url.searchParams.get('q');
url.searchParams.set('q', search.replace('library/', ''));
}
const newRequest = new Request(url, request);
return fetch(newRequest);
}
}
// 修改包含 %2F 和 %3A 的请求
if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) {
let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F');
url = new URL(modifiedUrl);
console.log(`handle_url: ${url}`);
}
// 处理token请求
if (url.pathname.includes('/token')) {
let token_parameter = {
headers: {
'Host': 'auth.docker.io',
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
}
};
let token_url = auth_url + url.pathname + url.search;
return fetch(new Request(token_url, request), token_parameter);
}
// 修改 /v2/ 请求路径
if (hub_host == 'registry-1.docker.io' && /^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) {
//url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/');
url.pathname = '/v2/library/' + url.pathname.split('/v2/')[1];
console.log(`modified_url: ${url.pathname}`);
}
// 新增:/v2/、/manifests/、/blobs/、/tags/ 先获取token再请求
if (
url.pathname.startsWith('/v2/') &&
(
url.pathname.includes('/manifests/') ||
url.pathname.includes('/blobs/') ||
url.pathname.includes('/tags/')
|| url.pathname.endsWith('/tags/list')
)
) {
// 提取镜像名
let repo = '';
const v2Match = url.pathname.match(/^\/v2\/(.+?)(?:\/(manifests|blobs|tags)\/)/);
if (v2Match) {
repo = v2Match[1];
}
if (repo) {
const tokenUrl = `${auth_url}/token?service=registry.docker.io&scope=repository:${repo}:pull`;
const tokenRes = await fetch(tokenUrl, {
headers: {
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
}
});
const tokenData = await tokenRes.json();
const token = tokenData.token;
let parameter = {
headers: {
'Host': hub_host,
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
'Authorization': `Bearer ${token}`
},
cacheTtl: 3600
};
if (request.headers.has("X-Amz-Content-Sha256")) {
parameter.headers['X-Amz-Content-Sha256'] = getReqHeader("X-Amz-Content-Sha256");
}
let original_response = await fetch(new Request(url, request), parameter);
let original_response_clone = original_response.clone();
let original_text = original_response_clone.body;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
if (new_response_headers.get("Www-Authenticate")) {
let auth = new_response_headers.get("Www-Authenticate");
let re = new RegExp(auth_url, 'g');
new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
}
if (new_response_headers.get("Location")) {
const location = new_response_headers.get("Location");
console.info(`Found redirection location, redirecting to ${location}`);
return httpHandler(request, location, hub_host);
}
let response = new Response(original_text, {
status,
headers: new_response_headers
});
return response;
}
}
// 构造请求参数
let parameter = {
headers: {
'Host': hub_host,
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
},
cacheTtl: 3600 // 缓存时间
};
// 添加Authorization头
if (request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
// 添加可能存在字段X-Amz-Content-Sha256
if (request.headers.has("X-Amz-Content-Sha256")) {
parameter.headers['X-Amz-Content-Sha256'] = getReqHeader("X-Amz-Content-Sha256");
}
// 发起请求并处理响应
let original_response = await fetch(new Request(url, request), parameter);
let original_response_clone = original_response.clone();
let original_text = original_response_clone.body;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
// 修改 Www-Authenticate 头
if (new_response_headers.get("Www-Authenticate")) {
let auth = new_response_headers.get("Www-Authenticate");
let re = new RegExp(auth_url, 'g');
new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
}
// 处理重定向
if (new_response_headers.get("Location")) {
const location = new_response_headers.get("Location");
console.info(`Found redirection location, redirecting to ${location}`);
return httpHandler(request, location, hub_host);
}
// 返回修改后的响应
let response = new Response(original_text, {
status,
headers: new_response_headers
});
return response;
}
};
/**
* 处理HTTP请求
* @param {Request} req 请求对象
* @param {string} pathname 请求路径
* @param {string} baseHost 基地址
*/
function httpHandler(req, pathname, baseHost) {
const reqHdrRaw = req.headers;
// 处理预检请求
if (req.method === 'OPTIONS' &&
reqHdrRaw.has('access-control-request-headers')
) {
return new Response(null, PREFLIGHT_INIT);
}
let rawLen = '';
const reqHdrNew = new Headers(reqHdrRaw);
reqHdrNew.delete("Authorization"); // 修复s3错误
const refer = reqHdrNew.get('referer');
let urlStr = pathname;
const urlObj = newUrl(urlStr, 'https://' + baseHost);
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
};
return proxy(urlObj, reqInit, rawLen);
}
/**
* 代理请求
* @param {URL} urlObj URL对象
* @param {RequestInit} reqInit 请求初始化对象
* @param {string} rawLen 原始长度
*/
async function proxy(urlObj, reqInit, rawLen) {
const res = await fetch(urlObj.href, reqInit);
const resHdrOld = res.headers;
const resHdrNew = new Headers(resHdrOld);
// 验证长度
if (rawLen) {
const newLen = resHdrOld.get('content-length') || '';
const badLen = (rawLen !== newLen);
if (badLen) {
return makeRes(res.body, 400, {
'--error': `bad len: ${newLen}, except: ${rawLen}`,
'access-control-expose-headers': '--error',
});
}
}
const status = res.status;
resHdrNew.set('access-control-expose-headers', '*');
resHdrNew.set('access-control-allow-origin', '*');
resHdrNew.set('Cache-Control', 'max-age=1500');
// 删除不必要的头
resHdrNew.delete('content-security-policy');
resHdrNew.delete('content-security-policy-report-only');
resHdrNew.delete('clear-site-data');
return new Response(res.body, {
status,
headers: resHdrNew
});
}
async function ADD(envadd) {
var addtext = envadd.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号
if (addtext.charAt(0) == ',') addtext = addtext.slice(1);
if (addtext.charAt(addtext.length - 1) == ',') addtext = addtext.slice(0, addtext.length - 1);
const add = addtext.split(',');
return add;
}
gitextract_w56m1j5c/ ├── .github/ │ └── workflows/ │ └── sync.yml ├── README.md └── _worker.js
SYMBOL INDEX (10 symbols across 1 files)
FILE: _worker.js
function routeByHosts (line 11) | function routeByHosts(host) {
constant PREFLIGHT_INIT (line 32) | const PREFLIGHT_INIT = {
function makeRes (line 47) | function makeRes(body, status = 200, headers = {}) {
function newUrl (line 57) | function newUrl(urlStr, base) {
function nginx (line 67) | async function nginx() {
function searchInterface (line 98) | async function searchInterface() {
method fetch (line 415) | async fetch(request, env, ctx) {
function httpHandler (line 652) | function httpHandler(req, pathname, baseHost) {
function proxy (line 690) | async function proxy(urlObj, reqInit, rawLen) {
function ADD (line 723) | async function ADD(envadd) {
Condensed preview — 3 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
{
"path": ".github/workflows/sync.yml",
"chars": 1340,
"preview": "name: Upstream Sync\n\npermissions:\n contents: write\n\non:\n schedule:\n - cron: \"0 0 * * *\" # every day\n workflow_disp"
},
{
"path": "README.md",
"chars": 5150,
"preview": "[**第三方 DockerHub 镜像服务列表**](https://github.com/cmliu/CF-Workers-docker.io?tab=readme-ov-file#%EF%B8%8F-%E7%AC%AC%E4%B8%89"
},
{
"path": "_worker.js",
"chars": 21590,
"preview": "// _worker.js\n\n// Docker镜像仓库主机地址\nlet hub_host = 'registry-1.docker.io';\n// Docker认证服务器地址\nconst auth_url = 'https://auth."
}
]
About this extraction
This page contains the full source code of the cmliu/CF-Workers-docker.io GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 3 files (27.4 KB), approximately 10.1k tokens, and a symbol index with 10 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.