极简 GitHub Porxy 支持GitHub脚本的无限嵌套调用

前言

当我看到这个DD系统的项目时 https://github.com/bin456789/reinstall
我被它的安装命令吸引了.
curl -O https://raw.githubusercontent.com/bin456789/reinstall/main/reinstall.sh || wget -O ${_##*/} $_
找GPT解析了一下原理, 是为了考虑到有些linux系统默认有wget,有些默认有curl .
我准备把我的极简一键脚本的安装方式都改成这样.
curl -LO https://github.com/crazypeace/xray-vless-reality/raw/main/install.sh || wget -O ${_##*/}$_ && bash install.sh 4 8443
然后在脚本的一开始, 安装 curl 和 wget.
apt-get -y install curl wget -qq
我倾向于显示错误内容, 这样在小白反馈问题的时候, 直接发截图或日志就行了. 所以curl和wget没有加静默参数.


复杂的脚本命令, 在结合我的ghproxy时, 如果要实现嵌套github脚本调用, 现有的方案会比较难处理.
我准备改用 WJQSERVER-ghproxy 的思路.
在ghproxy 本身的处理过程中, 针对  .sh 的资源, 全量查找替换, 把访问github资源的链接, 都再套一次自己 ghproxy. 然后再返回.

不影响原来使用 gh-proxy 的用户.

worker.js

我想了一下, 
基于 cloudflare 的 woker, 开发 一个专门 反向代理  github 的工具
1. 本代理 接收的 path部分 应该是一个 http:// 或者 https://
2. 如果 path部分 不是 http:// 或者 https:// 开头
那么加上 http:// 或者 https:// 
3. 判断 本代理 接收的 链接 是否 github
判断方法为:
链接 的域名部分 应该是 git 开头的主域名
如 
github.com
raw.githubusercontent.com
api.github.com
gist.github.com
codeload.github.com
avatars.githubusercontent.com
assets-cdn.github.com
这些域名的 主域名 都是  git 开头的
4. 在获取需要反向代理的内容后
检查 path 是否以 .sh 结尾, 来判断 是否 脚本文件
5. 对于 .sh 结尾的脚本文件
对文本内容进行查找替换
将 github 的链接前面都加上 本代理的域名, 
这样可以解决脚本嵌套使用的场景
判断 是否 github 链接的方法 参考 第3步
把上面这些需求发给GPT. 
走读代码, 提一些小修小改的要求.
就得到了我们的 worker.js
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});

/**
 * 处理所有传入的请求
 * @param {Request} request
 */
async function handleRequest(request) {
  const url = new URL(request.url);
  const workerUrl = url.origin; // 获取 worker 自己的域名, e.g., https://my-worker.example.com

  // 1. 本代理 接受的 path 部分 应该是一个 http:// 或者 https://
  // 我们从 path 中提取目标 URL
  let path = url.pathname.substring(1); // 移除开头的 '/'

  if (!path || path === 'favicon.ico') {
    return new Response('使用方法: ' + workerUrl + '/<您要代理的GitHub链接>', { status: 400 });
  }

  // 2. 如果 path 部分 不是 http:// 或者 https:// 开头, 那么加上 https://
  if (!path.startsWith('http://') && !path.startsWith('https://')) {
    path = 'https://' + path;
  }

  let targetUrl;
  try {
    targetUrl = new URL(path);
  } catch (e) {
    return new Response('路径中包含无效的 URL', { status: 400 });
  }

  // 3. 判断 本代理 接受的 链接 是否 github
  if (!isGitHubDomain(targetUrl.hostname)) {
    return new Response('访问被拒绝:此代理仅支持 GitHub 相关域名。', { status: 403 });
  }

  // 准备转发请求
  // GET 或 HEAD 方法不能有 body
  const hasBody = request.method === 'POST' || request.method === 'PUT' || request.method === 'PATCH';
  
  const response = await fetch(targetUrl.toString(), {
    method: request.method,
    headers: request.headers,
    body: hasBody ? request.body : null,
    redirect: 'follow',
  });

  // 复制响应头,并设置 CORS
  const newHeaders = new Headers(response.headers);
  newHeaders.set('access-control-allow-origin', '*');
  newHeaders.set('access-control-allow-headers', '*');
  newHeaders.set('access-control-allow-methods', '*');

  // 4. 检查 path 是否以 .sh 结尾
  const finalUrl = new URL(response.url);
  const isScript = finalUrl.pathname.endsWith('.sh');

  // 5. 对于 .sh 结尾的脚本文件 (并且请求成功)
  if (isScript && response.status === 200) {
    let bodyText = await response.text();

    // ********** git.io 短链 ************
    // 修复 git.io 链接:[空格]git.io 替换为 [空格]https://git.io
    bodyText = bodyText.replace(/(\s)(git\.io)/g, '$1https://$2');
    // **********************************

    // 对所有 GitHub 链接进行查找替换 (嵌套代理)
    // 匹配所有 https?://... 链接
    const urlRegex = /(https?:\/\/[^\s"'`()<>]+)/g;

    bodyText = bodyText.replace(urlRegex, (match) => {
      try {
        // 'match' 是一个完整的 URL, e.g., "https://github.com/foo"
        const linkUrl = new URL(match);
        
        // 使用 isGitHubDomain 函数来判断
        if (isGitHubDomain(linkUrl.hostname)) {
          // 如果是 GitHub 链接,添加代理前缀
          return `${workerUrl}/${match}`;
        } else {
          // 如果不是,保持原样
          return match;
        }
      } catch (e) {
        // 如果 URL 解析失败 (例如,它可能只是看起来像 URL 的文本),保持原样
        return match;
      }
    });

    // 因为修改了内容,所以 content-length 头部失效了,删除它
    newHeaders.delete('content-length');

    return new Response(bodyText, {
      status: response.status,
      statusText: response.statusText,
      headers: newHeaders,
    });
  }

  // 对于非 .sh 文件或非 200 状态码,直接返回(已修改 CORS 和 Location 头部)
  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: newHeaders,
  });
}

/**
 * 助手函数:判断是否为目标的 GitHub 域名
 * 域名是否以 git 开头
 * @param {string} hostname
 * @returns {boolean}
 */
function isGitHubDomain(hostname) {
// 这个正则表达式检查:
  // 1. (^|\.) : 字符串是否以...开头 (^) 或 ( | ) 以一个点 (.) 开头
  // 2. git     : 后面紧跟着 'git'
  //
  // 示例:
  // - "github.com"      -> 匹配 (^git)
  // - "api.github.com"  -> 匹配 (.git)hub.com
  // - "gitlab.com"      -> 匹配 (^git)
  // - "my.gitee.com"    -> 匹配 (.git)ee.com
  // - "my-git.com"      -> 不匹配 (因为 'g' 前面是 '-' 而不是 '.' 或开头)
  return /(^|\.)git/.test(hostname);
}

index.html 

工具页面 index.html 本身与 github proxy 是相对独立的.
html内容很少, 也很简单.
参考之前的 gh-proxy 的 index.html 删掉一些界面元素, 调整一些文字说明就行了.

main.js

工具页面 index.html上点击按钮时, 将链接添加ghproxy的工作是在 main.js 中完成的.
借用 worker.js 中的实现, 正好都是JS, 代码直接复制粘贴就行了.

访问worker时, 显示index.html

处理 path 为空时, path 为 styles.css 时, path 为 main.js 时, 应该去访问 github page
// 如果path为空,返回主页
if (!path) {
  return fetch(ASSET_URL)
}
if (path === 'styles.css') {
  return fetch(ASSET_URL + path)
}
if (path === 'main.js') {
  return fetch(ASSET_URL + path)
}

上传 Github


演示站


演示视频


部署方法


========

后记

在本次 (2025-11-6) 面向GPT开发中最好用的GPT是 

评论

The Hot3 in Last 30 Days

酒馆SillyTavern 玩英文角色卡 也能以中文输出 设置世界书Lorebooks

搭 Docker版 Sub-Store订阅转换专家 带 http-meta 实现 集合订阅 测延迟 排序 筛选 生成新订阅 定时任务上传Gist