|  | 
 
| 背景 在中国,访问 GitHub 速度受限,导致下载代码库和克隆项目缓慢。即使使用第三方镜像,也存在稳定性和可靠性问题。为了解决这一问题,我决定自部署一个 GitHub 镜像,并分享部署过程。
 项目简介
 我参考了前辈的镜像代码,但发现无法实现登录。因此,我使用 Cloudflare Workers 重新构建了镜像,通过 Cloudflare 全球 CDN 网络,将 GitHub 请求代理到最近的数据中心,从而加速访问速度。
 下面是代码:
 const upstream = 'github.com'
 const upstream_path = '/'
 const upstream_mobile = 'github.com'
 const blocked_region = ['KP', 'SY', 'PK', 'CU']
 const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
 const https = true
 const replace_dict = {
 '$upstream': '$custom_domain',
 '//github.com': '',
 'https://github.com': ''
 }
 addEventListener('fetch', event => {
 event.respondWith(handleRequest(event.request))
 })
 async function handleRequest(request) {
 const region = request.headers.get('cf-ipcountry').toUpperCase()
 const ip_address = request.headers.get('cf-connecting-ip')
 const user_agent = request.headers.get('user-agent')
 
 if (blocked_region.includes(region)) {
 return new Response('Access denied: WorkersProxy is not available in your region yet.', { status: 403 })
 } else if (blocked_ip_address.includes(ip_address)) {
 return new Response('Access denied: Your IP address is blocked by WorkersProxy.', { status: 403 })
 }
 
 const requestURL = new URL(request.url)
 const path = requestURL.pathname
 const destURL = new URL((https ? 'https://' : 'http://') + upstream + path)
 
 // 添加原始查询参数
 destURL.search = requestURL.search
 
 const upstreamDomain = await device_status(user_agent) ? upstream : upstream_mobile
 const newRequestHeaders = new Headers(request.headers)
 newRequestHeaders.set('Host', upstreamDomain)
 newRequestHeaders.set('Referer', 'https://' + upstreamDomain)
 
 // 处理Origin头
 const origin = request.headers.get('Origin')
 if (origin) {
 newRequestHeaders.set('Origin', 'https://' + upstreamDomain)
 }
 
 // 处理ZIP下载请求
 if (path.endsWith('.zip') || path.includes('/archive/')) {
 return handleZipDownload(destURL, newRequestHeaders)
 }
 
 // 处理git操作
 if (path.includes('/info/refs') || path.includes('/git-upload-pack')) {
 return handleGitOperation(destURL, request, newRequestHeaders)
 }
 
 let response = await fetch(destURL.href, {
 method: request.method,
 headers: newRequestHeaders,
 body: request.body,
 redirect: 'manual',
 })
 
 let newResponseHeaders = new Headers(response.headers)
 newResponseHeaders.set('Access-Control-Allow-Origin', '*')
 newResponseHeaders.set('Access-Control-Allow-Credentials', 'true')
 newResponseHeaders.delete('Content-Security-Policy')
 newResponseHeaders.delete('Content-Security-Policy-Report-Only')
 newResponseHeaders.delete('Clear-Site-Data')
 
 // 处理重定向
 if ([301, 302, 307, 308].includes(response.status)) {
 const location = newResponseHeaders.get('Location')
 if (location) {
 const redirectURL = new URL(location)
 redirectURL.host = requestURL.host
 newResponseHeaders.set('Location', redirectURL.href)
 }
 return new Response(null, {
 status: response.status,
 headers: newResponseHeaders
 })
 }
 
 const content_type = newResponseHeaders.get('content-type')
 let body = await response.text()
 
 // 替换响应中的域名
 for (let key in replace_dict) {
 const replaceKey = key === '$upstream' ? upstreamDomain : key
 const replaceValue = replace_dict[key] === '$custom_domain' ? requestURL.host : replace_dict[key]
 body = body.replace(new RegExp(replaceKey, 'g'), replaceValue)
 }
 
 return new Response(body, {
 status: response.status,
 headers: newResponseHeaders
 })
 }
 async function handleZipDownload(destURL, headers) {
 const response = await fetch(destURL.href, {
 headers: headers,
 redirect: 'follow',
 })
 
 if (response.ok) {
 const newHeaders = new Headers(response.headers)
 newHeaders.set('Content-Disposition', response.headers.get('Content-Disposition') || 'attachment')
 return new Response(response.body, {
 status: response.status,
 headers: newHeaders
 })
 } else {
 return new Response('Failed to download ZIP file', { status: 404 })
 }
 }
 async function handleGitOperation(destURL, request, headers) {
 const response = await fetch(destURL.href, {
 method: request.method,
 headers: headers,
 body: request.body,
 redirect: 'follow',
 })
 
 if (response.ok) {
 const newHeaders = new Headers(response.headers)
 newHeaders.set('Cache-Control', 'no-cache')
 return new Response(response.body, {
 status: response.status,
 headers: newHeaders
 })
 } else {
 return new Response('Git operation failed', { status: response.status })
 }
 }
 async function device_status(user_agent_info) {
 const agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]
 return !agents.some(agent => user_agent_info.includes(agent))
 }
 
 | 
 |