index.html(файл создан)
@@ -0,0 +1,103 @@ | |||
1 | + | <!DOCTYPE html> | |
2 | + | <html lang="zh-CN"> | |
3 | + | <head> | |
4 | + | <meta charset="utf-8"> | |
5 | + | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
6 | + | <title>V2镜像使用说明</title> | |
7 | + | <style> | |
8 | + | body { | |
9 | + | font-family: 'Roboto', sans-serif; | |
10 | + | margin: 0; | |
11 | + | padding: 0; | |
12 | + | background: linear-gradient(135deg, #e0f7fa, #e1bee7); | |
13 | + | color: #333; | |
14 | + | } | |
15 | + | .header { | |
16 | + | background: linear-gradient(135deg, #667eea, #764ba2); | |
17 | + | color: #fff; | |
18 | + | padding: 20px 0; | |
19 | + | text-align: center; | |
20 | + | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); | |
21 | + | animation: fadeIn 1.5s; | |
22 | + | } | |
23 | + | .container { | |
24 | + | max-width: 800px; | |
25 | + | margin: 40px auto; | |
26 | + | padding: 20px; | |
27 | + | background-color: #fff; | |
28 | + | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
29 | + | border-radius: 10px; | |
30 | + | animation: slideInUp 1s; | |
31 | + | } | |
32 | + | .content { | |
33 | + | margin-bottom: 20px; | |
34 | + | } | |
35 | + | .footer { | |
36 | + | text-align: center; | |
37 | + | padding: 20px 0; | |
38 | + | background-color: #333; | |
39 | + | color: #fff; | |
40 | + | animation: fadeIn 1.5s; | |
41 | + | } | |
42 | + | pre { | |
43 | + | background-color: #272822; | |
44 | + | color: #f8f8f2; | |
45 | + | padding: 15px; | |
46 | + | border-radius: 5px; | |
47 | + | overflow-x: auto; | |
48 | + | } | |
49 | + | code { | |
50 | + | font-family: 'Source Code Pro', monospace; | |
51 | + | } | |
52 | + | a { | |
53 | + | color: #4CAF50; | |
54 | + | text-decoration: none; | |
55 | + | transition: color 0.3s; | |
56 | + | } | |
57 | + | a:hover { | |
58 | + | color: #388E3C; | |
59 | + | text-decoration: underline; | |
60 | + | } | |
61 | + | @media (max-width: 600px) { | |
62 | + | .container { | |
63 | + | margin: 20px; | |
64 | + | padding: 15px; | |
65 | + | } | |
66 | + | .header { | |
67 | + | padding: 15px 0; | |
68 | + | } | |
69 | + | } | |
70 | + | @keyframes fadeIn { | |
71 | + | from { opacity: 0; } | |
72 | + | to { opacity: 1; } | |
73 | + | } | |
74 | + | @keyframes slideInUp { | |
75 | + | from { transform: translateY(20px); opacity: 0; } | |
76 | + | to { transform: translateY(0); opacity: 1; } | |
77 | + | } | |
78 | + | </style> | |
79 | + | <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&family=Source+Code+Pro:wght@400;700&display=swap" rel="stylesheet"> | |
80 | + | </head> | |
81 | + | <body> | |
82 | + | <div class="header"> | |
83 | + | <h1>ghcr镜像使用说明</h1> | |
84 | + | </div> | |
85 | + | <div class="container"> | |
86 | + | <div class="content"> | |
87 | + | <p>为了加速镜像拉取,你可以使用以下命令设置 registry mirror:</p> | |
88 | + | <pre><code>sudo tee /etc/docker/daemon.json <<EOF | |
89 | + | { | |
90 | + | "registry-mirrors": ["https://{{host}}"] | |
91 | + | } | |
92 | + | EOF</code></pre> | |
93 | + | <p>为了避免 Worker 用量耗尽,你可以手动 pull 镜像然后 re-tag 之后 push 至本地镜像仓库:</p> | |
94 | + | <pre><code>docker pull {{host}}/library/alpine:latest # 拉取 library 镜像 | |
95 | + | docker pull {{host}}/coredns/coredns:latest # 拉取 coredns 镜像</code></pre> | |
96 | + | </div> | |
97 | + | </div> | |
98 | + | <div class="footer"> | |
99 | + | <p>Powered by Cloudflare Workers</p> | |
100 | + | <p>免责声明:本服务及其内容按“现状”提供,不提供任何形式的明示或暗示的保证,包括但不限于对适销性、适用性的任何保证。使用本服务存在的风险由用户自行承担。</p> | |
101 | + | </div> | |
102 | + | </body> | |
103 | + | </html> |
worker.js(файл создан)
@@ -0,0 +1,41 @@ | |||
1 | + | import HTML from './index.html'; | |
2 | + | ||
3 | + | export default { | |
4 | + | async fetch(request) { | |
5 | + | const url = new URL(request.url); | |
6 | + | const path = url.pathname; | |
7 | + | const originalHost = request.headers.get("host"); | |
8 | + | const registryHost = "ghcr.io"; | |
9 | + | ||
10 | + | if (path.startsWith("/v2/")) { | |
11 | + | const headers = new Headers(request.headers); | |
12 | + | headers.set("host", registryHost); | |
13 | + | ||
14 | + | const registryUrl = `https://${registryHost}${path}`; | |
15 | + | const registryRequest = new Request(registryUrl, { | |
16 | + | method: request.method, | |
17 | + | headers: headers, | |
18 | + | body: request.body, | |
19 | + | redirect: "follow", | |
20 | + | }); | |
21 | + | ||
22 | + | const registryResponse = await fetch(registryRequest); | |
23 | + | const responseHeaders = new Headers(registryResponse.headers); | |
24 | + | responseHeaders.set("access-control-allow-origin", originalHost); | |
25 | + | responseHeaders.set("access-control-allow-headers", "Authorization"); | |
26 | + | ||
27 | + | return new Response(registryResponse.body, { | |
28 | + | status: registryResponse.status, | |
29 | + | statusText: registryResponse.statusText, | |
30 | + | headers: responseHeaders, | |
31 | + | }); | |
32 | + | } else { | |
33 | + | return new Response(HTML.replace(/{{host}}/g, originalHost), { | |
34 | + | status: 200, | |
35 | + | headers: { | |
36 | + | "content-type": "text/html" | |
37 | + | } | |
38 | + | }); | |
39 | + | } | |
40 | + | } | |
41 | + | } |
Новее
Позже