//by zgcwkj const HTML = ` 加速服務
GitHub 文件加速
Docker 鏡像加速

GitHub文件鏈接帶不帶協議頭都可以,支持release、archive以及文件,右鍵複制出來的鏈接都是符合標准的,更多用法、clone加速請參考這篇文章

release、archive使用cf加速,文件會跳轉至JsDelivr

注意,不支持項目文件夾

分支源碼:https://github.com/hunshcn/project/archive/master.zip

release源碼:https://github.com/hunshcn/project/archive/v0.1.0.tar.gz

release文件:https://github.com/hunshcn/project/releases/download/v0.1.0/example.zip

分支文件:https://github.com/hunshcn/project/blob/master/filename

為了加速鏡像拉取,你可以使用以下命令設置registery mirror:

sudo tee /etc/docker/daemon.json <<EOF
{
    "registry-mirrors": ["https://{{host}}"]
}
EOF

為了避免 Worker 用量耗盡,你可以手動 pull 鏡像然後 re-tag 之後 push 至本地鏡像倉庫:

docker pull {{host}}/library/alpine:latest # 拉取 library 鏡像
docker pull {{host}}/coredns/coredns:latest # 拉取 library 鏡像

Blog: zgcwkj

GitHub: hunshcn/gh-proxy

Docker: Doublemine/container-registry-worker

` const PREFIX = '/'; const Config = { jsdelivr: 0 }; const whiteList = []; const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i; const exp2 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob|raw)\/.*$/i; const exp3 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i; const exp4 = /^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+?\/.+$/i; const exp5 = /^(?:https?:\/\/)?gist\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+$/i; const exp6 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/tags.*$/i; /** @type {RequestInit} */ const PREFLIGHT_INIT = { // @ts-ignore status: 204, headers: new Headers({ 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET, POST, PUT, PATCH, TRACE, DELETE, HEAD, OPTIONS', 'access-control-max-age': '1728000', }), }; /** * Create a new response. * @param {any} body * @param {number} [status=200] * @param {Object} headers * @returns {Response} */ function makeResponse(body, status = 200, headers = {}) { headers['access-control-allow-origin'] = '*'; return new Response(body, { status, headers }); } /** * Create a new URL object. * @param {string} urlStr * @returns {URL|null} */ function createURL(urlStr) { try { return new URL(urlStr); } catch (err) { return null; } } addEventListener('fetch', (event) => { event.respondWith(handleFetchEvent(event).catch(err => makeResponse(`cfworker error:\n${err.stack}`, 502))); }); /** * Handle the fetch event. * @param {FetchEvent} event * @returns {Promise} */ async function handleFetchEvent(event) { const req = event.request; const url = new URL(req.url); if (url.pathname.startsWith('/v2/') || url.pathname.startsWith('/token')) { return handleDockerProxy(req, url); } if (url.pathname.startsWith(PREFIX)) { return handleGitHubProxy(req, url); } return makeResponse('Not Found', 404); } /** * Handle token requests and Docker proxy. * @param {Request} req * @param {URL} url * @returns {Promise} */ async function handleDockerProxy(req, url) { const path = url.pathname; const DOCKER_HUB = "registry-1.docker.io"; const DOCKER_AUTH = "auth.docker.io"; let targetHost = DOCKER_HUB; let targetUrl = ""; // 路由分發 if (path.startsWith("/token")) { // 認證請求 -> auth.docker.io targetHost = DOCKER_AUTH; targetUrl = `https://${DOCKER_AUTH}${path}${url.search}`; } else { // 鏡像請求 -> registry-1.docker.io targetHost = DOCKER_HUB; targetUrl = `https://${DOCKER_HUB}${path}`; } const headers = new Headers(req.headers); headers.set("Host", targetHost); const proxyRequest = new Request(targetUrl, { method: req.method, headers: headers, body: req.body, redirect: "follow", }); try { const response = await fetch(proxyRequest); const responseHeaders = new Headers(response.headers); // 重寫 Www-Authenticate // 將 Docker 官方要求的認證地址,替換為 Worker 的地址 const wwwAuth = responseHeaders.get("Www-Authenticate"); if (wwwAuth) { const newAuth = wwwAuth.replace('realm="https://auth.docker.io/token"', `realm="https://${url.hostname}/token"`); responseHeaders.set("Www-Authenticate", newAuth); } // 處理跨域 responseHeaders.set("access-control-allow-origin", "*"); responseHeaders.set("access-control-allow-headers", "Authorization"); return new Response(response.body, { status: response.status, statusText: response.statusText, headers: responseHeaders, }); } catch (err) { return makeResponse(`Docker Proxy Error: ${err.message}`, 500); } } /** * Handle GitHub proxy requests. * @param {Request} req * @param {URL} url * @returns {Promise} */ async function handleGitHubProxy(req, url) { let path = url.searchParams.get('q'); if (path) { return Response.redirect('https://' + url.host + PREFIX + path, 301); } path = url.href.substr(url.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://'); if (checkUrl(path)) { return httpHandler(req, path); } else if (path.search(exp2) === 0) { if (Config.jsdelivr) { const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh'); return Response.redirect(newUrl, 302); } else { path = path.replace('/blob/', '/raw/'); return httpHandler(req, path); } } else if (path.search(exp4) === 0) { const newUrl = path.replace(/(?<=com\/.+?\/.+?)\/(.+?\/)/, '@$1').replace(/^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com/, 'https://cdn.jsdelivr.net/gh'); return Response.redirect(newUrl, 302); } else { return makeResponse(HTML.replace(/{{host}}/g, url.host), 200, { "content-type": "text/html" }); } } /** * Check if the URL matches GitHub patterns. * @param {string} url * @returns {boolean} */ function checkUrl(url) { return [exp1, exp2, exp3, exp4, exp5, exp6].some(exp => url.search(exp) === 0); } /** * Handle HTTP requests. * @param {Request} req * @param {string} pathname * @returns {Promise} */ async function httpHandler(req, pathname) { if (req.method === 'OPTIONS' && req.headers.has('access-control-request-headers')) { return new Response(null, PREFLIGHT_INIT); } const headers = new Headers(req.headers); let flag = !whiteList.length; for (const i of whiteList) { if (pathname.includes(i)) { flag = true; break; } } if (!flag) { return new Response('blocked', { status: 403 }); } if (pathname.search(/^https?:\/\//) !== 0) { pathname = 'https://' + pathname; } const url = createURL(pathname); return proxyRequest(url, { method: req.method, headers, body: req.body }); } /** * Proxy a request. * @param {URL} url * @param {RequestInit} reqInit * @returns {Promise} */ async function proxyRequest(url, reqInit) { const response = await fetch(url.href, reqInit); const responseHeaders = new Headers(response.headers); if (responseHeaders.has('location')) { const location = responseHeaders.get('location'); if (checkUrl(location)) { responseHeaders.set('location', PREFIX + location); } else { reqInit.redirect = 'follow'; return proxyRequest(createURL(location), reqInit); } } responseHeaders.set('access-control-expose-headers', '*'); responseHeaders.set('access-control-allow-origin', '*'); responseHeaders.delete('content-security-policy'); responseHeaders.delete('content-security-policy-report-only'); responseHeaders.delete('clear-site-data'); return new Response(response.body, { status: response.status, headers: responseHeaders, }); }