Github文件加速
-为访问Github文件进行加速
- - -GitHub 链接带不带协议头均可,支持 release、archive 以及文件,转换后链接均可使用。
-文件大小限制
-...
-白名单状态
-...
-黑名单状态
-...
-diff --git a/.github/ISSUE_TEMPLATE/features_request.md b/.github/ISSUE_TEMPLATE/features_request.md index 603c02f..38732d9 100644 --- a/.github/ISSUE_TEMPLATE/features_request.md +++ b/.github/ISSUE_TEMPLATE/features_request.md @@ -2,7 +2,7 @@ name: Features request about: 提出新功能建议 title: "[Features]" -labels: enhancement +labels: 改进 assignees: '' --- diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml index bb5ddc5..397b80d 100644 --- a/.github/workflows/auto-assign.yml +++ b/.github/workflows/auto-assign.yml @@ -15,5 +15,5 @@ jobs: uses: pozil/auto-assign-issue@v1 with: repo-token: ${{ secrets.AUTO_ASSIGN }} - assignees: WJQSERVER, satomitoka + assignees: WJQSERVER, satomitouka numOfAssignee: 2 \ No newline at end of file diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 40a60f6..5906659 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -46,7 +46,7 @@ jobs: goarch: [amd64, arm64] env: OUTPUT_BINARY: ghproxy - GO_VERSION: 1.24 + GO_VERSION: 1.25 steps: - uses: actions/checkout@v4 @@ -59,6 +59,11 @@ jobs: else echo "DEV-VERSION file not found!" && exit 1 fi + - name: 拉取前端 + run: | + sudo git clone https://github.com/WJQSERVER-STUDIO/GHProxy-Frontend.git pages + sudo rm -rf pages/.git/ + - name: 安装 Go uses: actions/setup-go@v3 with: @@ -68,7 +73,7 @@ jobs: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} run: | - CGO_ENABLED=0 go build -ldflags "-X main.version=${{ env.VERSION }} -X main.dev=true" -o ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./main.go + CGO_ENABLED=0 go build -ldflags "-X main.version=${{ env.VERSION }} -X main.dev=true" -o ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} . - name: 打包 run: | mkdir ghproxyd diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 76306c5..c814dfb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + ref: main - name: 加载版本号 run: | if [ -f VERSION ]; then @@ -45,10 +47,12 @@ jobs: goarch: [amd64, arm64] env: OUTPUT_BINARY: ghproxy - GO_VERSION: 1.24 + GO_VERSION: 1.25 steps: - uses: actions/checkout@v3 + with: + ref: main - name: 加载版本号 run: | if [ -f VERSION ]; then @@ -56,6 +60,11 @@ jobs: else echo "VERSION file not found!" && exit 1 fi + - name: 拉取前端 + run: | + sudo git clone https://github.com/WJQSERVER-STUDIO/GHProxy-Frontend.git pages + sudo rm -rf pages/.git/ + - name: 安装 Go uses: actions/setup-go@v3 with: @@ -65,7 +74,7 @@ jobs: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} run: | - CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=${{ env.VERSION }}" -o ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./main.go + CGO_ENABLED=0 go build -ldflags "-s -w -X main.version=${{ env.VERSION }}" -o ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} . - name: 打包 run: | mkdir ghproxyd @@ -102,6 +111,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: main - name: Load VERSION run: | if [ -f VERSION ]; then @@ -130,4 +141,7 @@ jobs: push: true tags: | ${{ env.IMAGE_NAME }}:${{ env.VERSION }} - ${{ env.IMAGE_NAME }}:latest \ No newline at end of file + ${{ env.IMAGE_NAME }}:v4 + ${{ env.IMAGE_NAME }}:latest + wjqserver/ghproxy-touka:latest + wjqserver/ghproxy-touka:${{ env.VERSION }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c16c6b1..b5c0aee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ demo demo.toml +demo.wanf *.log *.bak list.json -repos \ No newline at end of file +iplist.json +repos +pages +*_test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2b774..77c5a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,750 @@ # 更新日志 -2.4.1 - 2025-03-12 +4.3.4 - 2025-09-14 +--- +- CHANGE: 改进嵌套加速实现, 增强稳定性 + +4.3.3 - 2025-09-10 +--- +- CHANGE: 增强对[wanf](https://github.com/WJQSERVER/wanf)的支持 +- CHANGE: 更新包括Touka框架在内的各个依赖版本 + +4.3.2 - 2025-08-20 +--- +- FIX: 修正`cfg.Pages.StaticDir`为空时的处置 + +4.3.1 - 2025-08-13 +--- +- CHANGE: 更新至[Go 1.25](https://tip.golang.org/doc/go1.25) + +4.3.0 - 2025-08-11 +--- +- CHANGE: 为OCI镜像(Docker)代理带来自动library附加功能 +- CHANGE(refactor): 改进OCI镜像(Docker)代理路径组成流程 +- ADD: 新增[WANF](https://github.com/WJQSERVER/wanf)配置文件格式支持 + +4.3.0-rc.0 - 2025-08-11 +--- +- PRE-RELEASE: v4.3.0-rc.0是v4.3.0发布版本,请勿在生产环境中使用; +- CHANGE: 为OCI镜像(Docker)代理带来自动library附加功能 +- CHANGE(refactor): 改进OCI镜像(Docker)代理路径组成流程 +- ADD: 新增[WANF](https://github.com/WJQSERVER/wanf)配置文件格式支持 + +4.2.7 - 2025-08-04 +--- +- CHANGE: 在OCI镜像(docker)代理部分增加特殊处理, 保证可用性 参看[#159](https://github.com/WJQSERVER-STUDIO/ghproxy/issues/159) +- CHANGE: 更新Touka框架, 同步解决部分日志过多问题 + +4.2.6 - 2025-08-01 +--- +- CHANGE: 修正匹配器 + +4.2.5 - 2025-07-31 +--- +- CHANGE: 进一步完善匹配器, 兼容更多情况 + +4.2.4 - 2025-07-29 +--- +- CHANGE: 改进匹配器, 防止匹配不应匹配的内容 + +4.2.4-rc.0 - 2025-07-29 +--- +- PRE-RELEASE: v4.2.4-rc.0是v4.2.4预发布版本,请勿在生产环境中使用; +- CHANGE: 改进匹配器, 防止匹配不应匹配的内容 + +4.2.3 - 2025-07-27 +--- +- CHANGE: 改进错误页面加载器, 避免在选择`external`模式时错误页面渲染回退到json输出 +- CHANGE: 完善OCI(Docker)镜像代理默认target逻辑 + +4.2.3-rc.0 - 2025-07-27 +--- +- PRE-RELEASE: v4.2.3-rc.0是v4.2.3预发布版本,请勿在生产环境中使用; +- CHANGE: 改进错误页面加载器, 避免在选择`external`模式时错误页面渲染回退到json输出 +- CHANGE: 完善OCI(Docker)镜像代理默认target逻辑 + +4.2.2 - 2025-07-25 +--- +- CHANGE: 重构OCI镜像代理部分, 完善对`ghcr`,`gcr`,`k8s.gcr`等上游源特殊处理的适配 + +4.2.2-rc.0 - 2025-07-25 +--- +- PRE-RELEASE: v4.2.2-rc.0是v4.2.2预发布版本,请勿在生产环境中使用; +- CHANGE: 重构OCI镜像代理部分, 完善对`ghcr`,`gcr`,`k8s.gcr`等上游源特殊处理的适配 + +4.2.1 - 2025-07-25 +--- +- CHANGE: 更新主题样式, 新增`free`主题, `design`与`hub`主题样式更新 + +4.2.0 - 2025-07-22 +--- +- CHANGE: 支持根据IP(CDIR)进行白名单与屏蔽 +- CHANGE: 进一步推进`json/v2`支持 + +4.2.0-rc.0 - 2025-07-22 +--- +- PRE-RELEASE: v4.2.0-rc.0是v4.2.0预发布版本,请勿在生产环境中使用; +- CHANGE: 支持根据IP(CDIR)进行白名单与屏蔽 +- CHANGE: 深化json/v2改革, 预备go1.25 json/v2 + +4.1.7 - 2025-07-20 +--- +- CHANGE: 更新相关依赖 +- CHANGE: 改进代码结构, 完善处理 + +4.1.7-rc.0 - 2025-07-20 +--- +- PRE-RELEASE: v4.1.7-rc.0是v4.1.7预发布版本,请勿在生产环境中使用; +- CHANGE: 更新相关依赖 +- CHANGE: 改进代码结构, 完善处理 + +4.1.6 - 2025-07-07 +--- +- CHANGE: 更新[Touka框架](https://github.com/infinite-iroha/touka)版本到`v0.2.9`, 提升`io`相关方式的性能并降低分配 +- CHANGE: 更新[Touka HTTPC](https://github.com/WJQSERVER-STUDIO/httpc)版本到`v0.8.0`, 使用`json/v2`的同时, 提升`io`相关操作性能并降低分配, 优化`debug`模式下打印输出性能 + +4.1.6-rc.0 - 2025-07-07 +--- +- PRE-RELEASE: v4.1.6-rc.0是v4.1.6预发布版本,请勿在生产环境中使用; +- CHANGE: 更新[Touka框架](https://github.com/infinite-iroha/touka)版本到`v0.2.9`, 提升`io`相关方式的性能并降低分配 +- CHANGE: 更新[Touka HTTPC](https://github.com/WJQSERVER-STUDIO/httpc)版本到`v0.8.0`, 使用`json/v2`的同时, 提升`io`相关操作性能并降低分配, 优化`debug`模式下打印输出性能 + +4.1.5 - 2025-07-03 +--- +- CHANGE: 更新`httpc`依赖以修正一些问题 + +4.1.5-rc.0 - 2025-07-03 +--- +- PRE-RELEASE: v4.1.5-rc.0是v4.1.5预发布版本,请勿在生产环境中使用; +- CHANGE: 更新`httpc`依赖以修正一些问题 + +4.1.4 - 2025-06-30 +--- +- CHANGE: 使用`touka`框架的内建httpc统一管理, 同时对httpc相关初始化进行改进 +- CHANGE: 更新`json/v2`版本 + +4.1.4-rc.0 - 2025-06-30 +--- +- PRE-RELEASE: v4.1.4-rc.0是v4.1.4预发布版本,请勿在生产环境中使用; +- CHANGE: 使用`touka`框架的内建httpc统一管理, 同时对httpc相关初始化进行改进 +- CHANGE: 更新`json/v2`版本 + +4.1.3 - 2025-06-25 +--- +- CHANGE: 更新`touka`版本, 使用新的方式配置slash重定向功能 + +4.1.3-rc.0 - 2025-06-25 +--- +- PRE-RELEASE: 此版本是v4.1.3预发布版本,请勿在生产环境中使用; +- CHANGE: 更新`touka`版本, 使用新的方式配置slash重定向功能 + +4.1.2 - 2025-06-18 +--- +- CHANGE: 更新`design`主题, 更新默认配置生成 + +4.1.2-rc.0 - 2025-06-18 +--- +- PRE-RELEASE: 此版本是v4.1.2预发布版本,请勿在生产环境中使用; +- CHANGE: 更新`design`主题, 更新默认配置生成 + +4.1.1 - 2025-06-18 +--- +- CHANGE: 更新touka框架到v0.2.6, 解决MidwareX的一些状态问题 + +4.1.0 - 2025-06-17 +--- +- ADD: 加入基于`basic auth`的docker鉴权支持 + +4.1.0-rc.0 - 2025-06-17 +--- +- PRE-RELEASE: 此版本是v4.1.0预发布版本,请勿在生产环境中使用; +- ADD: 加入基于`basic auth`的docker鉴权支持 + +4.1.0-beta.0 - 2025-06-17 +--- +- BETA-TEST: 此版本是v4.1.0的测试版本,请勿在生产环境中使用; +- ADD: 加入基于`basic auth`的docker鉴权支持 + +4.0.0 - 2025-06-16 +--- +- CHANGE: 移交到Touka框架 +- REMOVE: 移除req rate limit的total方式 +- CHANGE: 使用[reco](https://github.com/fenthope/reco)日志库, 异步使能 +- FIX: 更换HTTP框架以解决v3可能存在的内存分配与回收问题 + +4.0.0-rc.0 - 2025-06-16 +--- +- PRE-RELEASE: 此版本是v4.0.0预发布版本,请勿在生产环境中使用; +- CHANGE: 移交到Touka框架 +- REMOVE: 移除req rate limit的total方式 +- CHANGE: 使用[reco](https://github.com/fenthope/reco)日志库, 异步使能 + +4.0.0-beta.0 - 2025-06-15 +--- +- BETA-TEST: 此版本是v4.0.0的测试版本,请勿在生产环境中使用; +- CHANGE: 移交到Touka框架 +- REMOVE: 移除req rate limit的total方式 +- CHANGE: 使用[reco](https://github.com/fenthope/reco)日志库, 异步使能 + +3.5.6 - 2025-06-15 +--- +- FIX: 修正blob重写的生成问题 +- CHANGE: 改进302重定向逻辑 + +25w48c - 2025-06-15 +--- +- PRE-RELEASE: 此版本是v3.5.6预发布版本,请勿在生产环境中使用; +- CHANGE: 加入内部301处理 + +25w48b - 2025-06-15 +--- +- PRE-RELEASE: 此版本是v3.5.6预发布版本,请勿在生产环境中使用; +- FIX: 修正blob重写的生成问题 +- CHANGE: 验证与连接释放相关的修正 + +25w48a - 2025-06-14 +--- +- PRE-RELEASE: 此版本是v3.5.6预发布版本,请勿在生产环境中使用; +- CHANGE: 测试302重定向逻辑 + +3.5.5 - 2025-06-14 +--- +- CHANGE: 修正新匹配器的覆盖问题, 同时增加test的覆盖 + +25w47a - 2025-06-14 +--- +- PRE-RELEASE: 此版本是v3.5.5预发布版本,请勿在生产环境中使用; +- CHANGE: 修正新匹配器的覆盖问题, 同时增加test的覆盖 + +3.5.4 - 2025-06-14 +--- +- CHANGE: 移植来自于[GHProxy-Touka](https://github.com/WJQSERVER-STUDIO/ghproxy-touka)的blob处理逻辑与302处理逻辑 + +25w46c - 2025-06-14 +--- +- PRE-RELEASE: 此版本是v3.5.4预发布版本,请勿在生产环境中使用; +- CHANGE: 移植来自于[GHProxy-Touka](https://github.com/WJQSERVER-STUDIO/ghproxy-touka)的blob处理逻辑与302处理逻辑 + +25w46b - 2025-06-14 +--- +- PRE-RELEASE: 此版本是v3.5.4预发布版本,请勿在生产环境中使用; +- CHANGE: 修改关闭行为以测试问题 + +25w46a - 2025-06-14 +--- +- PRE-RELEASE: 此版本是v3.5.4预发布版本,请勿在生产环境中使用; +- CHANGE: 修改payload行为以测试问题 + +3.5.3 - 2025-06-13 +--- +- CHANGE: 显式配置`WithStreamBody(true)` + +3.5.2 - 2025-06-11 +--- +- CHANGE: 加入MPL 2.0许可证, 项目转为双重许可 + +3.5.1 - 2025-06-09 +--- +- CHANGE: 大幅优化`Matcher`的性能, 实现零分配, 大幅提升性能; 单次操作时间: `254.3 ns/op` => `29.59 ns/op` + +25w45a - 2025-06-09 +--- +- PRE-RELEASE: 此版本是v3.5.1预发布版本,请勿在生产环境中使用; +- CHANGE: 大幅优化`Matcher`的性能, 实现零分配, 大幅提升性能; 单次操作时间: `254.3 ns/op` => `29.59 ns/op` + +3.5.0 - 2025-06-05 +--- +- CHANGE: 更新许可证 v2.0 => v2.1 +- CHANGE: 修正工作流的一些问题 +- ADD: 增加`ForceAllowApiPassList`, 实现 #114 + +25w44a - 2025-06-05 +--- +- PRE-RELEASE: 此版本是v3.5.0预发布版本,请勿在生产环境中使用; +- CHANGE: 更新许可证 v2.0 => v2.1 +- CHANGE: 修正工作流的一些问题 +- ADD: 增加`ForceAllowApiPassList`, 实现 #114 + +3.4.3 - 2025-06-05 +--- +- CHANGE: 弃用`adaptor.GetCompatRequest`, 切换到`adaptor.HertzHandler` +- CHANGE: 为`embedFS`使用包装器, 使其支持`Last-Modified` +- CHANGE: 为静态资源增加`Cache-Control: public, max-age=3600, must-revalidate` + +25w43a - 2025-06-05 +--- +- PRE-RELEASE: 此版本是v3.4.3预发布版本,请勿在生产环境中使用; +- CHANGE: 弃用`adaptor.GetCompatRequest`, 切换到`adaptor.HertzHandler` +- CHANGE: 为`embedFS`使用包装器, 使其支持`Last-Modified` +- CHANGE: 为静态资源增加`Cache-Control: public, max-age=3600, must-revalidate` + +3.4.2 - 2025-06-03 +--- +- DEP: 回滚 github.com/nyaruka/phonenumbers 版本到 v1.6.1, v1.6.3观测到了一些反射造成的内存占用异常 + +25w42a - 2025-06-02 +--- +- PRE-RELEASE: 此版本是v3.4.2预发布版本,请勿在生产环境中使用; +- DEP: 回滚 github.com/nyaruka/phonenumbers 版本到 v1.6.1, v1.6.3观测到了一些反射造成的内存占用异常 + +3.4.1 - 2025-05-29 +--- +- ADD: 为`errorpage`部分增加lru缓存, 避免重复渲染 +- CHANGE: 把json库替换到[sonic](github.com/bytedance/sonic) + +25w41b - 2025-05-28 +--- +- PRE-RELEASE: 此版本是v3.4.1预发布版本,请勿在生产环境中使用; +- CHANGE: 把json库替换到[sonic](github.com/bytedance/sonic) + +25w41a - 2025-05-28 +--- +- PRE-RELEASE: 此版本是v3.4.1预发布版本,请勿在生产环境中使用; +- ADD: 为`errorpage`部分增加lru缓存, 避免重复渲染 +- CHANGE: 替换到实验性的`encoding/json/v2` + +3.4.0 - 2025-05-21 +--- +- ADD: 初步实现多`target` Docker代理 +- ADD: 加入`weakcache`用于处理短期令牌 +- ADD: 新增`hub`主题 +- ADD: 新增`/api/shell_nest/status`与`/api/oci_proxy/status` API + +25w40b - 2025-05-21 +--- +- PRE-RELEASE: 此版本是v3.4.0预发布版本,请勿在生产环境中使用; +- ADD: 新增`hub`主题 +- ADD: 新增`/api/shell_nest/status`与`/api/oci_proxy/status` API +- CHANGE: 对细节进行优化 + +25w40a - 2025-05-21 +--- +- PRE-RELEASE: 此版本是v3.4.0预发布版本,请勿在生产环境中使用; +- ADD: 初步实现多`target` Docker代理 +- ADD: 加入`weakcache`用于处理短期令牌 + +3.3.3 - 2025-05-20 +--- +- CHANGE: 加入`senseClientDisconnection`与`async`配置项 + +25w39a - 2025-05-19 +--- +- PRE-RELEASE: 此版本是v3.3.3预发布版本,请勿在生产环境中使用; +- CHANGE: 加入`senseClientDisconnection`与`async`配置项 + +3.3.2 - 2025-05-18 +--- +- CHANGE: 默认主题改为`design` + +25w38a - 2025-05-18 +--- +- PRE-RELEASE: 此版本是v3.3.2预发布版本,请勿在生产环境中使用; +- CHANGE: 默认主题改为`design` + +3.3.1 - 2025-05-16 +- CHANGE: 为`target`放宽限制, 支持自定义 +- CHANGE: 更新`hertz`, `0.9.7`=>`0.10.0` + +25w37a - 2025-05-16 +--- +- PRE-RELEASE: 此版本是v3.3.1预发布版本,请勿在生产环境中使用; +- CHANGE: 为`target`放宽限制, 支持自定义 +- CHANGE: 更新`hertz`, `0.9.7`=>`0.10.0` + +3.3.0 - 2025-05-15 +--- +- CHANGE: 为`httpc`加入`request builder`的`withcontext`选项 +- ADD: 加入带宽限制功能 +- ADD: 为`netpoll`模式开启探测客户端是否断开功能 + +25w36d - 2025-05-14 +--- +- PRE-RELEASE: 此版本是v3.3.0预发布版本,请勿在生产环境中使用; +- ADD: 为`netpoll`模式开启探测客户端是否断开功能 + +25w36c - 2025-05-14 +--- +- PRE-RELEASE: 此版本是v3.3.0预发布版本,请勿在生产环境中使用; +- ADD: 加入带宽限制功能 +- CHANGE: 将`httpc`切换回主分支, `25w36b`测试的部分已被合入`httpc`主线 + +25w36b - 2025-05-13 +--- +- PRE-RELEASE: 此版本是v3.3.0预发布版本,请勿在生产环境中使用; +- CHANGE: `httpc`切换到`dev`, 测试在retry前检查ctx状态 + +25w36a - 2025-05-13 +--- +- PRE-RELEASE: 此版本是v3.3.0预发布版本,请勿在生产环境中使用; +- CHANGE: 为`httpc`加入`request builder`的`withcontext`选项 + +3.2.4 - 2025-05-13 +--- +- CHANGE: 移除未使用的变量与相关计算 + +25w35a - 2025-05-12 +--- +- PRE-RELEASE: 此版本是v3.2.4预发布版本,请勿在生产环境中使用; +- CHANGE: 移除未使用的变量与相关计算 + +3.2.3 - 2025-05-07 +--- +- CHANGE: 迁移logger库到新的仓库, 开启异步日志记录 +- CHANGE: 更新Go版本到go1.24.3 +- CHANGE: 迁移httpc到新的仓库 + +25w34b - 2025-05-07 +--- +- PRE-RELEASE: 此版本是v3.2.3预发布版本,请勿在生产环境中使用; +- CHANGE: 更新Go版本到go1.24.3 + +25w34a - 2025-05-05 +--- +- PRE-RELEASE: 此版本是v3.2.3预发布版本,请勿在生产环境中使用; +- CHANGE: 迁移logger库到新的仓库, 开启异步日志记录 +- CHANGE: 迁移httpc到新的仓库 + +3.2.2 - 2025-04-29 +--- +- ADD: 实验性的raw Header处置, 用于应对Github对zh-CN的限制 +- FIX: 修正Header部分的一些处理问题 +- REVERT: 为`git clone`部分回滚 3.1.0中的 "使用`bodystream`进行req方向的body复制, 而不是使用额外的`buffer reader`" 修改 + +25w33b - 2025-04-29 +--- +- PRE-RELEASE: 此版本是v3.2.2预发布版本,请勿在生产环境中使用; +- REVERT: 为`git clone`部分回滚 3.1.0中的 "使用`bodystream`进行req方向的body复制, 而不是使用额外的`buffer reader`" 修改 + +25w33a - 2025-04-29 +--- +- PRE-RELEASE: 此版本是v3.2.2预发布版本,请勿在生产环境中使用; +- ADD: 实验性的raw Header处置, 用于应对Github对zh-CN的限制 +- FIX: 修正Header部分的一些处理问题 + +3.2.1 - 2025-04-29 +--- +- FIX: 修复在`HertZ`路由匹配器下`matcher`键值不一致的问题 + +25w32a - 2025-04-29 +--- +- PRE-RELEASE: 此版本是v3.2.1预发布版本,请勿在生产环境中使用; +- FIX: 修复在`HertZ`路由匹配器下`matcher`键值不一致的问题 + +3.2.0 - 2025-04-27 +--- +- CHANGE: 加入`ghcr`和`dockerhub`反代功能 +- FIX: 修复在`HertZ`路由匹配器下与用户名相关功能异常的问题 + +25w31a - 2025-04-27 +--- +- PRE-RELEASE: 此版本是v3.2.0预发布版本,请勿在生产环境中使用; +- CHANGE: 加入`ghcr`和`dockerhub`反代功能 +- FIX: 修复在`HertZ`路由匹配器下与用户名相关功能异常的问题 + +3.1.0 - 2025-04-24 +--- +- CHANGE: 对标准url使用`HertZ`路由匹配器, 而不是自制匹配器, 以提升效率 +- CHANGE: 使用`bodystream`进行req方向的body复制, 而不是使用额外的`buffer reader` +- CHANGE: 使用`HertZ`的`requestContext`传递matcher参数, 而不是`25w30a`中的ctx +- CHANGE: 改进`rate`模块, 避免并发竞争问题 +- CHANGE: 将大部分状态码返回改为新的`html/tmpl`方式处理 +- CHANGE: 修改部分log等级 +- FIX: 修正默认配置的填充错误 +- CHANGE: 使用go `html/tmpl`处理状态码页面, 同时实现错误信息显示 +- CHANGE: 改进handle, 复用共同部分 +- CHANGE: 细化url匹配的返回码处理 +- CHANGE: 增加404界面 + +25w30e - 2025-04-24 +--- +- PRE-RELEASE: 此版本是v3.1.0预发布版本,请勿在生产环境中使用; +- CHANGE: 改进`rate`模块, 避免并发竞争问题 +- CHANGE: 将大部分状态码返回改为新的`html/tmpl`方式处理 +- CHANGE: 修改部分log等级 +- FIX: 修正默认配置的填充错误 + +25w30d - 2025-04-22 +--- +- PRE-RELEASE: 此版本是v3.1.0预发布版本,请勿在生产环境中使用; +- CHANGE: 使用go `html/tmpl`处理状态码页面, 同时实现错误信息显示 + +25w30c - 2025-04-21 +--- +- PRE-RELEASE: 此版本是v3.1.0预发布版本,请勿在生产环境中使用; +- CHANGE: 改进handle, 复用共同部分 +- CHANGE: 细化url匹配的返回码处理 +- CHANGE: 增加404界面 + +25w30b - 2025-04-21 +--- +- PRE-RELEASE: 此版本是v3.1.0预发布版本,请勿在生产环境中使用; +- CHANGE: 使用`bodystream`进行req方向的body复制, 而不是使用额外的`buffer reader` +- CHANGE: 使用`HertZ`的`requestContext`传递matcher参数, 而不是`25w30a`中的标准ctx + +25w30a - 2025-04-19 +--- +- PRE-RELEASE: 此版本是v3.1.0预发布版本,请勿在生产环境中使用; +- CHANGE: 对标准url使用`HertZ`路由匹配器, 而不是自制匹配器 + +3.0.3 - 2025-04-19 +--- +- CHANGE: 增加移除部分header的处置, 避免向服务端/客户端透露过多信息 +- FIX: 修正非预期的header操作行为 +- CHANGE: 合并header相关逻辑, 避免多次操作 +- CHANGE: 对editor模式下的input进行处置, 增加隐式关闭处理 +- CHANGE: 增加`netlib`配置项 + +25w29b - 2025-04-19 +--- +- PRE-RELEASE: 此版本是v3.0.3预发布版本,请勿在生产环境中使用; +- CHANGE: 增加`netlib`配置项 + +25w29a - 2025-04-17 +--- +- PRE-RELEASE: 此版本是v3.0.3预发布版本,请勿在生产环境中使用; +- CHANGE: 增加移除部分header的处置, 避免向服务端/客户端透露过多信息 +- FIX: 修正非预期的header操作行为 +- CHANGE: 合并header相关逻辑, 避免多次操作 +- CHANGE: 对editor模式下的input进行处置, 增加隐式关闭处理 + +3.0.2 - 2025-04-15 +--- +- CHANGE: 避免重复的re编译操作 +- CHANGE: 去除不必要的请求 +- CHANGE: 改进`httpc`相关配置 +- CHANGE: 更新`httpc` 0.4.0 +- CHANGE: 为不遵守`RFC 2616`, `RFC 9112`的客户端带来兼容性改进 + +25w28b - 2025-04-15 +--- +- PRE-RELEASE: 此版本是v3.0.2预发布版本,请勿在生产环境中使用; +- CHANGE: 改进resp关闭 +- CHANGE: 避免重复的re编译操作 + +25w28a - 2025-04-14 +--- +- PRE-RELEASE: 此版本是预发布版本,请勿在生产环境中使用; +- CHANGE: 去除不必要的请求 +- CHANGE: 改进`httpc`相关配置 +- CHANGE: 合入test版本修改 + +25w28t-2 - 2025-04-11 +--- +- TEST: 测试验证版本 +- CHANGE: 为不遵守`RFC 2616`, `RFC 9112`的客户端带来兼容性改进 + +25w28t-1 - 2025-04-11 +--- +- TEST: 测试验证版本 +- CHANGE: 更新httpc 0.4.0 + +3.0.1 - 2025-04-08 +--- +- CHANGE: 加入`memLimit`指示gc +- CHANGE: 加入`hlog`输出路径配置 +- CHANGE: 修正H2C配置问题 + +25w27a - 2025-04-07 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- CHANGE: 加入`memLimit`指示gc +- CHANGE: 加入`hlog`输出路径配置 +- CHANGE: 修正H2C配置问题 + +3.0.0 - 2025-04-04 +--- +- RELEASE: Next Gen; 下一个起点; +- CHANGE: 使用HertZ框架重构, 提升性能 +- CHANGE: 前端在构建时加入, 新增`Design`,`Metro`,`Classic`主题 +- CHANGE: 加入`Mino`主题对接选项 +- FIX: 修正部分日志输出问题 +- CHANGE: 移除gin残留 +- CHANGE: 移除无用传入参数, 调整代码结构 +- CHANGE: 改进cli +- CHANGE: 改进`脚本嵌套加速处理器` +- CHANGE&FIX: 使用`c.SetBodyStream`方式, 修正此前`chunked`传输中存在的诸多问题, 参看[HertZ Issues #1309](https://github.com/cloudwego/hertz/issues/1309) +- PORT: 从v2移植`matcher`相关改进 +- CHANGE: 增加默认配置生成 +- CHANGE: 优化前端资源加载 +- CHANGE: 将`cfg`flag改为`c`以符合`POSIX`规范 +- CHANGE: 为`smart-git`添加`no-cache`标头 + +25w26a - 2025-04-03 +--- +- PRE-RELEASE: 此版本是v3的预发布版本,请勿在生产环境中使用; + +2.6.3 - 2025-03-30 +--- +- FIX: 修正一些`git clone`行为异常 + +25w25a - 2025-03-30 +--- +- PRE-RELEASE: 此版本是v2.6.3的预发布版本,请勿在生产环境中使用; +- FIX: 修正一些`git clone`行为异常 + +e3.0.7 -2025-03-29 +--- +- CHANGE: 将`cfg`flag改为`c`以符合`POSIX`规范 +- CHANGE: 为`smart-git`添加`no-cache`标头 + +2.6.2 - 2025-03-29 +--- +- BACKPORT: 反向移植前端资源加载改进 + +e3.0.6 - 2025-03-28 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- FIX: 修正状态码相关问题(开发遗留所致) + +e3.0.5 - 2025-03-28 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- CHANGE: 增加默认配置生成 +- CHANGE: 优化前端资源加载 + +2.6.1 - 2025-03-27 +--- +- CHANGE: 改进`matcher`组件 +- CHANGE: 加入优雅关闭 + +e3.0.3 - 2025-03.27 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- E-RELEASE: 修正过往问题, 还请各位多多测试反馈 +- PORT: 从v2移植`matcher`相关改进 +- CHANGE&FIX: 使用`c.SetBodyStream`方式, 修正此前`chunked`传输中存在的诸多问题, 参看[HertZ Issues #1309](https://github.com/cloudwego/hertz/issues/1309) + +25w24a - 2025-03-27 +--- +- PRE-RELEASE: 此版本是v2.6.1的预发布版本,请勿在生产环境中使用; +- CHANGE: 改进`matcher`组件 +- CHANGE: 加入优雅关闭 + +e3.0.3rc2 - 2025-03-27 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- PRE-RELEASE: 此版本是v3.0.3的候选版本,请勿在生产环境中使用; +- PORT: 从v2移植`matcher`相关改进 + +e3.0.3rc1 - 2025-03-26 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- PRE-RELEASE: 此版本是v3.0.3的候选版本,请勿在生产环境中使用; +- CHANGE&FIX: 使用`c.SetBodyStream`方式, 修正此前`chunked`传输中存在的诸多问题, 参看[HertZ Issues #1309](https://github.com/cloudwego/hertz/issues/1309) + +2.6.0 - 2025-03-22 +--- +- BACKPORT: 将v3的功能性改进反向移植 + +25w23a - 2025-03-22 +--- +- PRE-RELEASE: 此版本是v2.6.0的预发布版本,请勿在生产环境中使用; +- BACKPORT: 将v3的功能性改进反向移植 + +e3.0.2 - 2025-03-21 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- RELEASE: 在此表达对各位的歉意, v3迁移到HertZ带来了许多问题; 此版本完善v3的同时, 修正已知问题; +- FIX: 使用等效`c.Writer()`, 回归v2.5.0 func以修正问题 +- CHANGE: 更新相关依赖 + +25w22a - 2025-03-21 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- FIX: 使用等效`c.Writer()`, 回归v2.5.0 func以修正问题 + +e3.0.1 - 2025-03-21 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- RELEASE: Next Step; 下一步; 完善v3的同时, 修正已知问题; +- CHANGE: 改进cli +- CHANGE: 重写`ProcessLinksAndWriteChunked`(脚本嵌套加速处理器), 修正已知问题的同时提高性能与效率 +- CHANGE: 完善`gitreq`部分 +- FIX: 修正日志输出格式问题 +- FIX: 使用更新的`hwriter`以修正相关问题 + +25w21e - 2025-03-21 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- CHANGE: 重写`ProcessLinksAndWriteChunked`(脚本嵌套加速处理器), 修正已知问题的同时提高性能与效率 + +25w21d - 2025-03-21 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- FIX: 使用更新的`hwriter`以修正相关问题 + +25w21c - 2025-03-20 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- TEST: 测试新的`hwriter` + +25w21b - 2025-03-20 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- FIX: 修正日志输出格式问题 + +25w21a - 2025-03-20 +--- +- PRE-RELEASE: 此版本是v3.0.1的预发布版本,请勿在生产环境中使用; +- CHANGE: 改进cli +- CHANGE: 完善`gitreq`部分 + +e3.0.0 - 2025-03-19 +--- +- ATTENTION: 此版本是实验性的, 请确保了解这一点 +- RELEASE: Next Gen; 下一个起点; +- CHANGE: 使用HertZ框架重构, 提升性能 +- CHANGE: 前端在构建时加入, 新增`Design`,`Metro`,`Classic`主题 +- CHANGE: 加入`Mino`主题对接选项 +- FIX: 修正部分日志输出问题 +- CHANGE: 移除gin残留 +- CHANGE: 移除无用传入参数, 调整代码结构 + +25w20b - 2025-03-19 +--- +- PRE-RELEASE: 此版本是v3.0.0的预发布版本,请勿在生产环境中使用; +- CHANGE: 加入`Mino`主题对接选项 +- FIX: 修正部分日志输出问题 +- CHANGE: 移除gin残留 +- CHANGE: 移除无用传入参数, 调整代码结构 + +25w20a - 2025-03-18 +--- +- PRE-RELEASE: 此版本是v3.0.0的预发布版本,请勿在生产环境中使用; +- CHANGE: 使用HertZ重构 +- CHANGE: 前端在构建时加入, 新增`Design`,`Metro`,`Classic`主题 + +2.5.0 - 2025-03-17 +--- +- ADD: 加入脚本嵌套加速功能 +- CHANGE: 改进Auth模块 + +25w19a - 2025-03-16 +--- +- PRE-RELEASE: 此版本是v2.5.0的预发布版本,请勿在生产环境中使用; +- ADD: 加入脚本嵌套加速功能 +- CHANGE: 改进Auth模块 +- CHANGE: 将handler模块化改进 + +2.4.2 - 2025-03-14 +--- +- CHANGE: 在GitClone Cache模式下, 相关请求会使用独立httpc client +- CHANGE: 为GitClone Cache的独立httpc client增加ForceH2C选项 +- FIX: 修正GitClone Cache模式下的Url生成问题 + +25w18a - 2025-03-14 +--- +- PRE-RELEASE: 此版本是v2.4.2的预发布版本,请勿在生产环境中使用; +- CHANGE: 在GitClone Cache模式下, 相关请求会使用独立httpc client +- CHANGE: 为GitClone Cache的独立httpc client增加ForceH2C选项 +- FIX: 修正GitClone Cache模式下的Url生成问题 + +2.4.1 - 2025-03-13 --- - CHANGE: 重构路由匹配 - CHANGE: 更新相关依赖以修复错误 -25w17a - 2025-03-12 +25w17a - 2025-03-13 --- - PRE-RELEASE: 此版本是v2.4.1的预发布版本,请勿在生产环境中使用; - CHANGE: 重构路由匹配 @@ -1066,4 +1805,4 @@ v0.1.0 - ADD: 实现符合[RFC 7234](https://httpwg.org/specs/rfc7234.html)的HTTP缓存机制 - ADD: 实现action编译 - ADD: 实现Docker部署 -- INFO: 使用Caddy作为Web服务器,通过Caddy实现了缓存与速率限制 +- INFO: 使用Caddy作为Web服务器,通过Caddy实现了缓存与速率限制 \ No newline at end of file diff --git a/DEV-VERSION b/DEV-VERSION index bfc48ec..51ff7c7 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -25w17a \ No newline at end of file +4.3.0-rc.0 \ No newline at end of file diff --git a/LICENSE b/LICENSE index de09e5d..dbdb0fa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,197 +1,373 @@ -WJQserver Studio 开源许可证 -版本 v2.0 +Mozilla Public License Version 2.0 +================================== -版权所有 © WJQserver Studio 2024 +1. Definitions +-------------- -定义 +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. -* 许可 (License): 指的是在本许可证内定义的使用、复制、分发与修改软件的条款与要求。 -* 授权方 (Licensor): 指的是拥有版权的个人或组织,亦或是拥有版权的个人或组织所指派的实体,在本许可证中特指 WJQserver Studio。 -* 贡献者 (Contributor): 指的是授权方以及根据本许可证授予贡献代码或软件的个人或实体。 -* 您 (You): 指的是行使本许可授予的权限的个人或法律实体。 -* 衍生作品 (Derivative Works): 指的是基于本软件或本软件任何部分的修改作品,无论修改程度如何。这包括但不限于基于本软件或其任何部分的修改、修订、改编、翻译或其他形式的创作,以及包含本软件或其部分的集合作品。 -* 非营利性使用 (Non-profit Use): 指的是不以直接商业盈利为主要目的的使用方式,包括但不限于: - * 个人用途: 由个人为了个人学习、研究、实验、非商业项目、个人网站搭建、毕业设计、家庭内部娱乐等非直接商业目的使用软件。 - * 教育用途: 在教育机构(如学校、大学、培训机构)内部用于教学、研究、学术交流等活动。 - * 科研用途: 在科研院所、实验室等机构内部用于科学研究、实验开发等活动。 - * 慈善与公益用途: 由慈善机构、公益组织等非营利性组织为了其公益使命或慈善事业内部运营使用,或对外提供不直接产生商业利润的公益服务。 - * 内部运营用途 (非营利组织): 非营利性组织在其内部运营中使用软件,例如用于行政管理、会员管理、内部沟通、项目管理等非直接营利性活动。 +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. -开源与自由软件 +1.3. "Contribution" + means Covered Software of a particular Contributor. -本项目为开源软件,允许用户在遵循本许可证的前提下访问和使用源代码。 -本项目旨在向用户提供尽可能广泛的非商业使用自由,同时保障社区的共同发展和良性生态,并为商业创新提供清晰的路径。 -强调版权所有,所有权利由 WJQserver Studio 及贡献者共同保留。 +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. -许可证条款 +1.5. "Incompatible With Secondary Licenses" + means -1. 使用权限 + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or -* 1.1 非营利性使用: 您被授予在非营利性使用场景下,为了任何目的,自由使用本软件的权限。 非营利性使用的具体场景包括但不限于定义部分所列举的各种情况。 + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. -* 1.2 商业使用: 您可以在商业环境中使用本软件,无需获得额外授权,但您的商业使用行为必须遵守以下条款: +1.6. "Executable Form" + means any form of the work other than Source Code Form. - * 1.2.1 保持声明: 您在进行商业使用时,不得移除或修改软件中包含的原始版权声明、许可证声明以及来源声明。 - * 1.2.2 开源继承 (Copyleft) 与互惠共享: 如果您或您的组织希望将本软件或其衍生作品用于任何商业用途,包括但不限于: +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. - * 盈利性分发: 销售、出租、许可分发本软件或其衍生作品。 - * 盈利性服务: 基于本软件或其衍生作品提供商业服务,例如 SaaS 服务、咨询服务、定制开发服务、收费技术支持服务等。 - * 嵌入式商业应用: 将本软件或其衍生作品嵌入到商业产品或解决方案中进行销售。 - * 组织内部商业运营: 在营利性组织的内部运营中使用修改后的版本以直接支持其商业活动,例如定制化内部系统,通过例如但不限于在软件或相关服务中投放广告 (例如 Google Ads 等),应用内购买 (内购), 会员订阅, 增值功能收费等方式直接或间接产生商业收入。 +1.8. "License" + means this document. - 您必须选择以下两种方式之一: +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. - * i) 继承本许可证并开源: 您必须以本许可证或兼容的开源许可证分发您的衍生作品,并公开您的衍生作品的全部源代码,使得您的衍生作品的接收者也享有与您相同的权利,包括进一步修改和商业使用的权利。 本选项旨在促进社区的共同发展和知识共享,确保基于本软件的商业创新成果也能回馈社区。 - * ii) 获得授权方明确授权: 如果您不希望以开源方式发布您的衍生作品,或者希望使用其他许可证进行分发,或者您希望在商业运营中使用修改后的版本但不开源,您必须事先获得 WJQserver Studio 的明确书面授权。 授权的具体条款和条件将由 WJQserver Studio 另行协商确定。 +1.10. "Modifications" + means any of the following: -2. 复制与分发 + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or -* 2.1 原始版本复制与分发: 您可以复制和分发本软件的原始版本,前提是必须满足以下条件: + (b) any new file in Source Code Form that contains any Covered + Software. - * 保留所有声明: 完整保留所有原始版权声明、许可证声明、来源声明以及其他所有权声明。 - * 附带许可证: 在分发软件时,必须同时附带本许可证的完整文本,确保接收者知悉并理解本许可证的全部条款。 +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. -* 2.2 衍生作品复制与分发: 您可以复制和分发基于本软件的衍生作品,您对衍生作品的分发行为将受到本许可证第 1.2.2 条(开源继承与互惠共享)的约束。 +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. -3. 修改权限 +1.13. "Source Code Form" + means the form of the work preferred for making modifications. -* 3.1 自由修改: 您被授予自由修改本软件的权限,无论修改目的是非营利性使用还是商业用途。 +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. -* 3.2 修改后使用与分发约束: 当您将修改后的版本用于商业用途或分发修改后的版本时,您需要遵守本许可证第 1.2.2 条(开源继承与互惠共享)以及第 2 条(复制与分发)的规定。 即使您不分发修改后的版本,只要您将其用于商业目的,也需要遵守开源继承条款或获得授权。 +2. License Grants and Conditions +-------------------------------- -* 3.3 贡献接受: WJQserver Studio 鼓励社区贡献代码。如果您向本项目贡献代码,您需要同意您的贡献代码按照本许可证条款进行许可。 +2.1. Grants -4. 专利权 +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: -* 4.1 无专利担保,风险自担: 本软件以“现状”提供,授权方及贡献者明确声明,不对本软件的专利侵权问题做任何形式的担保,亦不承担任何因专利侵权可能产生的责任与后果。 用户理解并同意,使用本软件的专利风险完全由用户自行承担。 +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and -* 4.2 专利纠纷应对: 如因用户使用本软件而引发任何专利侵权指控、诉讼或索赔,用户应自行负责处理并承担全部法律责任。 授权方及贡献者无义务参与任何相关法律程序,亦不承担任何由此产生的费用或赔偿。 +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. -5. 免责声明 +2.2. Effective Date -* 5.1 “现状”提供,无任何保证: 本软件按“现状”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性及非侵权性。 +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. -* 5.2 责任限制: 在适用法律允许的最大范围内,在任何情况下,授权方或任何贡献者均不对因使用或无法使用本软件而产生的任何直接、间接、偶然、特殊、惩罚性或后果性损害(包括但不限于采购替代商品或服务;损失使用、数据或利润;或业务中断)负责,无论其是如何造成的,也无论依据何种责任理论,即使已被告知可能发生此类损害。 +2.3. Limitations on Grant Scope -* 5.3 用户法律责任: 用户需根据当地法律对待本项目,确保遵守所有适用法规。 - -6. 许可证期限与终止 - -* 6.1 许可证期限: 除版权所有人主动宣布放弃本软件版权外,本许可证无限期生效。 - -* 6.2 许可证终止: 如果您未能遵守本许可证的任何条款或条件,授权方有权终止本许可证。 您的许可证将在您违反本许可证条款时自动终止。 - -* 6.3 终止后的效力: 许可证终止后,您根据本许可证所享有的所有权利将立即终止,但您在许可证终止前已合法分发的软件副本,其接收者所获得的许可及权利将不受影响,继续有效。 免责声明(第 5 条)和责任限制(第 5.2 条)在本许可证终止后仍然有效。 - -7. 条款修订 - -* 7.1 修订权利保留: 授权方保留随时修改本许可证条款的权利,以便更好地适应法律、技术发展以及社区需求。 - -* 7.2 修订生效与接受: 修订后的条款将在发布时生效,除非另行声明,否则继续使用、复制、分发或修改本软件即表示您接受修订后的条款。授权方鼓励用户定期查阅本许可证的最新版本。 - -8. 其他 - -* 8.1 法定权利: 本许可证不影响您作为最终用户在适用法律下的法定权利。 - -* 8.2 条款可分割性: 若本许可证的某些条款被认定为不可执行,其余条款仍然完全有效。 - -* 8.3 版本更新: 授权方可能会发布本许可证的修订版本或新版本。您可以选择是继续使用本许可证的旧版本还是选择适用新版本。 - -WJQserver Studio Open Source License -Version v2.0 - -Copyright © WJQserver Studio 2024 - -Definitions - -* License: Refers to the terms and requirements for use, reproduction, distribution, and modification defined within this license. -* Licensor: Refers to the individual or organization that holds the copyright, or the entity designated by the copyright holder, specifically WJQserver Studio in this license. -* Contributor: Refers to the Licensor and individuals or entities who contribute code or software under this License. -* You: Refers to the individual or legal entity exercising permissions granted by this License. -* Derivative Works: Refers to works modified based on the Software or any part thereof, regardless of the extent of modification. This includes but is not limited to modifications, revisions, adaptations, translations, or other forms of creation based on the Software or any part thereof, as well as collective works containing the Software or parts thereof. -* Non-profit Use: Refers to uses not primarily intended for direct commercial profit, including but not limited to: - * Personal Use: Use by an individual for personal learning, research, experimentation, non-commercial projects, personal website development, graduation projects, home entertainment, and other non-directly commercial purposes. - * Educational Use: Use within educational institutions (such as schools, universities, training organizations) for activities such as teaching, research, and academic exchange. - * Scientific Research Use: Use within scientific research institutions, laboratories, and similar organizations for activities such as scientific research and experimental development. - * Charitable and Public Welfare Use: Use by charitable organizations, public welfare organizations, and similar non-profit entities for their public missions or internal operation of charitable activities, or to provide public services that do not directly generate commercial profit. - * Internal Operational Use (Non-profit Organizations): Use within the internal operations of non-profit organizations, such as for administrative management, membership management, internal communication, project management, and other non-directly profit-generating activities. - -Open Source and Free Software - -This project is open-source software, allowing users to access and use the source code under the premise of complying with this License. -This project aims to provide users with the broadest possible freedom for non-commercial use while ensuring the common development and healthy ecosystem of the community, and providing a clear path for commercial innovation. -Copyright is emphasized; all rights are jointly reserved by WJQserver Studio and Contributors. - -License Terms - -1. Permissions for Use - -* 1.1 Non-profit Use: You are granted permission to freely use the Software for any purpose in non-profit use scenarios. Specific non-profit use scenarios include but are not limited to the various situations listed in the Definition section. - -* 1.2 Commercial Use: You may use the Software in a commercial environment without additional authorization, but your commercial use must comply with the following terms: - - * 1.2.1 Maintain Statements: When conducting commercial use, you must not remove or modify the original copyright notices, license notices, and source statements contained in the Software. - * 1.2.2 Open Source Inheritance (Copyleft) and Reciprocal Sharing: If you or your organization wish to use the Software or its Derivative Works for any commercial purpose, including but not limited to: - - * Profit-generating Distribution: Selling, renting, licensing, or distributing the Software or its Derivative Works. - * Profit-generating Services: Providing commercial services based on the Software or its Derivative Works, such as SaaS services, consulting services, custom development services, and paid technical support services. - * Embedded Commercial Applications: Embedding the Software or its Derivative Works into commercial products or solutions for sale. - * Internal Commercial Operations: Using modified versions within the internal operations of for-profit organizations to directly support their commercial activities, such as customized internal systems, generating commercial revenue directly or indirectly through means including but not limited to placing advertisements in the software or related services (e.g., Google Ads), in-app purchases, membership subscriptions, and charging for value-added features. - - You must choose one of the following two options: - - * i) Inherit this License and Open Source: You must distribute your Derivative Works under this License or a compatible open-source license and publicly disclose the entire source code of your Derivative Works, so that recipients of your Derivative Works also enjoy the same rights as you, including the right to further modify and use commercially. This option aims to promote the common development and knowledge sharing of the community, ensuring that commercial innovation achievements based on this Software can also contribute back to the community. - * ii) Obtain Explicit Authorization from the Licensor: If you do not wish to release your Derivative Works in an open-source manner, or wish to distribute them under another license, or you wish to use a modified version in commercial operations without open-sourcing it, you must obtain explicit written authorization from WJQserver Studio in advance. The specific terms and conditions of authorization will be determined separately by WJQserver Studio through negotiation. - -2. Reproduction and Distribution - -* 2.1 Reproduction and Distribution of Original Version: You may reproduce and distribute the original version of the Software, provided that the following conditions are met: - - * Retain All Statements: Completely retain all original copyright notices, license notices, source statements, and other proprietary notices. - * Accompany with License: When distributing the Software, you must also include the full text of this License to ensure that recipients are aware of and understand all terms of this License. - -* 2.2 Reproduction and Distribution of Derivative Works: You may reproduce and distribute Derivative Works based on the Software. Your distribution of Derivative Works will be subject to the constraints of Clause 1.2.2 of this License (Open Source Inheritance and Reciprocal Sharing). - -3. Modification Permissions - -* 3.1 Free Modification: You are granted permission to freely modify the Software, regardless of whether the purpose of modification is for non-profit use or commercial use. - -* 3.2 Constraints on Use and Distribution after Modification: When you use a modified version for commercial purposes or distribute a modified version, you need to comply with the provisions of Clause 1.2.2 of this License (Open Source Inheritance and Reciprocal Sharing) and Clause 2 (Reproduction and Distribution). Even if you do not distribute the modified version, as long as you use it for commercial purposes, you also need to comply with the open-source inheritance clause or obtain authorization. - -* 3.3 Contribution Acceptance: WJQserver Studio encourages community contribution of code. If you contribute code to this project, you need to agree that your contributed code is licensed under the terms of this License. - -4. Patent Rights - -* 4.1 No Patent Warranty, Risk Self-Bearing: The software is provided “AS IS”, and the Licensor and Contributors explicitly declare that they do not provide any form of warranty regarding patent infringement issues of this software, nor do they assume any responsibility and consequences arising from patent infringement. Users understand and agree that the patent risk of using this software is entirely borne by the users themselves. - -* 4.2 Handling of Patent Disputes: If any patent infringement allegations, lawsuits, or claims arise due to the user's use of this Software, the user shall be solely responsible for handling and bear all legal liabilities. The Licensor and Contributors are under no obligation to participate in any related legal proceedings, nor do they bear any costs or compensation arising therefrom. - -5. Disclaimer of Warranty - -* 5.1 “AS IS” Provision, No Warranty: The software is provided “AS IS” without any express or implied warranties, including but not limited to warranties of merchantability, fitness for a particular purpose, and non-infringement. - -* 5.2 Limitation of Liability: To the maximum extent permitted by applicable law, in no event shall the Licensor or any Contributor be liable for any direct, indirect, incidental, special, punitive, or consequential damages (including but not limited to procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. - -* 5.3 User Legal Responsibility: Users shall treat this project in accordance with local laws and regulations to ensure compliance with all applicable laws and regulations. - -6. License Term and Termination - -* 6.1 License Term: Unless the copyright holder proactively announces the abandonment of the copyright of this software, this License shall be effective indefinitely from the date of your acceptance. - -* 6.2 License Termination: If you fail to comply with any terms or conditions of this License, the Licensor has the right to terminate this License. Your License will automatically terminate upon your violation of the terms of this License. - -* 6.3 Effect after Termination: Upon termination of the License, all rights granted to you under this License will terminate immediately, but the licenses and rights obtained by recipients of software copies you have legally distributed before the termination of the License will not be affected and will remain valid. The Disclaimer of Warranty (Clause 5) and Limitation of Liability (Clause 5.2) shall remain in effect after the termination of this License. - -7. Revision of Terms - -* 7.1 Reservation of Revision Rights: The Licensor reserves the right to modify the terms of this License at any time to better adapt to legal, technological developments, and community needs. - -* 7.2 Effectiveness and Acceptance of Revisions: Revised terms will take effect upon publication, and unless otherwise stated, continued use, reproduction, distribution, or modification of the Software indicates your acceptance of the revised terms. The Licensor encourages users to periodically review the latest version of this License. - -8. Other - -* 8.1 Statutory Rights: This License does not affect your statutory rights as an end-user under applicable laws. - -* 8.2 Severability of Terms: If certain terms of this License are deemed unenforceable, the remaining terms shall remain in full force and effect. - -* 8.3 Version Updates: The Licensor may publish revised versions or new versions of this License. You may choose to continue using the old version of this License or choose to apply the new version. +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/LICENSE-WSL b/LICENSE-WSL new file mode 100644 index 0000000..e3f452b --- /dev/null +++ b/LICENSE-WSL @@ -0,0 +1,199 @@ +WJQserver Studio 开源许可证 +版本 v2.1 + +版权所有 © WJQserver Studio 2024 + +定义 + +* 许可 (License): 指的是在本许可证内定义的使用、复制、分发与修改软件的条款与要求。 +* 授权方 (Licensor): 指的是拥有版权的个人或组织,亦或是拥有版权的个人或组织所指派的实体,在本许可证中特指 WJQserver Studio。 +* 贡献者 (Contributor): 指的是授权方以及根据本许可证授予贡献代码或软件的个人或实体。 +* 您 (You): 指的是行使本许可授予的权限的个人或法律实体。 +* 衍生作品 (Derivative Works): 指的是基于本软件或本软件任何部分的修改作品,无论修改程度如何。这包括但不限于基于本软件或其任何部分的修改、修订、改编、翻译或其他形式的创作,以及包含本软件或其部分的集合作品。 +* 非营利性使用 (Non-profit Use): 指的是不以直接商业盈利为主要目的的使用方式,包括但不限于: + * 个人用途: 由个人为了个人学习、研究、实验、非商业项目、个人网站搭建、毕业设计、家庭内部娱乐等非直接商业目的使用软件。 + * 教育用途: 在教育机构(如学校、大学、培训机构)内部用于教学、研究、学术交流等活动。 + * 科研用途: 在科研院所、实验室等机构内部用于科学研究、实验开发等活动。 + * 慈善与公益用途: 由慈善机构、公益组织等非营利性组织为了其公益使命或慈善事业内部运营使用,或对外提供不直接产生商业利润的公益服务。 + * 内部运营用途 (非营利组织): 非营利性组织在其内部运营中使用软件,例如用于行政管理、会员管理、内部沟通、项目管理等非直接营利性活动。 + +开源与自由软件 + +本项目为开源软件,允许用户在遵循本许可证的前提下访问和使用源代码。 +本项目旨在向用户提供尽可能广泛的非商业使用自由,同时保障社区的共同发展和良性生态,并为商业创新提供清晰的路径。 +强调版权所有,所有权利由 WJQserver Studio 及贡献者共同保留。 + +许可证条款 + +1. 使用权限 + +* 1.1 非营利性使用: 您被授予在非营利性使用场景下,为了任何目的,自由使用本软件的权限。 非营利性使用的具体场景包括但不限于定义部分所列举的各种情况。 + +* 1.2 商业使用: 您可以在商业环境中使用本软件,无需获得额外授权,但您的商业使用行为必须遵守以下条款: + + * 1.2.1 开源继承 (Copyleft) 与互惠共享: 如果您或您的组织希望将本软件或其衍生作品用于任何商业用途,包括但不限于: + + * 盈利性分发: 销售、出租、许可分发本软件或其衍生作品。 + * 盈利性服务: 基于本软件或其衍生作品提供商业服务,例如 SaaS 服务、咨询服务、定制开发服务、收费技术支持服务等。 + * 嵌入式商业应用: 将本软件或其衍生作品嵌入到商业产品或解决方案中进行销售。 + * 组织内部商业运营: 在营利性组织的内部运营中使用修改后的版本以直接支持其商业活动,例如定制化内部系统,通过例如但不限于在软件或相关服务中投放广告 (例如 Google Ads 等),应用内购买 (内购), 会员订阅, 增值功能收费等方式直接或间接产生商业收入。 + + 您必须选择以下两种方式之一: + + * i) 继承本许可证并开源: 您必须以本许可证或兼容的开源许可证分发您的衍生作品,并公开您的衍生作品的全部源代码,使得您的衍生作品的接收者也享有与您相同的权利,包括进一步修改和商业使用的权利。 本选项旨在促进社区的共同发展和知识共享,确保基于本软件的商业创新成果也能回馈社区。 + * ii) 获得授权方明确授权: 如果您不希望以开源方式发布您的衍生作品,或者希望使用其他许可证进行分发,或者您希望在商业运营中使用修改后的版本但不开源,您必须事先获得 WJQserver Studio 的明确书面授权。 授权的具体条款和条件将由 WJQserver Studio 另行协商确定。 + +* 1.3 保持声明: 公开发布服务时,不得移除或修改软件中包含的原始版权声明、许可证声明以及来源声明。 + +2. 复制与分发 + +* 2.1 原始版本复制与分发: 您可以复制和分发本软件的原始版本,前提是必须满足以下条件: + + * 保留所有声明: 完整保留所有原始版权声明、许可证声明、来源声明以及其他所有权声明。 + * 附带许可证: 在分发软件时,必须同时附带本许可证的完整文本,确保接收者知悉并理解本许可证的全部条款。 + +* 2.2 衍生作品复制与分发: 您可以复制和分发基于本软件的衍生作品,您对衍生作品的分发行为将受到本许可证第 1.3 条(开源继承与互惠共享)的约束。 + +3. 修改权限 + +* 3.1 自由修改: 您被授予自由修改本软件的权限,无论修改目的是非营利性使用还是商业用途。 + +* 3.2 修改后使用与分发约束: 当您将修改后的版本用于商业用途或分发修改后的版本时,您需要遵守本许可证第 1.3 条(开源继承与互惠共享)以及第 2 条(复制与分发)的规定。 即使您不分发修改后的版本,只要您将其用于商业目的,也需要遵守开源继承条款或获得授权。 + +* 3.3 贡献接受: WJQserver Studio 鼓励社区贡献代码。如果您向本项目贡献代码,您需要同意您的贡献代码按照本许可证条款进行许可。 + +4. 专利权 + +* 4.1 无专利担保,风险自担: 本软件以“现状”提供,授权方及贡献者明确声明,不对本软件的专利侵权问题做任何形式的担保,亦不承担任何因专利侵权可能产生的责任与后果。 用户理解并同意,使用本软件的专利风险完全由用户自行承担。 + +* 4.2 专利纠纷应对: 如因用户使用本软件而引发任何专利侵权指控、诉讼或索赔,用户应自行负责处理并承担全部法律责任。 授权方及贡献者无义务参与任何相关法律程序,亦不承担任何由此产生的费用或赔偿。 + +5. 免责声明 + +* 5.1 “现状”提供,无任何保证: 本软件按“现状”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性及非侵权性。 + +* 5.2 责任限制: 在适用法律允许的最大范围内,在任何情况下,授权方或任何贡献者均不对因使用或无法使用本软件而产生的任何直接、间接、偶然、特殊、惩罚性或后果性损害(包括但不限于采购替代商品或服务;损失使用、数据或利润;或业务中断)负责,无论其是如何造成的,也无论依据何种责任理论,即使已被告知可能发生此类损害。 + +* 5.3 用户法律责任: 用户需根据当地法律对待本项目,确保遵守所有适用法规。 + +6. 许可证期限与终止 + +* 6.1 许可证期限: 除版权所有人主动宣布放弃本软件版权外,本许可证无限期生效。 + +* 6.2 许可证终止: 如果您未能遵守本许可证的任何条款或条件,授权方有权终止本许可证。 您的许可证将在您违反本许可证条款时自动终止。 + +* 6.3 终止后的效力: 许可证终止后,您根据本许可证所享有的所有权利将立即终止,但您在许可证终止前已合法分发的软件副本,其接收者所获得的许可及权利将不受影响,继续有效。 免责声明(第 5 条)和责任限制(第 5.2 条)在本许可证终止后仍然有效。 + +7. 条款修订 + +* 7.1 修订权利保留: 授权方保留随时修改本许可证条款的权利,以便更好地适应法律、技术发展以及社区需求。 + +* 7.2 修订生效与接受: 修订后的条款将在发布时生效,除非另行声明,否则继续使用、复制、分发或修改本软件即表示您接受修订后的条款。授权方鼓励用户定期查阅本许可证的最新版本。 + +8. 其他 + +* 8.1 法定权利: 本许可证不影响您作为最终用户在适用法律下的法定权利。 + +* 8.2 条款可分割性: 若本许可证的某些条款被认定为不可执行,其余条款仍然完全有效。 + +* 8.3 版本更新: 授权方可能会发布本许可证的修订版本或新版本。您可以选择是继续使用本许可证的旧版本还是选择适用新版本。 + +WJQserver Studio Open Source License +Version v2.1 + +Copyright © WJQserver Studio 2024 + +Definitions + +* License: Refers to the terms and requirements for use, reproduction, distribution, and modification defined within this license. +* Licensor: Refers to the individual or organization that holds the copyright, or the entity designated by the copyright holder, specifically WJQserver Studio in this license. +* Contributor: Refers to the Licensor and individuals or entities who contribute code or software under this License. +* You: Refers to the individual or legal entity exercising permissions granted by this License. +* Derivative Works: Refers to works modified based on the Software or any part thereof, regardless of the extent of modification. This includes but is not limited to modifications, revisions, adaptations, translations, or other forms of creation based on the Software or any part thereof, as well as collective works containing the Software or parts thereof. +* Non-profit Use: Refers to uses not primarily intended for direct commercial profit, including but not limited to: + * Personal Use: Use by an individual for personal learning, research, experimentation, non-commercial projects, personal website development, graduation projects, home entertainment, and other non-directly commercial purposes. + * Educational Use: Use within educational institutions (such as schools, universities, training organizations) for activities such as teaching, research, and academic exchange. + * Scientific Research Use: Use within scientific research institutions, laboratories, and similar organizations for activities such as scientific research and experimental development. + * Charitable and Public Welfare Use: Use by charitable organizations, public welfare organizations, and similar non-profit entities for their public missions or internal operation of charitable activities, or to provide public services that do not directly generate commercial profit. + * Internal Operational Use (Non-profit Organizations): Use within the internal operations of non-profit organizations, such as for administrative management, membership management, internal communication, project management, and other non-directly profit-generating activities. + +Open Source and Free Software + +This project is open-source software, allowing users to access and use the source code under the premise of complying with this License. +This project aims to provide users with the broadest possible freedom for non-commercial use while ensuring the common development and healthy ecosystem of the community, and providing a clear path for commercial innovation. +Copyright is emphasized; all rights are jointly reserved by WJQserver Studio and Contributors. + +License Terms + +1. Permissions for Use + +* 1.1 Non-profit Use: You are granted permission to freely use the Software for any purpose in non-profit use scenarios. Specific non-profit use scenarios include but are not limited to the various situations listed in the Definition section. + +* 1.2 Commercial Use: You may use the Software in a commercial environment without additional authorization, but your commercial use must comply with the following terms: + + * 1.2.1 Open Source Inheritance (Copyleft) and Reciprocal Sharing: If you or your organization wish to use the Software or its Derivative Works for any commercial purpose, including but not limited to: + + * Profit-generating Distribution: Selling, renting, licensing, or distributing the Software or its Derivative Works. + * Profit-generating Services: Providing commercial services based on the Software or its Derivative Works, such as SaaS services, consulting services, custom development services, and paid technical support services. + * Embedded Commercial Applications: Embedding the Software or its Derivative Works into commercial products or solutions for sale. + * Internal Commercial Operations: Using modified versions within the internal operations of for-profit organizations to directly support their commercial activities, such as customized internal systems, generating commercial revenue directly or indirectly through means including but not limited to placing advertisements in the software or related services (e.g., Google Ads), in-app purchases, membership subscriptions, and charging for value-added features. + + You must choose one of the following two options: + + * i) Inherit this License and Open Source: You must distribute your Derivative Works under this License or a compatible open-source license and publicly disclose the entire source code of your Derivative Works, so that recipients of your Derivative Works also enjoy the same rights as you, including the right to further modify and use commercially. This option aims to promote the common development and knowledge sharing of the community, ensuring that commercial innovation achievements based on this Software can also contribute back to the community. + * ii) Obtain Explicit Authorization from the Licensor: If you do not wish to release your Derivative Works in an open-source manner, or wish to distribute them under another license, or you wish to use a modified version in commercial operations without open-sourcing it, you must obtain explicit written authorization from WJQserver Studio in advance. The specific terms and conditions of authorization will be determined separately by WJQserver Studio through negotiation. + +* 1.3 Maintain Statements: When publish services to public, you must not remove or modify the original copyright notices, license notices, and source statements contained in the Software. + +2. Reproduction and Distribution + +* 2.1 Reproduction and Distribution of Original Version: You may reproduce and distribute the original version of the Software, provided that the following conditions are met: + + * Retain All Statements: Completely retain all original copyright notices, license notices, source statements, and other proprietary notices. + * Accompany with License: When distributing the Software, you must also include the full text of this License to ensure that recipients are aware of and understand all terms of this License. + +* 2.2 Reproduction and Distribution of Derivative Works: You may reproduce and distribute Derivative Works based on the Software. Your distribution of Derivative Works will be subject to the constraints of Clause 1.3 of this License (Open Source Inheritance and Reciprocal Sharing). + +3. Modification Permissions + +* 3.1 Free Modification: You are granted permission to freely modify the Software, regardless of whether the purpose of modification is for non-profit use or commercial use. + +* 3.2 Constraints on Use and Distribution after Modification: When you use a modified version for commercial purposes or distribute a modified version, you need to comply with the provisions of Clause 1.3 of this License (Open Source Inheritance and Reciprocal Sharing) and Clause 2 (Reproduction and Distribution). Even if you do not distribute the modified version, as long as you use it for commercial purposes, you also need to comply with the open-source inheritance clause or obtain authorization. + +* 3.3 Contribution Acceptance: WJQserver Studio encourages community contribution of code. If you contribute code to this project, you need to agree that your contributed code is licensed under the terms of this License. + +4. Patent Rights + +* 4.1 No Patent Warranty, Risk Self-Bearing: The software is provided “AS IS”, and the Licensor and Contributors explicitly declare that they do not provide any form of warranty regarding patent infringement issues of this software, nor do they assume any responsibility and consequences arising from patent infringement. Users understand and agree that the patent risk of using this software is entirely borne by the users themselves. + +* 4.2 Handling of Patent Disputes: If any patent infringement allegations, lawsuits, or claims arise due to the user's use of this Software, the user shall be solely responsible for handling and bear all legal liabilities. The Licensor and Contributors are under no obligation to participate in any related legal proceedings, nor do they bear any costs or compensation arising therefrom. + +5. Disclaimer of Warranty + +* 5.1 “AS IS” Provision, No Warranty: The software is provided “AS IS” without any express or implied warranties, including but not limited to warranties of merchantability, fitness for a particular purpose, and non-infringement. + +* 5.2 Limitation of Liability: To the maximum extent permitted by applicable law, in no event shall the Licensor or any Contributor be liable for any direct, indirect, incidental, special, punitive, or consequential damages (including but not limited to procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage. + +* 5.3 User Legal Responsibility: Users shall treat this project in accordance with local laws and regulations to ensure compliance with all applicable laws and regulations. + +6. License Term and Termination + +* 6.1 License Term: Unless the copyright holder proactively announces the abandonment of the copyright of this software, this License shall be effective indefinitely from the date of your acceptance. + +* 6.2 License Termination: If you fail to comply with any terms or conditions of this License, the Licensor has the right to terminate this License. Your License will automatically terminate upon your violation of the terms of this License. + +* 6.3 Effect after Termination: Upon termination of the License, all rights granted to you under this License will terminate immediately, but the licenses and rights obtained by recipients of software copies you have legally distributed before the termination of the License will not be affected and will remain valid. The Disclaimer of Warranty (Clause 5) and Limitation of Liability (Clause 5.2) shall remain in effect after the termination of this License. + +7. Revision of Terms + +* 7.1 Reservation of Revision Rights: The Licensor reserves the right to modify the terms of this License at any time to better adapt to legal, technological developments, and community needs. + +* 7.2 Effectiveness and Acceptance of Revisions: Revised terms will take effect upon publication, and unless otherwise stated, continued use, reproduction, distribution, or modification of the Software indicates your acceptance of the revised terms. The Licensor encourages users to periodically review the latest version of this License. + +8. Other + +* 8.1 Statutory Rights: This License does not affect your statutory rights as an end-user under applicable laws. + +* 8.2 Severability of Terms: If certain terms of this License are deemed unenforceable, the remaining terms shall remain in full force and effect. + +* 8.3 Version Updates: The Licensor may publish revised versions or new versions of this License. You may choose to continue using the old version of this License or choose to apply the new version. diff --git a/README.md b/README.md index 0e4c6bb..c977c51 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,66 @@ # GHProxy +   + [](https://goreportcard.com/report/github.com/WJQSERVER-STUDIO/ghproxy) -使用Go实现的GHProxy,用于加速部分地区Github仓库的拉取,支持速率限制,用户鉴权,支持Docker部署 - -[DEMO](https://ghproxy.1888866.xyz) - -[TG讨论群组](https://t.me/ghproxy_go) - -[版本更新介绍](https://blog.wjqserver.com/categories/my-program/) +一个基于Go的高性能Github资源代理程序, 同时支持Docker镜像代理与脚本嵌套加速等多种功能 ## 项目说明 ### 项目特点 -- 基于Go语言实现,支持多平台 -- 使用[Gin](https://github.com/gin-gonic/gin)作为Web框架 -- 使用[Touka-HTTPC](https://github.com/satomitouka/touka-httpc)作为HTTP客户端 -- 支持Git clone,raw,realeases等文件拉取 -- 支持Docker部署 -- 支持速率限制 -- 支持用户鉴权 -- 支持自定义黑名单/白名单 -- 基于[WJQSERVER-STUDIO/golang-temp](https://github.com/WJQSERVER-STUDIO/golang-temp)模板构建,具有标准化的日志记录与构建流程 +- ⚡ **基于 Go 语言实现,跨平台的同时提供高并发性能** +- 🌐 **使用自有[Touka框架](https://github.com/infinite-iroha/touka)作为 HTTP服务端框架** +- 📡 **使用 [Touka-HTTPC](https://github.com/WJQSERVER-STUDIO/httpc) 作为 HTTP 客户端** +- 📥 **支持 Git clone、raw、releases 等文件拉取** +- 🐳 **支持反代Docker, GHCR等镜像仓库** +- 🎨 **支持多个前端主题** +- 🚫 **支持自定义黑名单/白名单** +- 🗄️ **支持 Git Clone 缓存(配合 [Smart-Git](https://github.com/WJQSERVER-STUDIO/smart-git))** +- 🐳 **支持自托管与Docker容器化部署** +- ⚡ **支持速率限制** +- ⚡ **支持带宽速率限制** +- 🔒 **支持用户鉴权** +- 🐚 **支持 shell 脚本多层嵌套加速** -### 项目开发过程 +### 项目相关 -**本项目是[WJQSERVER-STUDIO/ghproxy-go](https://github.com/WJQSERVER-STUDIO/ghproxy-go)的重构版本,实现了原项目原定功能的同时,进一步优化了性能** -关于此项目的详细开发过程,请参看Commit记录与[CHANGELOG.md](https://github.com/WJQSERVER-STUDIO/ghproxy/blob/main/CHANGELOG.md) +[DEMO](https://ghproxy.1888866.xyz) -- V2.0.0 对`proxy`核心模块进行了重构,大幅优化内存占用 -- V1.0.0 迁移至本仓库,并再次重构内容实现 -- v0.2.0 重构项目实现 +[TG讨论群组](https://t.me/ghproxy_go) -### LICENSE +[GHProxy项目文档](https://wjqserver-docs.pages.dev/docs/ghproxy/) 感谢 [@redbunnys](https://github.com/redbunnys)的维护 -本项目使用WJQserver Studio License 2.0 [WJQserver Studio License 2.0](https://wjqserver-studio.github.io/LICENSE/LICENSE.html) +[相关文章](https://blog.wjqserver.com/categories/my-program/) -在v2.3.0之前, 本项目使用WJQserver Studio License 1.2 +### 使用示例 -在v1.0.0版本之前,本项目继承于[WJQSERVER-STUDIO/ghproxy-go](https://github.com/WJQSERVER-STUDIO/ghproxy-go)的APACHE2.0 LICENSE VERSION - -## 使用示例 - -``` +```bash # 下载文件 https://ghproxy.1888866.xyz/raw.githubusercontent.com/WJQSERVER-STUDIO/tools-stable/main/tools-stable-ghproxy.sh +https://ghproxy.1888866.xyz/https://raw.githubusercontent.com/WJQSERVER-STUDIO/tools-stable/main/tools-stable-ghproxy.sh # 克隆仓库 git clone https://ghproxy.1888866.xyz/github.com/WJQSERVER-STUDIO/ghproxy.git +git clone https://ghproxy.1888866.xyz/https://github.com/WJQSERVER-STUDIO/ghproxy.git + +# Docker(OCI) 代理 +docker pull gh.example.com/wjqserver/ghproxy +docker pull gh.example.com/adguard/adguardhome + +docker pull gh.example.com/docker.io/wjqserver/ghproxy +docker pull gh.example.com/docker.io/adguard/adguardhome + +docker pull gh.example.com/ghcr.io/openfaas/queue-worker ``` ## 部署说明 +可参考文章: https://blog.wjqserver.com/post/ghproxy-deploy-with-smart-git/ + ### Docker部署 - Docker-cli @@ -83,114 +89,36 @@ wget -O install-dev.sh https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghprox ## 配置说明 -### 外部配置文件 - -本项目采用`config.toml`作为外部配置,默认配置如下 -使用Docker部署时,慎重修改`config.toml`,以免造成不必要的麻烦 - -```toml -[server] -host = "0.0.0.0" # 监听地址 -port = 8080 # 监听端口 -sizeLimit = 125 # 125MB -H2C = true # 是否开启H2C传输 -cors = "*" # "*"/"" -> "*" ; "nil" -> "" ; 除以上特殊情况, 会将值直接传入 - -[httpc] -mode = "auto" # "auto" or "advanced" HTTP客户端模式 自动/高级模式 -maxIdleConns = 100 # only for advanced mode 仅用于高级模式 -maxIdleConnsPerHost = 60 # only for advanced mode 仅用于高级模式 -maxConnsPerHost = 0 # only for advanced mode 仅用于高级模式 - -[gitclone] -mode = "bypass" # bypass / cache 运行模式, cache模式依赖smart-git -smartGitAddr = "http://127.0.0.1:8080" # smart-git组件地址 - -[pages] -mode = "internal" # "internal" or "external" 内部/外部 前端 默认内部 -theme = "bootstrap" # "bootstrap" or "nebula" 内置主题 -staticPath = "/data/www" # 静态页面文件路径 - -[log] -logFilePath = "/data/ghproxy/log/ghproxy.log" # 日志文件路径 -maxLogSize = 5 # MB 日志文件最大大小 -level = "info" # 日志级别 dump, debug, info, warn, error, none - -[auth] -authMethod = "parameters" # 鉴权方式,支持parameters,header -authToken = "token" # 用户鉴权Token -enabled = false # 是否开启用户鉴权 - -[blacklist] -blacklistFile = "/data/ghproxy/config/blacklist.json" # 黑名单文件路径 -enabled = false # 是否开启黑名单 - -[whitelist] -enabled = false # 是否开启白名单 -whitelistFile = "/data/ghproxy/config/whitelist.json" # 白名单文件路径 - -[rateLimit] -enabled = false # 是否开启速率限制 -rateMethod = "total" # "ip" or "total" 速率限制方式 -ratePerMinute = 180 # 每分钟限制请求数量 -burst = 5 # 突发请求数量 - -[outbound] -enabled = false # 是否使用自定义代理出站 -url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" 支持Socks5/HTTP(S)出站传输 -``` - -### 黑名单配置 - -黑名单配置位于config/blacklist.json,格式如下: - -```json -{ - "blacklist": [ - "test/test1", - "example/repo2", - "another/*" - "another" - ] - } -``` - -### 白名单配置 - -白名单配置位于config/whitelist.json,格式如下: - -```json -{ - "whitelist": [ - "test/test1", - "example/repo2", - "another/*" - "another" - ] - } -``` - -### Caddy反代配置 - -```Caddyfile -example.com { - reverse_proxy * 127.0.0.1:7210 -} -``` +参看[项目文档](https://github.com/WJQSERVER-STUDIO/ghproxy/blob/main/docs/config.md) ### 前端页面 - - +参看[GHProxy-Frontend](https://github.com/WJQSERVER-STUDIO/GHProxy-Frontend) + +## 文档 + +* [GHProxy项目文档](https://wjqserver-docs.pages.dev/docs/ghproxy/) 感谢 [@redbunnys](https://github.com/redbunnys)的维护 + +* [](https://deepwiki.com/WJQSERVER-STUDIO/ghproxy) 可供参考, AI生成存在幻觉, 不完全可靠, 请注意辨别 + +## LICENSE + +v3.5.2开始, 本项目使用 [WJQserver Studio License 2.1](https://wjqserver-studio.github.io/LICENSE/LICENSE.html) 和 [Mozilla Public License Version 2.0](https://mozilla.org/MPL/2.0/) 双重许可, 您可从中选择一个使用 + +前端位于单独仓库中, 且各个主题均存在各自的许可证, 本项目许可证并不包括前端 + +在v2.3.0之前, 本项目使用WJQserver Studio License 1.2 + +在v1.0.0版本之前,本项目继承于[WJQSERVER-STUDIO/ghproxy-go](https://github.com/WJQSERVER-STUDIO/ghproxy-go)的APACHE2.0 LICENSE VERSION ## 赞助 如果您觉得本项目对您有帮助,欢迎赞助支持,您的赞助将用于Demo服务器开支及开发者时间成本支出,感谢您的支持! -为爱发电,开源不易 - -爱发电: https://afdian.com/a/wjqserver +USDT(TRC20): `TNfSYG6F2vkiibd6J6mhhHNWDgWgNdF5hN` ### 捐赠列表 -虚位以待... +| 赞助人 |金额| +|--------|------| +| starry | 8 USDT (TRC20) | diff --git a/SECURITY.MD b/SECURITY.MD index 5ac050f..7696664 100644 --- a/SECURITY.MD +++ b/SECURITY.MD @@ -6,9 +6,13 @@ | 版本 | 是否支持 | | --- | --- | -| v2.x.x | :white_check_mark: 当前最新版本序列, 受支持 | -| v1.x.x | :x: 这些版本已结束生命周期,不再受支持 | -| 25w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 | +| v4.x.x | :white_check_mark: 当前最新版本序列 | +| v3.x.x | :x: 这些版本已结束生命周期,不受支持 | +| v2.x.x | :x: 这些版本已结束生命周期,不受支持 | +| v1.x.x | :x: 这些版本已结束生命周期,不受支持 | +| *-rc.x | :warning: 此为PRE-RELEASE预发布版本,用于测试问题 | +| *-beta.x | :warning: 此为Beta测试版本,用于开发与测试,可能存在未知的问题 | +| 25w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 生命周期已完全结束 | | 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 生命周期已完全结束 | | v0.x.x | :x: 这些版本不再受支持 | @@ -16,9 +20,15 @@ 本项目为开源项目,开发者不对使用本项目造成的任何损失或问题承担责任。用户需自行评估并承担使用本项目的风险。 -使用本项目,请遵循 **[WSL (WJQSERVER-STUDIO LICENSE)](https://wjqserver-studio.github.io/LICENSE/LICENSE.html)** 协议。 +使用本项目,请遵循 **[WSL 2.1 (WJQSERVER-STUDIO LICENSE 2.1)](https://wjqserver-studio.github.io/LICENSE/LICENSE.html)** 协议 或 [Mozilla Public License Version 2.0](https://mozilla.org/MPL/2.0/) 。 -本项目所有文件均受到 WSL (WJQSERVER-STUDIO LICENSE) 协议保护,任何人不得在任何情况下以非 WSL (WJQSERVER-STUDIO LICENSE) 协议内规定的方式使用,复制,修改,编译,发布,分发,再许可,或者出售本项目的任何部分。 +#### 选择WSL 2.1时 + +本项目所有文件均受到 WSL 2.1 (WJQSERVER-STUDIO LICENSE 2.1) 协议保护,任何人不得在任何情况下以非 WSL 2.1 (WJQSERVER-STUDIO LICENSE 2.1) 协议内规定的方式使用,复制,修改,编译,发布,分发,再许可,或者出售本项目的任何部分。 + +#### 选择MPL 2.0时 + +本项目内文件除特别版权标注声明外, 均受到 [Mozilla Public License Version 2.0](https://mozilla.org/MPL/2.0/) 授权保护, 具体条款参看 [Mozilla Public License Version 2.0](https://mozilla.org/MPL/2.0/) ## 报告漏洞 diff --git a/VERSION b/VERSION index 58073ef..a6695ff 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.4.1 \ No newline at end of file +4.3.4 \ No newline at end of file diff --git a/api/api.go b/api/api.go index 5a53c63..b1bb61c 100644 --- a/api/api.go +++ b/api/api.go @@ -1,129 +1,133 @@ package api import ( - "encoding/json" "ghproxy/config" + "ghproxy/middleware/nocache" - "github.com/WJQSERVER-STUDIO/go-utils/logger" - "github.com/gin-gonic/gin" + "github.com/infinite-iroha/touka" ) -var ( - router *gin.Engine - cfg *config.Config -) - -var ( - logw = logger.Logw - LogDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError -) - -func NoCacheMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - // 设置禁止缓存的响应头 - c.Header("Cache-Control", "no-store, no-cache, must-revalidate") - c.Header("Pragma", "no-cache") - c.Header("Expires", "0") - c.Next() // 继续处理请求 - } -} - -func InitHandleRouter(cfg *config.Config, router *gin.Engine, version string) { - apiRouter := router.Group("api", NoCacheMiddleware()) +func InitHandleRouter(cfg *config.Config, r *touka.Engine, version string) { + apiRouter := r.Group("/api", nocache.NoCacheMiddleware()) { - apiRouter.GET("/size_limit", func(c *gin.Context) { + apiRouter.GET("/size_limit", func(c *touka.Context) { SizeLimitHandler(cfg, c) }) - apiRouter.GET("/whitelist/status", func(c *gin.Context) { - WhiteListStatusHandler(c, cfg) + apiRouter.GET("/whitelist/status", func(c *touka.Context) { + WhiteListStatusHandler(cfg, c) }) - apiRouter.GET("/blacklist/status", func(c *gin.Context) { - BlackListStatusHandler(c, cfg) + apiRouter.GET("/blacklist/status", func(c *touka.Context) { + BlackListStatusHandler(cfg, c) }) - apiRouter.GET("/cors/status", func(c *gin.Context) { - CorsStatusHandler(c, cfg) + apiRouter.GET("/cors/status", func(c *touka.Context) { + CorsStatusHandler(cfg, c) }) - apiRouter.GET("/healthcheck", func(c *gin.Context) { + apiRouter.GET("/healthcheck", func(c *touka.Context) { HealthcheckHandler(c) }) - apiRouter.GET("/version", func(c *gin.Context) { + apiRouter.GET("/ok", func(c *touka.Context) { + HealthcheckHandler(c) + }) + apiRouter.GET("/version", func(c *touka.Context) { VersionHandler(c, version) }) - apiRouter.GET("/rate_limit/status", func(c *gin.Context) { - RateLimitStatusHandler(c, cfg) + apiRouter.GET("/rate_limit/status", func(c *touka.Context) { + RateLimitStatusHandler(cfg, c) }) - apiRouter.GET("/rate_limit/limit", func(c *gin.Context) { - RateLimitLimitHandler(c, cfg) + apiRouter.GET("/rate_limit/limit", func(c *touka.Context) { + RateLimitLimitHandler(cfg, c) + }) + apiRouter.GET("/smartgit/status", func(c *touka.Context) { + SmartGitStatusHandler(cfg, c) + }) + apiRouter.GET("/shell_nest/status", func(c *touka.Context) { + shellNestStatusHandler(cfg, c) + }) + apiRouter.GET("/oci_proxy/status", func(c *touka.Context) { + ociProxyStatusHandler(cfg, c) }) } - logInfo("API router Init success") } -func SizeLimitHandler(cfg *config.Config, c *gin.Context) { +func SizeLimitHandler(cfg *config.Config, c *touka.Context) { sizeLimit := cfg.Server.SizeLimit - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "MaxResponseBodySize": sizeLimit, - }) + })) } -func WhiteListStatusHandler(c *gin.Context, cfg *config.Config) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func WhiteListStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "Whitelist": cfg.Whitelist.Enabled, - }) + })) } -func BlackListStatusHandler(c *gin.Context, cfg *config.Config) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func BlackListStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "Blacklist": cfg.Blacklist.Enabled, - }) + })) } -func CorsStatusHandler(c *gin.Context, cfg *config.Config) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func CorsStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "Cors": cfg.Server.Cors, - }) + })) } -func HealthcheckHandler(c *gin.Context) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func HealthcheckHandler(c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "Status": "OK", - }) + "Repo": "WJQSERVER-STUDIO/GHProxy", + "Author": "WJQSERVER-STUDIO", + })) } -func VersionHandler(c *gin.Context, version string) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func VersionHandler(c *touka.Context, version string) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "Version": version, - }) + "Repo": "WJQSERVER-STUDIO/GHProxy", + "Author": "WJQSERVER-STUDIO", + })) } -func RateLimitStatusHandler(c *gin.Context, cfg *config.Config) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func RateLimitStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "RateLimit": cfg.RateLimit.Enabled, - }) + })) } -func RateLimitLimitHandler(c *gin.Context, cfg *config.Config) { - logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) - c.Writer.Header().Set("Content-Type", "application/json") - json.NewEncoder(c.Writer).Encode(map[string]interface{}{ +func RateLimitLimitHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ "RatePerMinute": cfg.RateLimit.RatePerMinute, - }) + })) +} + +func SmartGitStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ + "enabled": cfg.GitClone.Mode == "cache", + })) +} + +func shellNestStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ + "enabled": cfg.Shell.Editor, + })) +} + +func ociProxyStatusHandler(cfg *config.Config, c *touka.Context) { + c.SetHeader("Content-Type", "application/json") + c.JSON(200, (map[string]interface{}{ + "enabled": cfg.Docker.Enabled, + "target": cfg.Docker.Target, + })) } diff --git a/auth/auth-header.go b/auth/auth-header.go index 4cdff18..5180c05 100644 --- a/auth/auth-header.go +++ b/auth/auth-header.go @@ -4,26 +4,29 @@ import ( "fmt" "ghproxy/config" - "github.com/gin-gonic/gin" + "github.com/infinite-iroha/touka" ) -func AuthHeaderHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) { +func AuthHeaderHandler(c *touka.Context, cfg *config.Config) (isValid bool, err error) { if !cfg.Auth.Enabled { - return true, "" + return true, nil } // 获取"GH-Auth"的值 - authToken := c.GetHeader("GH-Auth") - logDebug("%s %s %s %s %s AUTH_TOKEN: %s", c.Request.Method, c.Request.Host, c.Request.URL.Path, c.Request.Proto, c.Request.RemoteAddr, authToken) + var authToken string + if cfg.Auth.Key != "" { + authToken = string(c.Request.Header.Get(cfg.Auth.Key)) + + } else { + authToken = string(c.Request.Header.Get("GH-Auth")) + } if authToken == "" { - err := "Auth Header == nil" - return false, err + return false, fmt.Errorf("Auth token not found") } - isValid = authToken == cfg.Auth.AuthToken + isValid = authToken == cfg.Auth.Token if !isValid { - err := fmt.Sprintf("Auth token incorrect: %s", authToken) - return false, err + return false, fmt.Errorf("Auth token incorrect") } - return isValid, "" + return isValid, nil } diff --git a/auth/auth-parameters.go b/auth/auth-parameters.go index 20d1ac2..4a4fecd 100644 --- a/auth/auth-parameters.go +++ b/auth/auth-parameters.go @@ -4,27 +4,29 @@ import ( "fmt" "ghproxy/config" - "github.com/gin-gonic/gin" + "github.com/infinite-iroha/touka" ) -func AuthParametersHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) { +func AuthParametersHandler(c *touka.Context, cfg *config.Config) (isValid bool, err error) { if !cfg.Auth.Enabled { - return true, "" + return true, nil } - authToken := c.Query("auth_token") - logDebug("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken) + var authToken string + if cfg.Auth.Key != "" { + authToken = c.Query(cfg.Auth.Key) + } else { + authToken = c.Query("auth_token") + } if authToken == "" { - err := "Auth token == nil" - return false, err + return false, fmt.Errorf("Auth token not found") } - isValid = authToken == cfg.Auth.AuthToken + isValid = authToken == cfg.Auth.Token if !isValid { - err := fmt.Sprintf("Auth token incorrect: %s", authToken) - return false, err + return false, fmt.Errorf("Auth token invalid") } - return isValid, "" + return isValid, nil } diff --git a/auth/auth.go b/auth/auth.go index 99cb6de..ad8efaf 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -1,51 +1,40 @@ package auth import ( + "fmt" "ghproxy/config" - "github.com/WJQSERVER-STUDIO/go-utils/logger" - "github.com/gin-gonic/gin" + "github.com/infinite-iroha/touka" ) -var ( - logw = logger.Logw - LogDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError -) - -func Init(cfg *config.Config) { +func ListInit(cfg *config.Config) error { if cfg.Blacklist.Enabled { err := InitBlacklist(cfg) if err != nil { - logError(err.Error()) - return + return err } } if cfg.Whitelist.Enabled { err := InitWhitelist(cfg) if err != nil { - logError(err.Error()) - return + return err } } - logDebug("Auth Init") + return nil } -func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) { - if cfg.Auth.AuthMethod == "parameters" { +func AuthHandler(c *touka.Context, cfg *config.Config) (isValid bool, err error) { + if cfg.Auth.Method == "parameters" { isValid, err = AuthParametersHandler(c, cfg) return isValid, err - } else if cfg.Auth.AuthMethod == "header" { + } else if cfg.Auth.Method == "header" { isValid, err = AuthHeaderHandler(c, cfg) return isValid, err - } else if cfg.Auth.AuthMethod == "" { - logError("Auth method not set") - return true, "" + } else if cfg.Auth.Method == "" { + c.Errorf("Auth method not set") + return true, nil } else { - logError("Auth method not supported") - return false, "Auth method not supported" + c.Errorf("Auth method not supported %s", cfg.Auth.Method) + return false, fmt.Errorf("%s", fmt.Sprintf("Auth method %s not supported", cfg.Auth.Method)) } } diff --git a/auth/blacklist.go b/auth/blacklist.go index ba2091b..014a41d 100644 --- a/auth/blacklist.go +++ b/auth/blacklist.go @@ -1,12 +1,13 @@ package auth import ( - "encoding/json" "fmt" "ghproxy/config" "os" "strings" "sync" + + "github.com/go-json-experiment/json" ) type Blacklist struct { diff --git a/auth/ipfilter.go b/auth/ipfilter.go new file mode 100644 index 0000000..45fe884 --- /dev/null +++ b/auth/ipfilter.go @@ -0,0 +1,60 @@ +package auth + +import ( + "fmt" + "ghproxy/config" + "os" + + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" +) + +func ReadIPFilterList(cfg *config.Config) (whitelist []string, blacklist []string, err error) { + if cfg.IPFilter.IPFilterFile == "" { + return nil, nil, nil + } + + // 检查文件是否存在, 不存在则创建空json + if _, err := os.Stat(cfg.IPFilter.IPFilterFile); os.IsNotExist(err) { + if err := CreateEmptyIPFilterFile(cfg.IPFilter.IPFilterFile); err != nil { + return nil, nil, fmt.Errorf("failed to create empty IP filter file: %w", err) + } + } + + data, err := os.ReadFile(cfg.IPFilter.IPFilterFile) + if err != nil { + return nil, nil, fmt.Errorf("failed to read IP filter file: %w", err) + } + + var ipFilterData struct { + AllowList []string `json:"allow"` + BlockList []string `json:"block"` + } + if err := json.Unmarshal(data, &ipFilterData); err != nil { + return nil, nil, fmt.Errorf("invalid IP filter file format: %w", err) + } + + return ipFilterData.AllowList, ipFilterData.BlockList, nil +} + +// 创建空列表json +func CreateEmptyIPFilterFile(filePath string) error { + emptyData := struct { + AllowList []string `json:"allow"` + BlockList []string `json:"block"` + }{ + AllowList: []string{}, + BlockList: []string{}, + } + + jsonData, err := json.Marshal(emptyData, jsontext.Multiline(true), jsontext.WithIndent(" ")) + if err != nil { + return fmt.Errorf("failed to marshal empty IP filter data: %w", err) + } + + err = os.WriteFile(filePath, jsonData, 0644) + if err != nil { + return fmt.Errorf("failed to write empty IP filter file: %w", err) + } + return nil +} diff --git a/auth/whitelist.go b/auth/whitelist.go index ee93c20..1218307 100644 --- a/auth/whitelist.go +++ b/auth/whitelist.go @@ -1,12 +1,13 @@ package auth import ( - "encoding/json" "fmt" "ghproxy/config" "os" "strings" "sync" + + "github.com/go-json-experiment/json" ) // Whitelist 用于存储白名单信息 diff --git a/config/config.go b/config/config.go index 2d55564..e098e72 100644 --- a/config/config.go +++ b/config/config.go @@ -1,39 +1,51 @@ package config import ( + "fmt" + "os" + "path/filepath" + "strings" + "github.com/BurntSushi/toml" + + "github.com/WJQSERVER/wanf" ) +// Config 结构体定义了整个应用程序的配置 type Config struct { - Server ServerConfig - Httpc HttpcConfig - GitClone GitCloneConfig - Pages PagesConfig - Log LogConfig - Auth AuthConfig - Blacklist BlacklistConfig - Whitelist WhitelistConfig - RateLimit RateLimitConfig - Outbound OutboundConfig + Server ServerConfig `toml:"server" wanf:"server"` + Httpc HttpcConfig `toml:"httpc" wanf:"httpc"` + GitClone GitCloneConfig `toml:"gitclone" wanf:"gitclone"` + Shell ShellConfig `toml:"shell" wanf:"shell"` + Pages PagesConfig `toml:"pages" wanf:"pages"` + Log LogConfig `toml:"log" wanf:"log"` + Auth AuthConfig `toml:"auth" wanf:"auth"` + Blacklist BlacklistConfig `toml:"blacklist" wanf:"blacklist"` + Whitelist WhitelistConfig `toml:"whitelist" wanf:"whitelist"` + IPFilter IPFilterConfig `toml:"ipFilter" wanf:"ipFilter"` + RateLimit RateLimitConfig `toml:"rateLimit" wanf:"rateLimit"` + Outbound OutboundConfig `toml:"outbound" wanf:"outbound"` + Docker DockerConfig `toml:"docker" wanf:"docker"` } /* [server] -host = "0.0.0.0" # 监听地址 -port = 8080 # 监听端口 -sizeLimit = 125 # 125MB -H2C = true # 是否开启H2C传输 -enableH2C = "on" # 是否开启H2C传输(latest和dev版本请开启) on/off (2.4.0弃用) +host = "0.0.0.0" +port = 8080 +sizeLimit = 125 # MB +memLimit = 0 # MB +cors = "*" # "*"/"" -> "*" ; "nil" -> "" ; +debug = false */ +// ServerConfig 定义服务器相关的配置 type ServerConfig struct { - Port int `toml:"port"` - Host string `toml:"host"` - SizeLimit int `toml:"sizeLimit"` - H2C bool `toml:"H2C"` - Cors string `toml:"cors"` - EnableH2C string `toml:"enableH2C"` - Debug bool `toml:"debug"` + Port int `toml:"port" wanf:"port"` + Host string `toml:"host" wanf:"host"` + SizeLimit int `toml:"sizeLimit" wanf:"sizeLimit"` + MemLimit int64 `toml:"memLimit" wanf:"memLimit"` + Cors string `toml:"cors" wanf:"cors"` + Debug bool `toml:"debug" wanf:"debug"` } /* @@ -42,66 +54,133 @@ mode = "auto" # "auto" or "advanced" maxIdleConns = 100 # only for advanced mode maxIdleConnsPerHost = 60 # only for advanced mode maxConnsPerHost = 0 # only for advanced mode +useCustomRawHeaders = false */ +// HttpcConfig 定义 HTTP 客户端相关的配置 type HttpcConfig struct { - Mode string `toml:"mode"` - MaxIdleConns int `toml:"maxIdleConns"` - MaxIdleConnsPerHost int `toml:"maxIdleConnsPerHost"` - MaxConnsPerHost int `toml:"maxConnsPerHost"` + Mode string `toml:"mode" wanf:"mode"` + MaxIdleConns int `toml:"maxIdleConns" wanf:"maxIdleConns"` + MaxIdleConnsPerHost int `toml:"maxIdleConnsPerHost" wanf:"maxIdleConnsPerHost"` + MaxConnsPerHost int `toml:"maxConnsPerHost" wanf:"maxConnsPerHost"` + UseCustomRawHeaders bool `toml:"useCustomRawHeaders" wanf:"useCustomRawHeaders"` } /* [gitclone] mode = "bypass" # bypass / cache -smartGitAddr = ":8080" +smartGitAddr = "http://127.0.0.1:8080" +//cacheTimeout = 10 +ForceH2C = true */ +// GitCloneConfig 定义 Git 克隆相关的配置 type GitCloneConfig struct { - Mode string `toml:"mode"` - SmartGitAddr string `toml:"smartGitAddr"` + Mode string `toml:"mode" wanf:"mode"` + SmartGitAddr string `toml:"smartGitAddr" wanf:"smartGitAddr"` + //CacheTimeout int `toml:"cacheTimeout"` + ForceH2C bool `toml:"ForceH2C" wanf:"ForceH2C"` +} + +/* +[shell] +editor = true +rewriteAPI = false +*/ +// ShellConfig 定义 Shell 相关的配置 +type ShellConfig struct { + Editor bool `toml:"editor" wanf:"editor"` + RewriteAPI bool `toml:"rewriteAPI" wanf:"rewriteAPI"` } /* [pages] mode = "internal" # "internal" or "external" -enabled = false theme = "bootstrap" # "bootstrap" or "nebula" staticDir = "/data/www" */ +// PagesConfig 定义静态页面相关的配置 type PagesConfig struct { - Mode string `toml:"mode"` - Enabled bool `toml:"enabled"` - Theme string `toml:"theme"` - StaticDir string `toml:"staticDir"` + Mode string `toml:"mode" wanf:"mode"` + Theme string `toml:"theme" wanf:"theme"` + StaticDir string `toml:"staticDir" wanf:"staticDir"` } +// LogConfig 定义日志相关的配置 type LogConfig struct { - LogFilePath string `toml:"logFilePath"` - MaxLogSize int `toml:"maxLogSize"` - Level string `toml:"level"` + LogFilePath string `toml:"logFilePath" wanf:"logFilePath"` + MaxLogSize int64 `toml:"maxLogSize" wanf:"maxLogSize"` + Level string `toml:"level" wanf:"level"` } +/* +[auth] +Method = "parameters" # "header" or "parameters" +Key = "" +Token = "token" +enabled = false +passThrough = false +ForceAllowApi = false +ForceAllowApiPassList = false +*/ +// AuthConfig 定义认证相关的配置 type AuthConfig struct { - Enabled bool `toml:"enabled"` - AuthMethod string `toml:"authMethod"` - AuthToken string `toml:"authToken"` - PassThrough bool `toml:"passThrough"` + Enabled bool `toml:"enabled" wanf:"enabled"` + Method string `toml:"method" wanf:"method"` + Key string `toml:"key" wanf:"key"` + Token string `toml:"token" wanf:"token"` + PassThrough bool `toml:"passThrough" wanf:"passThrough"` + ForceAllowApi bool `toml:"ForceAllowApi" wanf:"ForceAllowApi"` + ForceAllowApiPassList bool `toml:"ForceAllowApiPassList" wanf:"ForceAllowApiPassList"` } +// BlacklistConfig 定义黑名单相关的配置 type BlacklistConfig struct { - Enabled bool `toml:"enabled"` - BlacklistFile string `toml:"blacklistFile"` + Enabled bool `toml:"enabled" wanf:"enabled"` + BlacklistFile string `toml:"blacklistFile" wanf:"blacklistFile"` } +// WhitelistConfig 定义白名单相关的配置 type WhitelistConfig struct { - Enabled bool `toml:"enabled"` - WhitelistFile string `toml:"whitelistFile"` + Enabled bool `toml:"enabled" wanf:"enabled"` + WhitelistFile string `toml:"whitelistFile" wanf:"whitelistFile"` } +// IPFilterConfig 定义 IP 过滤相关的配置 +type IPFilterConfig struct { + Enabled bool `toml:"enabled" wanf:"enabled"` + EnableAllowList bool `toml:"enableAllowList" wanf:"enableAllowList"` + EnableBlockList bool `toml:"enableBlockList" wanf:"enableBlockList"` + IPFilterFile string `toml:"ipFilterFile" wanf:"ipFilterFile"` +} + +/* +[rateLimit] +enabled = false +ratePerMinute = 100 +burst = 10 + + [rateLimit.bandwidthLimit] + enabled = false + totalLimit = "100mbps" + totalBurst = "100mbps" + singleLimit = "10mbps" + singleBurst = "10mbps" +*/ + +// RateLimitConfig 定义限速相关的配置 type RateLimitConfig struct { - Enabled bool `toml:"enabled"` - RateMethod string `toml:"rateMethod"` - RatePerMinute int `toml:"ratePerMinute"` - Burst int `toml:"burst"` + Enabled bool `toml:"enabled" wanf:"enabled"` + RatePerMinute int `toml:"ratePerMinute" wanf:"ratePerMinute"` + Burst int `toml:"burst" wanf:"burst"` + BandwidthLimit BandwidthLimitConfig `toml:"bandwidthLimit" wanf:"bandwidthLimit"` +} + +// BandwidthLimitConfig 定义带宽限制相关的配置 +type BandwidthLimitConfig struct { + Enabled bool `toml:"enabled" wanf:"enabled"` + TotalLimit string `toml:"totalLimit" wanf:"totalLimit"` + TotalBurst string `toml:"totalBurst" wanf:"totalBurst"` + SingleLimit string `toml:"singleLimit" wanf:"singleLimit"` + SingleBurst string `toml:"singleBurst" wanf:"singleBurst"` } /* @@ -109,16 +188,193 @@ type RateLimitConfig struct { enabled = false url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" */ +// OutboundConfig 定义出站代理相关的配置 type OutboundConfig struct { - Enabled bool `toml:"enabled"` - Url string `toml:"url"` + Enabled bool `toml:"enabled" wanf:"enabled"` + Url string `toml:"url" wanf:"url"` } -// LoadConfig 从 TOML 配置文件加载配置 +/* +[docker] +enabled = false +target = "ghcr" # ghcr/dockerhub +auth = false +[docker.credentials] +user1 = "testpass" +test = "test123" +*/ +// DockerConfig 定义 Docker 相关的配置 +type DockerConfig struct { + Enabled bool `toml:"enabled" wanf:"enabled"` + Target string `toml:"target" wanf:"target"` + Auth bool `toml:"auth" wanf:"auth"` + Credentials map[string]string `toml:"credentials" wanf:"credentials"` + AuthPassThrough bool `toml:"authPassThrough" wanf:"authPassThrough"` +} + +// LoadConfig 从配置文件加载配置 func LoadConfig(filePath string) (*Config, error) { + exist, filePath2read := FileExists(filePath) + if !exist { + // 楔入配置文件 + err := DefaultConfig().WriteConfig(filePath) + if err != nil { + return nil, err + } + return DefaultConfig(), nil + } var config Config - if _, err := toml.DecodeFile(filePath, &config); err != nil { + ext := filepath.Ext(filePath2read) + if ext == ".wanf" { + if err := wanf.DecodeFile(filePath2read, &config); err != nil { + return nil, err + } + return &config, nil + } + + if _, err := toml.DecodeFile(filePath2read, &config); err != nil { return nil, err } return &config, nil } + +// WriteConfig 写入配置文件 +func (c *Config) WriteConfig(filePath string) error { + file, err := os.Create(filePath) + if err != nil { + return err + } + defer file.Close() + + ext := filepath.Ext(filePath) + if ext == ".wanf" { + err := wanf.NewStreamEncoder(file).Encode(c) + if err != nil { + return err + } + return nil + } + + encoder := toml.NewEncoder(file) + return encoder.Encode(c) +} + +// FileExists 检测文件是否存在 +func FileExists(filename string) (bool, string) { + _, err := os.Stat(filename) + if err == nil { + return true, filename + } + if os.IsNotExist(err) { + // 获取文件名(不包含路径) + base := filepath.Base(filename) + dir := filepath.Dir(filename) + + // 获取扩展名 + fileNameBody := strings.TrimSuffix(base, filepath.Ext(base)) + + // 重新组合路径, 扩展名改为.wanf, 确认是否存在 + wanfFilename := filepath.Join(dir, fileNameBody+".wanf") + + _, err = os.Stat(wanfFilename) + if err == nil { + // .wanf 文件存在 + fmt.Printf("\n Found .wanf file: %s\n", wanfFilename) + return true, wanfFilename + } else if os.IsNotExist(err) { + // .wanf 文件不存在 + return false, "" + } else { + // 其他错误 + return false, "" + } + } else { + return false, filename + } +} + +// DefaultConfig 返回默认配置结构体 +func DefaultConfig() *Config { + return &Config{ + Server: ServerConfig{ + Port: 8080, + Host: "0.0.0.0", + SizeLimit: 125, + MemLimit: 0, + Cors: "*", + Debug: false, + }, + Httpc: HttpcConfig{ + Mode: "auto", + MaxIdleConns: 100, + MaxIdleConnsPerHost: 60, + MaxConnsPerHost: 0, + }, + GitClone: GitCloneConfig{ + Mode: "bypass", + SmartGitAddr: "http://127.0.0.1:8080", + ForceH2C: false, + }, + Shell: ShellConfig{ + Editor: false, + RewriteAPI: false, + }, + Pages: PagesConfig{ + Mode: "internal", + Theme: "hub", + StaticDir: "/data/www", + }, + Log: LogConfig{ + LogFilePath: "/data/ghproxy/log/ghproxy.log", + MaxLogSize: 10, + Level: "info", + }, + Auth: AuthConfig{ + Enabled: false, + Method: "parameters", + Key: "", + Token: "token", + PassThrough: false, + ForceAllowApi: false, + ForceAllowApiPassList: false, + }, + Blacklist: BlacklistConfig{ + Enabled: false, + BlacklistFile: "/data/ghproxy/config/blacklist.json", + }, + Whitelist: WhitelistConfig{ + Enabled: false, + WhitelistFile: "/data/ghproxy/config/whitelist.json", + }, + IPFilter: IPFilterConfig{ + Enabled: false, + IPFilterFile: "/data/ghproxy/config/ipfilter.json", + EnableAllowList: false, + EnableBlockList: false, + }, + RateLimit: RateLimitConfig{ + Enabled: false, + RatePerMinute: 100, + Burst: 10, + BandwidthLimit: BandwidthLimitConfig{ + Enabled: false, + TotalLimit: "100mbps", + TotalBurst: "100mbps", + SingleLimit: "10mbps", + SingleBurst: "10mbps", + }, + }, + Outbound: OutboundConfig{ + Enabled: false, + Url: "socks5://127.0.0.1:1080", + }, + Docker: DockerConfig{ + Enabled: false, + Target: "dockerhub", + Auth: false, + Credentials: map[string]string{ + "testpass": "test123", + }, + }, + } +} diff --git a/config/config.toml b/config/config.toml index 7b07a17..ce490ca 100644 --- a/config/config.toml +++ b/config/config.toml @@ -2,7 +2,7 @@ host = "0.0.0.0" port = 8080 sizeLimit = 125 # MB -H2C = true +memLimit = 0 # MB cors = "*" # "*"/"" -> "*" ; "nil" -> "" ; debug = false @@ -11,10 +11,16 @@ mode = "auto" # "auto" or "advanced" maxIdleConns = 100 # only for advanced mode maxIdleConnsPerHost = 60 # only for advanced mode maxConnsPerHost = 0 # only for advanced mode +useCustomRawHeaders = false [gitclone] mode = "bypass" # bypass / cache smartGitAddr = "http://127.0.0.1:8080" +ForceH2C = false + +[shell] +editor = false +rewriteAPI = false [pages] mode = "internal" # "internal" or "external" @@ -24,13 +30,16 @@ staticDir = "/data/www" [log] logFilePath = "/data/ghproxy/log/ghproxy.log" maxLogSize = 5 # MB -level = "info" # dump, debug, info, warn, error, none +level = "info" # debug, info, warn, error, none [auth] -authMethod = "parameters" # "header" or "parameters" -authToken = "token" +method = "parameters" # "header" or "parameters" +token = "token" +key = "" enabled = false passThrough = false +ForceAllowApi = false +ForceAllowApiPassList = false [blacklist] blacklistFile = "/data/ghproxy/config/blacklist.json" @@ -40,12 +49,32 @@ enabled = false enabled = false whitelistFile = "/data/ghproxy/config/whitelist.json" +[ipFilter] +enabled = false +enableAllowList = false +enableBlockList = false +ipFilterFile = "/data/ghproxy/config/ipfilter.json" + [rateLimit] enabled = false -rateMethod = "total" # "ip" or "total" ratePerMinute = 180 burst = 5 +[rateLimit.bandwidthLimit] + enabled = false + totalLimit = "100mbps" + totalBurst = "100mbps" + singleLimit = "10mbps" + singleBurst = "10mbps" + [outbound] enabled = false -url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" \ No newline at end of file +url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" + +[docker] +enabled = false +target = "dockerhub" # ghcr/dockerhub/ custom +auth = false +[docker.credentials] +user1 = "testpass" +test = "test123" \ No newline at end of file diff --git a/config/ipfilter.json b/config/ipfilter.json new file mode 100644 index 0000000..283bbd8 --- /dev/null +++ b/config/ipfilter.json @@ -0,0 +1,11 @@ +{ + "allow": [ + "127.0.0.1", + "192.168.1.0/24", + "::1" + ], + "block": [ + "10.0.0.0/8", + "192.168.1.0/24" + ] +} \ No newline at end of file diff --git a/deploy/config.toml b/deploy/config.toml index e61fdd0..eb6d7b8 100644 --- a/deploy/config.toml +++ b/deploy/config.toml @@ -1,7 +1,9 @@ [server] host = "127.0.0.1" port = 8080 +netlib = "netpoll" # "netpoll" / "std" "standard" "net/http" "net" sizeLimit = 125 # MB +memLimit = 0 # MB H2C = true cors = "*" # "*"/"" -> "*" ; "nil" -> "" ; debug = false @@ -15,6 +17,11 @@ maxConnsPerHost = 0 # only for advanced mode [gitclone] mode = "bypass" # bypass / cache smartGitAddr = "http://127.0.0.1:8080" +ForceH2C = false + +[shell] +editor = false +rewriteAPI = false [pages] mode = "internal" # "internal" or "external" @@ -25,12 +32,14 @@ staticDir = "/usr/local/ghproxy/pages" logFilePath = "/usr/local/ghproxy/log/ghproxy.log" maxLogSize = 5 # MB level = "info" # dump, debug, info, warn, error, none +hertzLogPath = "/usr/local/ghproxy/log/hertz.log" [auth] authMethod = "parameters" # "header" or "parameters" authToken = "token" enabled = false passThrough = false +ForceAllowApi = false [blacklist] blacklistFile = "/usr/local/ghproxy/config/blacklist.json" @@ -49,3 +58,7 @@ burst = 5 [outbound] enabled = false url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" + +[docker] +enabled = false +target = "ghcr" # ghcr/dockerhub \ No newline at end of file diff --git a/deploy/ghproxy.service b/deploy/ghproxy.service index df85e32..224ce31 100644 --- a/deploy/ghproxy.service +++ b/deploy/ghproxy.service @@ -3,7 +3,7 @@ Description=Github Proxy Service After=network.target [Service] -ExecStart=/bin/bash -c '/usr/local/ghproxy/ghproxy -cfg /usr/local/ghproxy/config/config.toml > /usr/local/ghproxy/log/run.log 2>&1' +ExecStart=/bin/bash -c '/usr/local/ghproxy/ghproxy -c /usr/local/ghproxy/config/config.toml > /usr/local/ghproxy/log/run.log 2>&1' WorkingDirectory=/usr/local/ghproxy Restart=always User=root diff --git a/deploy/install-dev.sh b/deploy/install-dev.sh index 24c8cf0..a949110 100644 --- a/deploy/install-dev.sh +++ b/deploy/install-dev.sh @@ -123,7 +123,7 @@ Description=Github Proxy Service After=network.target [Service] -ExecStart=/bin/bash -c '$ghproxy_dir/ghproxy -cfg $ghproxy_dir/config/config.toml > $ghproxy_dir/log/run.log 2>&1' +ExecStart=/bin/bash -c '$ghproxy_dir/ghproxy -c $ghproxy_dir/config/config.toml > $ghproxy_dir/log/run.log 2>&1' WorkingDirectory=$ghproxy_dir Restart=always User=root diff --git a/deploy/install.sh b/deploy/install.sh index f050a0b..c8a83c8 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -123,7 +123,7 @@ Description=Github Proxy Service After=network.target [Service] -ExecStart=/bin/bash -c '$ghproxy_dir/ghproxy -cfg $ghproxy_dir/config/config.toml > $ghproxy_dir/log/run.log 2>&1' +ExecStart=/bin/bash -c '$ghproxy_dir/ghproxy -c $ghproxy_dir/config/config.toml > $ghproxy_dir/log/run.log 2>&1' WorkingDirectory=$ghproxy_dir Restart=always User=root diff --git a/docker/compose/docker-compose.yml b/docker/compose/docker-compose.yml index dc33343..c120f35 100644 --- a/docker/compose/docker-compose.yml +++ b/docker/compose/docker-compose.yml @@ -4,8 +4,7 @@ services: image: 'wjqserver/ghproxy:latest' restart: always volumes: - - './ghproxy/log/run:/data/ghproxy/log' - - './ghproxy/log/caddy:/data/caddy/log' + - './ghproxy/log:/data/ghproxy/log' - './ghproxy/config:/data/ghproxy/config' ports: - '7210:8080' diff --git a/docker/dockerfile/dev/Dockerfile b/docker/dockerfile/dev/Dockerfile index 470ea48..5248916 100644 --- a/docker/dockerfile/dev/Dockerfile +++ b/docker/dockerfile/dev/Dockerfile @@ -3,6 +3,7 @@ FROM alpine:latest AS builder ARG USER=WJQSERVER-STUDIO ARG REPO=ghproxy ARG APPLICATION=ghproxy +ARG BRANCH=dev ARG TARGETOS ARG TARGETARCH ARG TARGETPLATFORM @@ -16,32 +17,35 @@ RUN mkdir -p /data/${APPLICATION}/log RUN apk add --no-cache curl wget tar # 后端 -RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/dev/DEV-VERSION) && \ +RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/DEV-VERSION) && \ wget -O /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz && \ tar -zxvf /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz -C /data/${APPLICATION} && \ rm -rf /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz -RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/dev/docker/dockerfile/dev/init.sh # 拉取配置 -#RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/dev/caddyfile/dev/Caddyfile -RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/dev/config/config.toml -RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/dev/config/blacklist.json -RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/dev/config/whitelist.json +RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/config/config.toml +RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/config/blacklist.json +RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/config/whitelist.json # 权限 RUN chmod +x /data/${APPLICATION}/${APPLICATION} -RUN chmod +x /usr/local/bin/init.sh FROM alpine:latest RUN apk add --no-cache curl +ARG USER=WJQSERVER-STUDIO +ARG REPO=ghproxy +ARG BRANCH=v3 +ARG APPLICATION=ghproxy +ARG TARGETOS +ARG TARGETARCH +ARG TARGETPLATFORM + COPY --from=builder /data/www /data/www COPY --from=builder /data/${APPLICATION} /data/${APPLICATION} -COPY --from=builder /usr/local/bin/init.sh /usr/local/bin/init.sh # 权限 RUN chmod +x /data/${APPLICATION}/${APPLICATION} -RUN chmod +x /usr/local/bin/init.sh -CMD ["/usr/local/bin/init.sh"] \ No newline at end of file +CMD ["/data/ghproxy/ghproxy"] \ No newline at end of file diff --git a/docker/dockerfile/dev/init.sh b/docker/dockerfile/dev/init.sh deleted file mode 100644 index f9a10e7..0000000 --- a/docker/dockerfile/dev/init.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -APPLICATION=ghproxy - -if [ ! -f /data/${APPLICATION}/config/blacklist.json ]; then - cp /data/${APPLICATION}/blacklist.json /data/${APPLICATION}/config/blacklist.json -fi - -if [ ! -f /data/${APPLICATION}/config/whitelist.json ]; then - cp /data/${APPLICATION}/whitelist.json /data/${APPLICATION}/config/whitelist.json -fi - -if [ ! -f /data/${APPLICATION}/config/config.toml ]; then - cp /data/${APPLICATION}/config.toml /data/${APPLICATION}/config/config.toml -fi - -/data/${APPLICATION}/${APPLICATION} -cfg /data/${APPLICATION}/config/config.toml > /data/${APPLICATION}/log/run.log 2>&1 diff --git a/docker/dockerfile/release/Dockerfile b/docker/dockerfile/release/Dockerfile index 0d960e0..0e8c630 100644 --- a/docker/dockerfile/release/Dockerfile +++ b/docker/dockerfile/release/Dockerfile @@ -2,6 +2,7 @@ FROM alpine:latest AS builder ARG USER=WJQSERVER-STUDIO ARG REPO=ghproxy +ARG BRANCH=main ARG APPLICATION=ghproxy ARG TARGETOS ARG TARGETARCH @@ -16,34 +17,37 @@ RUN mkdir -p /data/${APPLICATION}/log RUN apk add --no-cache curl wget tar # 后端 -RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \ +RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/VERSION) && \ wget -O /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz && \ tar -zxvf /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz -C /data/${APPLICATION} && \ rm -rf /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz -RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/release/init.sh # 拉取配置 -#RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/release/Caddyfile -RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/config.toml -RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/blacklist.json -RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/whitelist.json +RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/config/config.toml +RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/config/blacklist.json +RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/${BRANCH}/config/whitelist.json # 权限 RUN chmod +x /data/${APPLICATION}/${APPLICATION} -RUN chmod +x /usr/local/bin/init.sh FROM alpine:latest RUN apk add --no-cache curl +ARG USER=WJQSERVER-STUDIO +ARG REPO=ghproxy +ARG BRANCH=v3 +ARG APPLICATION=ghproxy +ARG TARGETOS +ARG TARGETARCH +ARG TARGETPLATFORM + COPY --from=builder /data/www /data/www -#COPY --from=builder /data/caddy /data/caddy COPY --from=builder /data/${APPLICATION} /data/${APPLICATION} -COPY --from=builder /usr/local/bin/init.sh /usr/local/bin/init.sh # 权限 RUN chmod +x /data/${APPLICATION}/${APPLICATION} -RUN chmod +x /usr/local/bin/init.sh -CMD ["/usr/local/bin/init.sh"] +CMD ["/data/ghproxy/ghproxy"] + diff --git a/docker/dockerfile/release/init.sh b/docker/dockerfile/release/init.sh deleted file mode 100644 index f9a10e7..0000000 --- a/docker/dockerfile/release/init.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -APPLICATION=ghproxy - -if [ ! -f /data/${APPLICATION}/config/blacklist.json ]; then - cp /data/${APPLICATION}/blacklist.json /data/${APPLICATION}/config/blacklist.json -fi - -if [ ! -f /data/${APPLICATION}/config/whitelist.json ]; then - cp /data/${APPLICATION}/whitelist.json /data/${APPLICATION}/config/whitelist.json -fi - -if [ ! -f /data/${APPLICATION}/config/config.toml ]; then - cp /data/${APPLICATION}/config.toml /data/${APPLICATION}/config/config.toml -fi - -/data/${APPLICATION}/${APPLICATION} -cfg /data/${APPLICATION}/config/config.toml > /data/${APPLICATION}/log/run.log 2>&1 diff --git a/gitclone/git-client.go b/gitclone/git-client.go deleted file mode 100644 index 29f0fa8..0000000 --- a/gitclone/git-client.go +++ /dev/null @@ -1,120 +0,0 @@ -package gitclone - -import ( - "archive/tar" - "bytes" - "errors" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/go-git/go-git/v5" - "github.com/pierrec/lz4" -) - -func CloneRepo(dir string, repoName string, repoUrl string) error { - repoPath := dir - _, err := git.PlainClone(repoPath, true, &git.CloneOptions{ - URL: repoUrl, - Progress: os.Stdout, - Mirror: true, - }) - if err != nil && !errors.Is(err, git.ErrRepositoryAlreadyExists) { - fmt.Printf("Fail to clone: %v\n", err) - } else if err != nil && errors.Is(err, git.ErrRepositoryAlreadyExists) { - // 移除文件夹 - fmt.Printf("Repository already exists\n") - err = os.RemoveAll(repoPath) - if err != nil { - fmt.Printf("Fail to remove: %v\n", err) - return err - } - _, err = git.PlainClone(repoPath, true, &git.CloneOptions{ - URL: repoUrl, - Progress: os.Stdout, - Mirror: true, - }) - if err != nil { - fmt.Printf("Fail to clone: %v\n", err) - return err - } - } - - // 压缩 - err = CompressRepo(repoPath) - if err != nil { - fmt.Printf("Fail to compress: %v\n", err) - return err - } - return nil -} - -// CompressRepo 将指定的仓库压缩成 LZ4 格式的压缩包 -func CompressRepo(repoPath string) error { - lz4File, err := os.Create(repoPath + ".lz4") - if err != nil { - return fmt.Errorf("failed to create LZ4 file: %w", err) - } - defer lz4File.Close() - - // 创建 LZ4 编码器 - lz4Writer := lz4.NewWriter(lz4File) - defer lz4Writer.Close() - - // 创建 tar.Writer - tarBuffer := new(bytes.Buffer) - tarWriter := tar.NewWriter(tarBuffer) - - // 遍历仓库目录并打包 - err = filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // 创建 tar 文件头 - header, err := tar.FileInfoHeader(info, "") - if err != nil { - return err - } - header.Name, err = filepath.Rel(repoPath, path) - if err != nil { - return err - } - - // 写入 tar 文件头 - if err := tarWriter.WriteHeader(header); err != nil { - return err - } - - // 如果是文件,写入文件内容 - if !info.IsDir() { - file, err := os.Open(path) - if err != nil { - return err - } - defer file.Close() - - _, err = io.Copy(tarWriter, file) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return fmt.Errorf("failed to walk through repo directory: %w", err) - } - - // 关闭 tar.Writer - if err := tarWriter.Close(); err != nil { - return fmt.Errorf("failed to close tar writer: %w", err) - } - - // 将 tar 数据写入 LZ4 压缩包 - if _, err := lz4Writer.Write(tarBuffer.Bytes()); err != nil { - return fmt.Errorf("failed to write to LZ4 file: %w", err) - } - - return nil -} diff --git a/gitclone/gitclone.go b/gitclone/gitclone.go deleted file mode 100644 index d9a3f60..0000000 --- a/gitclone/gitclone.go +++ /dev/null @@ -1,14 +0,0 @@ -package gitclone - -import ( - "github.com/WJQSERVER-STUDIO/go-utils/logger" -) - -var ( - logw = logger.Logw - logDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError -) diff --git a/gitclone/smart-http.go b/gitclone/smart-http.go deleted file mode 100644 index cb06f0c..0000000 --- a/gitclone/smart-http.go +++ /dev/null @@ -1,164 +0,0 @@ -package gitclone - -/* -package gitclone - -import ( - "compress/gzip" - "ghproxy/config" - "io" - "log" - "net/http" - "os" - - "github.com/gin-gonic/gin" - "github.com/go-git/go-billy/v5/osfs" - "github.com/go-git/go-git/v5/plumbing/format/pktline" - "github.com/go-git/go-git/v5/plumbing/protocol/packp" - "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/go-git/go-git/v5/plumbing/transport/server" -) - -// MIT https://github.com/erred/gitreposerver - -// httpInfoRefs 函数处理 /info/refs 请求,用于 Git 客户端获取仓库的引用信息。 -// 返回一个 gin.HandlerFunc 类型的处理函数。 -func HttpInfoRefs(cfg *config.Config) gin.HandlerFunc { - return func(c *gin.Context) { - - repo := c.Param("repo") // 从 Gin 上下文中获取路由参数 "repo",即仓库名 - username := c.Param("username") - repoName := repo - dir := cfg.GitClone.Dir + "/" + username + "/" + repo - url := "https://github.com/" + username + "/" + repo - - // 输出 repo user dir url - logInfo("Repo: %s, User: %s, Dir: %s, Url: %s\n", repoName, username, dir, url) - - _, err := os.Stat(dir) // 检查目录是否存在 - if os.IsNotExist(err) { - CloneRepo(dir, repoName, url) - } - - // 检查请求参数 "service" 是否为 "git-upload-pack"。 - // 这是为了确保只处理 smart git 的 upload-pack 服务请求。 - if c.Query("service") != "git-upload-pack" { - c.String(http.StatusForbidden, "only smart git") // 如果 service 参数不正确,返回 403 Forbidden 状态码和错误信息 - log.Printf("Request to /info/refs with invalid service: %s, repo: %s\n", c.Query("service"), repoName) // 记录无效 service 参数的日志 - return // 结束处理 - } - - c.Header("content-type", "application/x-git-upload-pack-advertisement") // 设置 HTTP 响应头的 Content-Type 为 advertisement 类型。 - // 这种类型用于告知客户端服务器支持的 Git 服务。 - - ep, err := transport.NewEndpoint("/") // 创建一个新的传输端点 (Endpoint)。这里使用根路径 "/" 作为端点,表示本地文件系统。 - if err != nil { // 检查创建端点是否出错 - log.Printf("Error creating endpoint: %v, repo: %s\n", err, repoName) // 记录创建端点错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - bfs := osfs.New(dir) // 创建一个基于本地文件系统的 billy Filesystem (bfs)。dir 变量指定了仓库的根目录。 - ld := server.NewFilesystemLoader(bfs) // 创建一个基于文件系统的仓库加载器 (Loader)。Loader 负责从文件系统中加载仓库。 - svr := server.NewServer(ld) // 创建一个新的 Git 服务器 (Server)。Server 负责处理 Git 服务请求。 - sess, err := svr.NewUploadPackSession(ep, nil) // 创建一个新的 upload-pack 会话 (Session)。Session 用于处理客户端的 upload-pack 请求。 - if err != nil { // 检查创建会话是否出错 - log.Printf("Error creating upload pack session: %v, repo: %s\n", err, repoName) // 记录创建会话错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - ar, err := sess.AdvertisedReferencesContext(c.Request.Context()) // 获取已通告的引用 (Advertised References)。Advertised References 包含了仓库的分支、标签等信息。 - if err != nil { // 检查获取 Advertised References 是否出错 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - log.Printf("Error getting advertised references: %v, repo: %s\n", err, repoName) // 记录获取 Advertised References 错误日志 - return // 结束处理 - } - - // 设置 Advertised References 的前缀 (Prefix)。 - // Prefix 通常包含 # service=git-upload-pack 和 pktline.Flush。 - // # service=git-upload-pack 用于告知客户端服务器提供的是 upload-pack 服务。 - // pktline.Flush 用于在 pkt-line 格式中发送 flush-pkt。 - ar.Prefix = [][]byte{ - []byte("# service=git-upload-pack"), // 服务类型声明 - pktline.Flush, // pkt-line flush 信号 - } - err = ar.Encode(c.Writer) // 将 Advertised References 编码并写入 HTTP 响应。使用 pkt-line 格式进行编码。 - if err != nil { // 检查编码和写入是否出错 - log.Printf("Error encoding advertised references: %v, repo: %s\n", err, repoName) // 记录编码错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - } -} - -// httpGitUploadPack 函数处理 /git-upload-pack 请求,用于处理 Git 客户端的推送 (push) 操作。 -// 返回一个 gin.HandlerFunc 类型的处理函数。 -func HttpGitUploadPack(cfg *config.Config) gin.HandlerFunc { - return func(c *gin.Context) { - - repo := c.Param("repo") // 从 Gin 上下文中获取路由参数 "repo",即仓库名 - username := c.Param("username") - repoName := repo - dir := cfg.GitClone.Dir + "/" + username + "/" + repo - - c.Header("content-type", "application/x-git-upload-pack-result") // 设置 HTTP 响应头的 Content-Type 为 result 类型。 - // 这种类型用于返回 upload-pack 操作的结果。 - - var bodyReader io.Reader = c.Request.Body // 初始化 bodyReader 为 HTTP 请求的 body。用于读取客户端发送的数据。 - // 检查请求头 "Content-Encoding" 是否为 "gzip"。 - // 如果是 gzip,则需要使用 gzip 解压缩请求 body。 - if c.GetHeader("Content-Encoding") == "gzip" { - gzipReader, err := gzip.NewReader(c.Request.Body) // 创建一个新的 gzip Reader,用于解压缩请求 body。 - if err != nil { // 检查创建 gzip Reader 是否出错 - log.Printf("Error creating gzip reader: %v, repo: %s\n", err, repoName) // 记录创建 gzip Reader 错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - defer gzipReader.Close() // 延迟关闭 gzip Reader,确保资源释放 - bodyReader = gzipReader // 将 bodyReader 替换为 gzip Reader,后续从 gzip Reader 中读取数据 - } - - upr := packp.NewUploadPackRequest() // 创建一个新的 UploadPackRequest 对象。UploadPackRequest 用于解码客户端发送的 upload-pack 请求数据。 - err := upr.Decode(bodyReader) // 解码请求 body 中的数据到 UploadPackRequest 对象中。使用 packp 协议格式进行解码。 - if err != nil { // 检查解码是否出错 - log.Printf("Error decoding upload pack request: %v, repo: %s\n", err, repoName) // 记录解码错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - ep, err := transport.NewEndpoint("/") // 创建一个新的传输端点 (Endpoint)。这里使用根路径 "/" 作为端点,表示本地文件系统。 - if err != nil { // 检查创建端点是否出错 - log.Printf("Error creating endpoint: %v, repo: %s\n", err, repoName) // 记录创建端点错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - bfs := osfs.New(dir) // 创建一个基于本地文件系统的 billy Filesystem (bfs)。dir 变量指定了仓库的根目录。 - ld := server.NewFilesystemLoader(bfs) // 创建一个基于文件系统的仓库加载器 (Loader)。Loader 负责从文件系统中加载仓库。 - svr := server.NewServer(ld) // 创建一个新的 Git 服务器 (Server)。Server 负责处理 Git 服务请求。 - sess, err := svr.NewUploadPackSession(ep, nil) // 创建一个新的 upload-pack 会话 (Session)。Session 用于处理客户端的 upload-pack 请求。 - if err != nil { // 检查创建会话是否出错 - log.Printf("Error creating upload pack session: %v, repo: %s\n", err, repoName) // 记录创建会话错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - res, err := sess.UploadPack(c.Request.Context(), upr) // 处理 upload-pack 请求,执行实际的仓库推送操作。 - // sess.UploadPack 函数接收 context 和 UploadPackRequest 对象作为参数,返回 UploadPackResult 和 error。 - if err != nil { // 检查 UploadPack 操作是否出错 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - log.Printf("Error during upload pack: %v, repo: %s\n", err, repoName) // 记录 UploadPack 操作错误日志 - return // 结束处理 - } - - err = res.Encode(c.Writer) // 将 UploadPackResult 编码并写入 HTTP 响应。使用 pkt-line 格式进行编码。 - if err != nil { // 检查编码和写入是否出错 - log.Printf("Error encoding upload pack result: %v, repo: %s\n", err, repoName) // 记录编码错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - } -} - -*/ diff --git a/go.mod b/go.mod index 5d6971d..d43cb00 100644 --- a/go.mod +++ b/go.mod @@ -1,61 +1,27 @@ module ghproxy -go 1.24.1 +go 1.25.1 require ( - github.com/BurntSushi/toml v1.4.0 - github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 - github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0 - github.com/gin-gonic/gin v1.10.0 - github.com/go-git/go-git/v5 v5.14.0 - github.com/pierrec/lz4 v2.6.1+incompatible - github.com/satomitouka/touka-httpc v0.3.3 - golang.org/x/net v0.37.0 - golang.org/x/time v0.11.0 + github.com/BurntSushi/toml v1.5.0 + github.com/WJQSERVER-STUDIO/httpc v0.8.2 + golang.org/x/net v0.44.0 + golang.org/x/time v0.13.0 ) require ( - dario.cat/mergo v1.0.1 // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/ProtonMail/go-crypto v1.1.6 // indirect - github.com/WJQSERVER-STUDIO/go-utils/log v0.0.1 // indirect - github.com/bytedance/sonic v1.13.1 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudflare/circl v1.6.0 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect - github.com/cyphar/filepath-securejoin v0.4.1 // indirect - github.com/emirpasic/gods v1.18.1 // indirect - github.com/frankban/quicktest v1.14.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/gin-contrib/sse v1.0.0 // indirect - github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-git/go-billy/v5 v5.6.2 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.25.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pjbgf/sha1cd v0.3.2 // indirect - github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/skeema/knownhosts v1.3.1 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/arch v0.15.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + github.com/WJQSERVER-STUDIO/go-utils/iox v0.0.2 + github.com/WJQSERVER-STUDIO/go-utils/limitreader v0.0.2 + github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee + github.com/fenthope/bauth v0.0.1 + github.com/fenthope/ikumi v0.0.2 + github.com/fenthope/ipfilter v0.0.1 + github.com/fenthope/reco v0.0.4 + github.com/fenthope/record v0.0.4 + github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b + github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/infinite-iroha/touka v0.3.7 + github.com/wjqserver/modembed v0.0.1 ) + +require github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index 2699522..97337f6 100644 --- a/go.sum +++ b/go.sum @@ -1,186 +1,34 @@ -dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= -dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= -github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= -github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4 h1:JLtFd00AdFg/TP+dtvIzLkdHwKUGPOAijN1sMtEYoFg= -github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.4/go.mod h1:FZ6XE+4TKy4MOfX1xWKe6Rwsg0ucYFCdNh1KLvyKTfc= -github.com/WJQSERVER-STUDIO/go-utils/log v0.0.1 h1:gJEQspQPB527Vp2FPcdOrynQEj3YYtrg1ixVSB/JvZM= -github.com/WJQSERVER-STUDIO/go-utils/log v0.0.1/go.mod h1:j9Q+xnwpOfve7/uJnZ2izRQw6NNoXjvJHz7vUQAaLZE= -github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0 h1:Uk4N7Sh4OPth3am3xVv17JlAm7tsna97ZLQRpQj7r5c= -github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0/go.mod h1:mtxlnDdwsHcqDDpAQLa94nxbPFwNHSAHbBbIXQAA3po= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= -github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= -github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= -github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= -github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= -github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= -github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= -github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= -github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= -github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60= -github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= -github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= -github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= -github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= -github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/satomitouka/touka-httpc v0.3.3 h1:Th0uJ5do3oqqZgdUDtqD1SH11x8TcJmrwHMJQlEIKCg= -github.com/satomitouka/touka-httpc v0.3.3/go.mod h1:sNXyW5XBufkwB9ZJ+PIlgN/6xiJ7aZV1fWGrXR0u3bA= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= -github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= -github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/WJQSERVER-STUDIO/go-utils/iox v0.0.2 h1:AiIHXP21LpK7pFfqUlUstgQEWzjbekZgxOuvVwiMfyM= +github.com/WJQSERVER-STUDIO/go-utils/iox v0.0.2/go.mod h1:mCLqYU32bTmEE6dpj37MKKiZgz70Jh/xyK9vVbq6pok= +github.com/WJQSERVER-STUDIO/go-utils/limitreader v0.0.2 h1:8bBkKk6E2Zr+I5szL7gyc5f0DK8N9agIJCpM1Cqw2NE= +github.com/WJQSERVER-STUDIO/go-utils/limitreader v0.0.2/go.mod h1:yPX8xuZH+py7eLJwOYj3VVI/4/Yuy5+x8Mhq8qezcPg= +github.com/WJQSERVER-STUDIO/httpc v0.8.2 h1:PFPLodV0QAfGEP6915J57vIqoKu9cGuuiXG/7C9TNUk= +github.com/WJQSERVER-STUDIO/httpc v0.8.2/go.mod h1:8WhHVRO+olDFBSvL5PC/bdMkb6U3vRdPJ4p4pnguV5Y= +github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee h1:tJ31DNBn6UhWkk8fiikAQWqULODM+yBcGAEar1tzdZc= +github.com/WJQSERVER/wanf v0.0.0-20250810023226-e51d9d0737ee/go.mod h1:q2Pyg+G+s1acMWxrbI4CwS/Yk76/BzLREEdZ8iFwUNE= +github.com/fenthope/bauth v0.0.1 h1:+4UIQshGx3mYD4L3f2S4MLZOi5PWU7fU5GK3wsZvwzE= +github.com/fenthope/bauth v0.0.1/go.mod h1:1fveTpgfR1p+WXQ8MXm9BfBCeNYi55j23jxCOGOvBSA= +github.com/fenthope/ikumi v0.0.2 h1:5oaSTf/Msp7M2O3o/X20omKWEQbFhX4KV0CVF21oCdk= +github.com/fenthope/ikumi v0.0.2/go.mod h1:IYbxzOGndZv/yRrbVMyV6dxh06X2wXCbfxrTRM1IruU= +github.com/fenthope/ipfilter v0.0.1 h1:HrYAyixCMvsDAz36GRyFfyCNtrgYwzrhMcY0XV7fGcM= +github.com/fenthope/ipfilter v0.0.1/go.mod h1:QfY0GrpG0D82HROgdH4c9eog4js42ghLIfl/iM4MvvY= +github.com/fenthope/reco v0.0.4 h1:yo2g3aWwdoMpaZWZX4SdZOW7mCK82RQIU/YI8ZUQThM= +github.com/fenthope/reco v0.0.4/go.mod h1:eMyS8HpdMVdJ/2WJt6Cvt8P1EH9Igzj5lSJrgc+0jeg= +github.com/fenthope/record v0.0.4 h1:/1JHNCxiXGLL/qCh4LEGaAvhj4CcKsb6siTxjLmjdO4= +github.com/fenthope/record v0.0.4/go.mod h1:G0a6KCiCDyX2SsC3nfzSN651fJKxH482AyJvzlnvAJU= +github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b h1:6Q4zRHXS/YLOl9Ng1b1OOOBWMidAQZR3Gel0UKPC/KU= +github.com/go-json-experiment/json v0.0.0-20250813233538-9b1f9ea2e11b/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/infinite-iroha/touka v0.3.7 h1:bIIZW5Weh7lVpyOWh4FmyR9UOfb5FOt+cR9yQ30FJLA= +github.com/infinite-iroha/touka v0.3.7/go.mod h1:uwkF1gTrNEgQ4P/Gwtk6WLbERehq3lzB8x1FMedyrfE= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= -github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= -golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= -golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +github.com/wjqserver/modembed v0.0.1 h1:8ZDz7t9M5DLrUFlYgBUUmrMzxWsZPmHvOazkr/T2jEs= +github.com/wjqserver/modembed v0.0.1/go.mod h1:sYbQJMAjSBsdYQrUsuHY380XXE1CuRh8g9yyCztTXOQ= +golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= +golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= +golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= diff --git a/init.sh b/init.sh deleted file mode 100644 index 601185f..0000000 --- a/init.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -APPLICATON=ghproxy - -if [ ! -f /data/${APPLICATON}/config/blacklist.json ]; then - cp /data/${APPLICATON}/blacklist.json /data/${APPLICATON}/config/blacklist.json -fi - -if [ ! -f /data/${APPLICATON}/config/whitelist.json ]; then - cp /data/${APPLICATON}/whitelist.json /data/${APPLICATON}/config/whitelist.json -fi - -if [ ! -f /data/${APPLICATON}/config/config.yaml ]; then - cp /data/${APPLICATON}/config.yaml /data/${APPLICATON}/config/config.yaml -fi - -/data/${APPLICATON}/${APPLICATON} > /data/${APPLICATON}/log/run.log 2>&1 - diff --git a/loggin/loggin.go b/loggin/loggin.go deleted file mode 100644 index ccc66be..0000000 --- a/loggin/loggin.go +++ /dev/null @@ -1,34 +0,0 @@ -package loggin - -import ( - "ghproxy/timing" - "time" - - "github.com/WJQSERVER-STUDIO/go-utils/logger" - "github.com/gin-gonic/gin" -) - -var ( - logw = logger.Logw - LogDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError -) - -// 日志中间件 -func Middleware() gin.HandlerFunc { - return func(c *gin.Context) { - // 处理请求 - c.Next() - - var timingResults time.Duration - - // 获取计时结果 - timingResults, _ = timing.Get(c) - - // 记录日志 IP METHOD URL USERAGENT PROTOCOL STATUS TIMING - logInfo("%s %s %s %s %d %s ", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Writer.Status(), timingResults) - } -} diff --git a/main.go b/main.go index f8adcc2..5f5b3ed 100644 --- a/main.go +++ b/main.go @@ -4,54 +4,116 @@ import ( "embed" "flag" "fmt" - "io" "io/fs" "net/http" + "os" + "runtime/debug" + "strings" "time" "ghproxy/api" "ghproxy/auth" "ghproxy/config" - "ghproxy/middleware/loggin" - "ghproxy/middleware/timing" "ghproxy/proxy" - "ghproxy/rate" - "github.com/WJQSERVER-STUDIO/go-utils/logger" + "github.com/WJQSERVER-STUDIO/httpc" + "github.com/fenthope/bauth" - "github.com/gin-gonic/gin" + "ghproxy/weakcache" + + "github.com/fenthope/ikumi" + "github.com/fenthope/ipfilter" + "github.com/fenthope/reco" + "github.com/fenthope/record" + "github.com/infinite-iroha/touka" + "github.com/wjqserver/modembed" + "golang.org/x/time/rate" + + _ "net/http/pprof" ) var ( - cfg *config.Config - router *gin.Engine - configfile = "/data/ghproxy/config/config.toml" - cfgfile string - version string - dev string - runMode string - limiter *rate.RateLimiter - iplimiter *rate.IPRateLimiter + cfg *config.Config + r *touka.Engine + configfile = "/data/ghproxy/config/config.toml" + httpClient *httpc.Client + cfgfile string + version string + runMode string + showVersion bool + showHelp bool ) var ( - //go:embed pages/bootstrap/* + //go:embed pages/* pagesFS embed.FS - //go:embed pages/nebula/* - NebulaPagesFS embed.FS ) var ( - logw = logger.Logw - logDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError + wcache *weakcache.Cache[string] // docker token缓存 +) + +var ( + // supportedThemes 定义了所有支持的主题, 用于验证配置和动态加载 + supportedThemes = map[string]struct{}{ + "bootstrap": {}, + "nebula": {}, + "design": {}, + "metro": {}, + "classic": {}, + "mino": {}, + "hub": {}, + "free": {}, + } +) + +var ( + logger *reco.Logger ) func readFlag() { - flag.StringVar(&cfgfile, "cfg", configfile, "config file path") + flag.StringVar(&cfgfile, "c", configfile, "config file path") + flag.Func("cfg", "exit", func(s string) error { + + // 被弃用的flag, fail退出 + fmt.Printf("\n") + fmt.Println("[ERROR] cfg flag is deprecated, please use -c instead") + fmt.Printf("\n") + flag.Usage() + os.Exit(2) + return nil + }) + flag.BoolVar(&showVersion, "v", false, "show version and exit") // 添加-v标志 + flag.BoolVar(&showHelp, "h", false, "show help message and exit") // 添加-h标志 + // 捕获未定义的 flag + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + fmt.Fprintln(os.Stderr, "\nInvalid flags:") + + // 检查未定义的flags + invalidFlags := []string{} + for _, arg := range os.Args[1:] { + if arg[0] == '-' && arg != "-h" && arg != "-v" { // 检查是否是flag, 排除 -h 和 -v + defined := false + flag.VisitAll(func(f *flag.Flag) { + if "-"+f.Name == arg { + defined = true + } + }) + if !defined { + invalidFlags = append(invalidFlags, arg) + } + } + } + for _, flag := range invalidFlags { + fmt.Fprintf(os.Stderr, " %s\n", flag) + } + if len(invalidFlags) > 0 { + os.Exit(2) + } + + } } func loadConfig() { @@ -59,8 +121,11 @@ func loadConfig() { cfg, err = config.LoadConfig(cfgfile) if err != nil { fmt.Printf("Failed to load config: %v\n", err) + // 如果配置文件加载失败, 也显示帮助信息并退出 + flag.Usage() + os.Exit(1) } - if cfg.Server.Debug { + if cfg != nil && cfg.Server.Debug { // 确保 cfg 不为 nil fmt.Println("Config File Path: ", cfgfile) fmt.Printf("Loaded config: %v\n", cfg) } @@ -68,217 +133,395 @@ func loadConfig() { func setupLogger(cfg *config.Config) { var err error - err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize) + if cfg.Log.Level == "" { + cfg.Log.Level = "info" + } + recoLevel := reco.ParseLevel(cfg.Log.Level) + logger, err = reco.New(reco.Config{ + Level: recoLevel, + Mode: reco.ModeText, + FilePath: cfg.Log.LogFilePath, + MaxFileSizeMB: cfg.Log.MaxLogSize, + EnableRotation: true, + Async: true, + }) if err != nil { fmt.Printf("Failed to initialize logger: %v\n", err) + os.Exit(1) } - err = logger.SetLogLevel(cfg.Log.Level) - if err != nil { - fmt.Printf("Logger Level Error: %v\n", err) - } + logger.SetLevel(recoLevel) + fmt.Printf("Log Level: %s\n", cfg.Log.Level) - logDebug("Config File Path: ", cfgfile) - logDebug("Loaded config: %v\n", cfg) - logInfo("Init Completed") + logger.Debugf("Config File Path: %s", cfgfile) + logger.Debugf("Loaded config: %v", cfg) + logger.Infof("Logger Initialized Successfully") +} + +func setMemLimit(cfg *config.Config) { + if cfg.Server.MemLimit > 0 { + debug.SetMemoryLimit((cfg.Server.MemLimit) * 1024 * 1024) + logger.Infof("Set Memory Limit to %d MB", cfg.Server.MemLimit) + } } func loadlist(cfg *config.Config) { - auth.Init(cfg) -} - -func setupApi(cfg *config.Config, router *gin.Engine, version string) { - api.InitHandleRouter(cfg, router, version) -} - -func setupRateLimit(cfg *config.Config) { - if cfg.RateLimit.Enabled { - if cfg.RateLimit.RateMethod == "ip" { - iplimiter = rate.NewIPRateLimiter(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute) - } else if cfg.RateLimit.RateMethod == "total" { - limiter = rate.New(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute) - } else { - logError("Invalid RateLimit Method: %s", cfg.RateLimit.RateMethod) - } + err := auth.ListInit(cfg) + if err != nil { + logger.Errorf("Failed to initialize list: %v", err) } + +} + +func setupApi(cfg *config.Config, r *touka.Engine, version string) { + api.InitHandleRouter(cfg, r, version) } func InitReq(cfg *config.Config) { - proxy.InitReq(cfg) -} - -// loadEmbeddedPages 加载嵌入式页面资源 -func loadEmbeddedPages(cfg *config.Config) (fs.FS, error) { - var pages fs.FS var err error - - switch cfg.Pages.Theme { - case "bootstrap": - pages, err = fs.Sub(pagesFS, "pages/bootstrap") - case "nebula": - pages, err = fs.Sub(NebulaPagesFS, "pages/nebula") - default: - pages, err = fs.Sub(pagesFS, "pages/bootstrap") // 默认主题 - logWarning("Invalid Pages Theme: %s, using default theme 'bootstrap'", cfg.Pages.Theme) - } - + httpClient, err = proxy.InitReq(cfg) if err != nil { - return nil, fmt.Errorf("failed to load embedded pages: %w", err) + fmt.Printf("Failed to initialize request: %v\n", err) + os.Exit(1) } - return pages, nil } -// setupPages 设置页面路由 -func setupPages(cfg *config.Config, router *gin.Engine) { +// initializeErrorPages 初始化嵌入的错误页面资源 +// 无论页面模式(internal/external)如何, 都应执行此操作, 以确保统一的错误页面处理 +func initializeErrorPages() { + pageFS := modembed.NewModTimeFS(pagesFS, time.Now()) + if err := proxy.InitErrPagesFS(pageFS); err != nil { + // 这是一个警告而不是致命错误, 因为即使没有自定义错误页面, 服务器也能运行 + logger.Warnf("failed to initialize embedded error pages: %v", err) + } +} + +// loadEmbeddedPages 使用 map 替代 switch, 动态加载嵌入式页面和资源文件系统 +func loadEmbeddedPages(cfg *config.Config) (fs.FS, fs.FS, error) { + pageFS := modembed.NewModTimeFS(pagesFS, time.Now()) + theme := cfg.Pages.Theme + + // 检查主题是否受支持, 如果不支持则使用默认主题 + if _, ok := supportedThemes[theme]; !ok { + logger.Warnf("Invalid Pages Theme: %s, using default theme 'design'", theme) + theme = "design" // 默认主题 + } + + // 从嵌入式文件系统中获取主题子目录 + themePath := fmt.Sprintf("pages/%s", theme) + pages, err := fs.Sub(pageFS, themePath) + if err != nil { + return nil, nil, fmt.Errorf("failed to load embedded theme '%s': %w", theme, err) + } + + // 加载共享资源文件 + assets, err := fs.Sub(pageFS, "pages/assets") + if err != nil { + return nil, nil, fmt.Errorf("failed to load embedded assets: %w", err) + } + + return pages, assets, nil +} + +// setupPages 设置页面路由, 增强了错误处理 +func setupPages(cfg *config.Config, r *touka.Engine) { switch cfg.Pages.Mode { case "internal": - // 加载嵌入式资源 - pages, err := loadEmbeddedPages(cfg) + err := setInternalRoute(cfg, r) if err != nil { - logError("Failed when processing internal pages: %s", err) - return + logger.Errorf("Failed to set up internal pages, server cannot start: %s", err) + fmt.Printf("Failed to set up internal pages, server cannot start: %s", err) + os.Exit(1) } - // 设置嵌入式资源路由 - router.GET("/", gin.WrapH(http.FileServer(http.FS(pages)))) - router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages)))) - router.GET("/script.js", gin.WrapH(http.FileServer(http.FS(pages)))) - router.GET("/style.css", gin.WrapH(http.FileServer(http.FS(pages)))) - //router.GET("/bootstrap.min.css", gin.WrapH(http.FileServer(http.FS(pages)))) - case "external": - // 设置外部资源路径 - indexPagePath := fmt.Sprintf("%s/index.html", cfg.Pages.StaticDir) - faviconPath := fmt.Sprintf("%s/favicon.ico", cfg.Pages.StaticDir) - javascriptsPath := fmt.Sprintf("%s/script.js", cfg.Pages.StaticDir) - stylesheetsPath := fmt.Sprintf("%s/style.css", cfg.Pages.StaticDir) - //bootstrapPath := fmt.Sprintf("%s/bootstrap.min.css", cfg.Pages.StaticDir) - - // 设置外部资源路由 - router.GET("/", func(c *gin.Context) { - c.File(indexPagePath) - logInfo("IP:%s UA:%s METHOD:%s HTTPv:%s", c.ClientIP(), c.Request.UserAgent(), c.Request.Method, c.Request.Proto) - }) - router.StaticFile("/favicon.ico", faviconPath) - router.StaticFile("/script.js", javascriptsPath) - router.StaticFile("/style.css", stylesheetsPath) - //router.StaticFile("/bootstrap.min.css", bootstrapPath) - + if cfg.Pages.StaticDir == "" { + logger.Errorf("Pages Mode is 'external' but StaticDir is empty. Using embedded pages instead.") + err := setInternalRoute(cfg, r) + if err != nil { + logger.Errorf("Failed to load embedded pages: %s", err) + fmt.Printf("Failed to load embedded pages: %s", err) + os.Exit(1) + } + } else { + extPageFS := os.DirFS(cfg.Pages.StaticDir) + r.SetUnMatchFS(http.FS(extPageFS)) + } default: // 处理无效的Pages Mode - logWarning("Invalid Pages Mode: %s, using default embedded theme", cfg.Pages.Mode) - - // 加载嵌入式资源 - pages, err := loadEmbeddedPages(cfg) + logger.Warnf("Invalid Pages Mode: %s, using default embedded theme", cfg.Pages.Mode) + err := setInternalRoute(cfg, r) if err != nil { - logError("Failed when processing pages: %s", err) - return + logger.Errorf("Failed to set up internal pages, server cannot start: %s", err) + fmt.Printf("Failed to set up internal pages, server cannot start: %s", err) + os.Exit(1) } - // 设置嵌入式资源路由 - router.GET("/", gin.WrapH(http.FileServer(http.FS(pages)))) - router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages)))) - router.GET("/script.js", gin.WrapH(http.FileServer(http.FS(pages)))) - router.GET("/style.css", gin.WrapH(http.FileServer(http.FS(pages)))) } } +var viaString string = "WJQSERVER-STUDIO/GHProxy" + +func pageCacheHeader() func(c *touka.Context) { + return func(c *touka.Context) { + c.AddHeader("Cache-Control", "public, max-age=3600, must-revalidate") + c.Next() + } +} + +func viaHeader() func(c *touka.Context) { + return func(c *touka.Context) { + protoVersion := fmt.Sprintf("%d.%d", c.Request.ProtoMajor, c.Request.ProtoMinor) + c.AddHeader("Via", protoVersion+" "+viaString) + c.Next() + } +} + +func setInternalRoute(cfg *config.Config, r *touka.Engine) error { + // 加载嵌入式资源 + pages, assets, err := loadEmbeddedPages(cfg) + if err != nil { + return err + } + + r.HandleFunc([]string{"GET"}, "/favicon.ico", pageCacheHeader(), touka.FileServer(http.FS(assets))) + r.HandleFunc([]string{"GET"}, "/", pageCacheHeader(), touka.FileServer(http.FS(pages))) + r.HandleFunc([]string{"GET"}, "/script.js", pageCacheHeader(), touka.FileServer(http.FS(pages))) + r.HandleFunc([]string{"GET"}, "/style.css", pageCacheHeader(), touka.FileServer(http.FS(pages))) + r.HandleFunc([]string{"GET"}, "/bootstrap.min.css", pageCacheHeader(), touka.FileServer(http.FS(assets))) + r.HandleFunc([]string{"GET"}, "/bootstrap.bundle.min.js", pageCacheHeader(), touka.FileServer(http.FS(assets))) + + return nil +} + func init() { readFlag() flag.Parse() + + // 如果设置了 -h, 则显示帮助信息并退出 + if showHelp { + flag.Usage() + os.Exit(0) + } + + // 如果设置了 -v, 则显示版本号并退出 + if showVersion { + fmt.Printf("GHProxy Version: %s \n", version) + os.Exit(0) + } + loadConfig() - setupLogger(cfg) - InitReq(cfg) - loadlist(cfg) - setupRateLimit(cfg) + if cfg != nil { // 在setupLogger前添加空值检查 + setupLogger(cfg) + initializeErrorPages() + InitReq(cfg) + setMemLimit(cfg) + loadlist(cfg) + if cfg.Docker.Enabled { + wcache = proxy.InitWeakCache() + } - if cfg.Server.Debug { - dev = "true" - version = "dev" + if cfg.Server.Debug { + runMode = "dev" + } else { + runMode = "release" + } + + if cfg.Server.Debug { + version = "Dev" // 如果是Debug模式, 版本设置为"Dev" + } } - if dev == "true" { - gin.SetMode(gin.DebugMode) - runMode = "dev" - } else { - gin.SetMode(gin.ReleaseMode) - runMode = "release" +} + +func main() { + if showVersion || showHelp { + return } - logDebug("Run Mode: %s", runMode) - - gin.LoggerWithWriter(io.Discard) - router = gin.New() - - // 添加recovery中间件 - router.Use(gin.Recovery()) - - // 添加log中间件 - router.Use(loggin.Middleware()) - - // 添加计时中间件 - router.Use(timing.Middleware()) - - if cfg.Server.H2C { - router.UseH2C = true + if cfg == nil { + fmt.Println("Config not loaded, exiting.") + return } - setupApi(cfg, router, version) - - setupPages(cfg, router) - - // 1. GitHub Releases/Archive - Use distinct path segments for type - router.GET("/github.com/:username/:repo/releases/*filepath", func(c *gin.Context) { // Distinct path for releases - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r := touka.Default() + r.SetProtocols(&touka.ProtocolsConfig{ + Http1: true, + Http2_Cleartext: true, }) - router.GET("/github.com/:username/:repo/archive/*filepath", func(c *gin.Context) { // Distinct path for archive - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.Use(touka.Recovery()) // Recovery中间件 + r.SetLogger(logger) + r.SetErrorHandler(proxy.UnifiedToukaErrorHandler) + r.SetHTTPClient(httpClient) + r.Use(record.Middleware()) // log中间件 + r.Use(viaHeader()) + /* + r.Use(compress.Compression(compress.CompressOptions{ + Algorithms: map[string]compress.AlgorithmConfig{ + compress.EncodingGzip: { + Level: gzip.BestCompression, // Gzip最高压缩比 + PoolEnabled: true, // 启用Gzip压缩器的对象池 + }, + compress.EncodingDeflate: { + Level: flate.DefaultCompression, // Deflate默认压缩比 + PoolEnabled: false, // Deflate不启用对象池 + }, + compress.EncodingZstd: { + Level: int(zstd.SpeedBestCompression), // Zstandard最佳压缩比 + PoolEnabled: true, // 启用Zstandard压缩器的对象池 + }, + }, + })) + */ + + if cfg.RateLimit.Enabled { + r.Use(ikumi.TokenRateLimit(ikumi.TokenRateLimiterOptions{ + Limit: rate.Limit(cfg.RateLimit.RatePerMinute), + Burst: cfg.RateLimit.Burst, + })) + } + + if cfg.IPFilter.Enabled { + var err error + ipAllowList, ipBlockList, err := auth.ReadIPFilterList(cfg) + if err != nil { + fmt.Printf("Failed to read IP filter list: %v\n", err) + logger.Errorf("Failed to read IP filter list: %v", err) + os.Exit(1) + } + ipBlockFilter, err := ipfilter.NewIPFilter(ipfilter.IPFilterConfig{ + EnableAllowList: cfg.IPFilter.EnableAllowList, + EnableBlockList: cfg.IPFilter.EnableBlockList, + AllowList: ipAllowList, + BlockList: ipBlockList, + }) + if err != nil { + fmt.Printf("Failed to initialize IP filter: %v\n", err) + logger.Errorf("Failed to initialize IP filter: %v", err) + os.Exit(1) + } else { + r.Use(ipBlockFilter) + } + } + setupApi(cfg, r, version) + setupPages(cfg, r) + r.SetRedirectTrailingSlash(false) + + r.GET("/github.com/:user/:repo/releases/*filepath", func(c *touka.Context) { + // 规范化路径: 移除前导斜杠, 简化后续处理 + filepath := c.Param("filepath") + if len(filepath) > 0 && filepath[0] == '/' { + filepath = filepath[1:] + } + + isValidDownload := false + + // 检查两种合法的下载链接格式 + // 情况 A: "download/..." + if strings.HasPrefix(filepath, "download/") { + isValidDownload = true + } else { + // 情况 B: ":tag/download/..." + slashIndex := strings.IndexByte(filepath, '/') + // 确保 tag 部分存在 (slashIndex > 0) + if slashIndex > 0 { + pathAfterTag := filepath[slashIndex+1:] + if strings.HasPrefix(pathAfterTag, "download/") { + isValidDownload = true + } + } + } + + // 根据匹配结果执行最终操作 + if isValidDownload { + c.Set("matcher", "releases") + proxy.RoutingHandler(cfg)(c) + } else { + // 任何不符合下载链接格式的 'releases' 路径都被视为浏览页面并拒绝 + proxy.ErrorPage(c, proxy.NewErrorWithStatusLookup(400, "unsupported releases page, only download links are allowed")) + return + } }) - // 2. GitHub Blob/Raw - Use distinct path segments for type - router.GET("/github.com/:username/:repo/blob/*filepath", func(c *gin.Context) { // Distinct path for blob - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.GET("/github.com/:user/:repo/archive/*filepath", func(c *touka.Context) { + c.Set("matcher", "releases") + proxy.RoutingHandler(cfg)(c) }) - router.GET("/github.com/:username/:repo/raw/*filepath", func(c *gin.Context) { // Distinct path for raw - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.GET("/github.com/:user/:repo/blob/*filepath", func(c *touka.Context) { + c.Set("matcher", "blob") + proxy.RoutingHandler(cfg)(c) }) - router.GET("/github.com/:username/:repo/info/*filepath", func(c *gin.Context) { // Distinct path for info - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) - }) - router.GET("/github.com/:username/:repo/git-upload-pack", func(c *gin.Context) { - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.GET("/github.com/:user/:repo/raw/*filepath", func(c *touka.Context) { + c.Set("matcher", "raw") + proxy.RoutingHandler(cfg)(c) }) - // 4. Raw GitHubusercontent - Keep as is (assuming it's distinct enough) - router.GET("/raw.githubusercontent.com/:username/:repo/*filepath", func(c *gin.Context) { - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.GET("/github.com/:user/:repo/info/*filepath", func(c *touka.Context) { + c.Set("matcher", "clone") + proxy.RoutingHandler(cfg)(c) + }) + r.GET("/github.com/:user/:repo/git-upload-pack", func(c *touka.Context) { + c.Set("matcher", "clone") + proxy.RoutingHandler(cfg)(c) + }) + r.POST("/github.com/:user/:repo/git-upload-pack", func(c *touka.Context) { + c.Set("matcher", "clone") + proxy.RoutingHandler(cfg)(c) }) - // 5. Gist GitHubusercontent - Keep as is (assuming it's distinct enough) - router.GET("/gist.githubusercontent.com/:username/*filepath", func(c *gin.Context) { - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.GET("/raw.githubusercontent.com/:user/:repo/*filepath", func(c *touka.Context) { + c.Set("matcher", "raw") + proxy.RoutingHandler(cfg)(c) }) - // 6. GitHub API Repos - Keep as is (assuming it's distinct enough) - router.GET("/api.github.com/repos/:username/:repo/*filepath", func(c *gin.Context) { - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.GET("/gist.githubusercontent.com/:user/*filepath", func(c *touka.Context) { + c.Set("matcher", "gist") + proxy.NoRouteHandler(cfg)(c) }) - router.NoRoute(func(c *gin.Context) { - logInfo(c.Request.URL.Path) - proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) + r.ANY("/api.github.com/repos/:user/:repo/*filepath", func(c *touka.Context) { + c.Set("matcher", "api") + proxy.RoutingHandler(cfg)(c) + }) + + r.ANY("/v2/*path", + r.UseIf(cfg.Docker.Auth, func() touka.HandlerFunc { + return bauth.BasicAuthForStatic(cfg.Docker.Credentials, "GHProxy Docker Proxy") + }), + proxy.OciWithImageRouting(cfg), + ) + + r.GET("/v2", func(c *touka.Context) { + // 重定向到 /v2/ + c.Redirect(http.StatusMovedPermanently, "/v2/") + }) + + r.NoRoute(func(c *touka.Context) { + proxy.NoRouteHandler(cfg)(c) }) fmt.Printf("GHProxy Version: %s\n", version) fmt.Printf("A Go Based High-Performance Github Proxy \n") fmt.Printf("Made by WJQSERVER-STUDIO\n") -} + fmt.Printf("Power by Touka\n") -func main() { - err := router.Run(fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)) - if err != nil { - logError("Failed to start server: %v\n", err) + if cfg.Server.Debug { + go func() { + http.ListenAndServe("localhost:6060", nil) + }() } + if wcache != nil { + defer wcache.StopCleanup() + } + defer logger.Close() + + addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port) + err := r.RunShutdown(addr) + if err != nil { + logger.Errorf("Server Run Error: %v", err) + fmt.Printf("Server Run Error: %v\n", err) + } + fmt.Println("Program Exit") } diff --git a/middleware/loggin/loggin.go b/middleware/loggin/loggin.go deleted file mode 100644 index ca3f681..0000000 --- a/middleware/loggin/loggin.go +++ /dev/null @@ -1,34 +0,0 @@ -package loggin - -import ( - "ghproxy/middleware/timing" - "time" - - "github.com/WJQSERVER-STUDIO/go-utils/logger" - "github.com/gin-gonic/gin" -) - -var ( - logw = logger.Logw - LogDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError -) - -// 日志中间件 -func Middleware() gin.HandlerFunc { - return func(c *gin.Context) { - // 处理请求 - c.Next() - - var timingResults time.Duration - - // 获取计时结果 - timingResults, _ = timing.Get(c) - - // 记录日志 IP METHOD URL USERAGENT PROTOCOL STATUS TIMING - logInfo("%s %s %s %s %d %s ", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Writer.Status(), timingResults) - } -} diff --git a/middleware/nocache/nocache.go b/middleware/nocache/nocache.go new file mode 100644 index 0000000..ba3b900 --- /dev/null +++ b/middleware/nocache/nocache.go @@ -0,0 +1,15 @@ +package nocache + +import ( + "github.com/infinite-iroha/touka" +) + +func NoCacheMiddleware() touka.HandlerFunc { + return func(c *touka.Context) { + // 设置禁止缓存的响应头 + c.SetHeader("Cache-Control", "no-store, no-cache, must-revalidate") + c.SetHeader("Pragma", "no-cache") + c.SetHeader("Expires", "0") + c.Next() // 继续处理请求 + } +} diff --git a/middleware/timing/timing.go b/middleware/timing/timing.go deleted file mode 100644 index 9c0ada8..0000000 --- a/middleware/timing/timing.go +++ /dev/null @@ -1,86 +0,0 @@ -package timing - -import ( - "sync" - "time" - - "github.com/gin-gonic/gin" -) - -// 阶段计时结构(固定数组优化) -type timingData struct { - phases [8]struct { // 预分配8个阶段存储 - name string - dur time.Duration - } - count int - start time.Time -} - -// 对象池(内存重用优化) -var pool = sync.Pool{ - New: func() interface{} { - return new(timingData) - }, -} - -// 中间件入口 -func Middleware() gin.HandlerFunc { - return func(c *gin.Context) { - // 从池中获取计时器 - td := pool.Get().(*timingData) - td.start = time.Now() - td.count = 0 - - // 存储到上下文 - c.Set("timing", td) - - // 请求完成后回收对象 - defer func() { - pool.Put(td) - }() - - c.Next() - } -} - -// 记录阶段耗时 -func Record(c *gin.Context, name string) { - if val, exists := c.Get("timing"); exists { - //td := val.(*timingData) - td, ok := val.(*timingData) - if !ok { - return - } - if td.count < len(td.phases) { - td.phases[td.count].name = name - td.phases[td.count].dur = time.Since(td.start) // 直接记录当前时间 - td.count++ - } - } -} - -// 获取计时结果(日志输出用) -func Get(c *gin.Context) (total time.Duration, phases []struct { - Name string - Dur time.Duration -}) { - if val, exists := c.Get("timing"); exists { - //td := val.(*timingData) - td, ok := val.(*timingData) - if !ok { - return - } - for i := 0; i < td.count; i++ { - phases = append(phases, struct { - Name string - Dur time.Duration - }{ - Name: td.phases[i].name, - Dur: td.phases[i].dur, - }) - } - total = time.Since(td.start) - } - return -} diff --git a/pages/bootstrap/favicon.ico b/pages/bootstrap/favicon.ico deleted file mode 100644 index 9c04d31..0000000 Binary files a/pages/bootstrap/favicon.ico and /dev/null differ diff --git a/pages/bootstrap/index.html b/pages/bootstrap/index.html deleted file mode 100644 index a2bc33e..0000000 --- a/pages/bootstrap/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - -
- - -为访问Github文件进行加速
- - -GitHub 链接带不带协议头均可,支持 release、archive 以及文件,转换后链接均可使用。
-...
-...
-...
-高速稳定的 GitHub 资源访问解决方案
-
- git clone https://example.com/https://github.com/user/project.git
- git clone https://user:your_token@example.com/https://github.com/user/project.git
- wget https://example.com/https://raw.githubusercontent.com/user/project/main/README.md
- curl -LO https://example.com/https://github.com/user/project/releases/download/v1.0.0/project_1.0.0.amd64.tar.gz
- https://raw.githubusercontent.com/user/repo/main/file.txt
- https://github.com/user/repo/archive/main.zip
- https://github.com/user/repo/releases/download/v1.0.0/app.zip
- https://gist.githubusercontent.com/user/gist_id/raw/file.txt
- git clone https://github.com/user/repo.git
-