diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml
index 40a60f6..73d9a4c 100644
--- a/.github/workflows/build-dev.yml
+++ b/.github/workflows/build-dev.yml
@@ -59,6 +59,13 @@ jobs:
else
echo "DEV-VERSION file not found!" && exit 1
fi
+
+ - name: 拉取前端
+ run: |
+ sudo git clone https://github.com/WJQSERVER-STUDIO/GHPrxoy-Frontend.git pages
+ sudo rm -rf pages/.git/
+
+
- name: 安装 Go
uses: actions/setup-go@v3
with:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 76306c5..37c99a6 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -56,6 +56,12 @@ jobs:
else
echo "VERSION file not found!" && exit 1
fi
+
+ - name: 拉取前端
+ run: |
+ sudo git clone https://github.com/WJQSERVER-STUDIO/GHPrxoy-Frontend.git pages
+ sudo rm -rf pages/.git/
+
- name: 安装 Go
uses: actions/setup-go@v3
with:
diff --git a/.gitignore b/.gitignore
index c16c6b1..02838fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,5 @@ demo.toml
*.log
*.bak
list.json
-repos
\ No newline at end of file
+repos
+pages
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93bc519..838f66e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,83 @@
# 更新日志
+25w23a - 2025-03-22
+---
+- PRE-RELEASE: 此版本是v2.6.0的预发布版本,请勿在生产环境中使用;
+- BACKPORT: 将v3的功能性改进反向移植
+
+e3.0.2 - 2025-03-21
+---
+- ATTENTION: 此版本是实验性的, 请确保了解这一点
+- RELEASE: 在此表达对各位的歉意, v3迁移到HertZ带来了许多问题; 此版本完善v3的同时, 修正已知问题; v3会与v2.4.0及以上版本保证兼容关系, 可平顺升级;
+- 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的同时, 修正已知问题; v3会与v2.4.0及以上版本保证兼容关系, 可平顺升级;
+- 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; 下一个起点; v3会与v2.4.0及以上版本保证兼容关系, 可平顺升级;
+- CHANGE: 使用HertZ框架重构, 提升性能
+- CHANGE: 前端在构建时加入, 新增`Design`,`Metro`,`Classic`主题
+- CHANGE: 加入`Mino`主题对接选项
+- FIX: 修正部分日志输出问题
+- CHANGE: 移除gin残留
+- CHANGE: 移除无用传入参数, 调整代码结构
+
+25w20b - 2025-03-19
+---
+- PRE-RELEASE: 此版本是v3.0.0的预发布版本,请勿在生产环境中使用; v3.0.0会与v2.4.0及以上保证兼容关系, 可平顺升级;
+- CHANGE: 加入`Mino`主题对接选项
+- FIX: 修正部分日志输出问题
+- CHANGE: 移除gin残留
+- CHANGE: 移除无用传入参数, 调整代码结构
+
+25w20a - 2025-03-18
+---
+- PRE-RELEASE: 此版本是v3.0.0的预发布版本,请勿在生产环境中使用; v3.0.0会与v2.4.0及以上保证兼容关系, 可平顺升级;
+- CHANGE: 使用HertZ重构
+- CHANGE: 前端在构建时加入, 新增`Design`,`Metro`,`Classic`主题
+
2.5.0 - 2025-03-17
---
- ADD: 加入脚本嵌套加速功能
@@ -1091,4 +1169,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 e33ec6f..b9d5430 100644
--- a/DEV-VERSION
+++ b/DEV-VERSION
@@ -1 +1 @@
-25w19a
\ No newline at end of file
+25w23a
\ No newline at end of file
diff --git a/README.md b/README.md
index 7aeec5f..6fa4002 100644
--- a/README.md
+++ b/README.md
@@ -20,11 +20,13 @@
- 使用[Gin](https://github.com/gin-gonic/gin)作为Web框架
- 使用[Touka-HTTPC](https://github.com/satomitouka/touka-httpc)作为HTTP客户端
- 支持Git clone,raw,realeases等文件拉取
+- 支持多个前端主题
+- 支持自定义黑名单/白名单
- 支持Git Clone缓存(配合组件)
- 支持Docker部署
- 支持速率限制
- 支持用户鉴权
-- 支持自定义黑名单/白名单
+- 支持shell脚本嵌套加速
- 基于[WJQSERVER-STUDIO/golang-temp](https://github.com/WJQSERVER-STUDIO/golang-temp)模板构建,具有标准化的日志记录与构建流程
### 项目开发过程
@@ -201,4 +203,6 @@ USDT(TRC20): `TNfSYG6F2vkiibd6J6mhhHNWDgWgNdF5hN`
### 捐赠列表
-虚位以待...
+| 赞助人 |金额|
+|--------|------|
+| starry | 8 USDT (TRC20) |
diff --git a/SECURITY.MD b/SECURITY.MD
index 5ac050f..09acd9e 100644
--- a/SECURITY.MD
+++ b/SECURITY.MD
@@ -6,7 +6,8 @@
| 版本 | 是否支持 |
| --- | --- |
-| v2.x.x | :white_check_mark: 当前最新版本序列, 受支持 |
+| v3.x.x | :white_check_mark: 接受issue, 实验性 |
+| v2.x.x | :white_check_mark: 受支持, 正在维护 |
| v1.x.x | :x: 这些版本已结束生命周期,不再受支持 |
| 25w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 |
| 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 生命周期已完全结束 |
diff --git a/VERSION b/VERSION
index fad066f..914ec96 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.5.0
\ No newline at end of file
+2.6.0
\ No newline at end of file
diff --git a/api/api.go b/api/api.go
index 5a53c63..1b28789 100644
--- a/api/api.go
+++ b/api/api.go
@@ -15,7 +15,7 @@ var (
var (
logw = logger.Logw
- LogDump = logger.LogDump
+ logDump = logger.LogDump
logDebug = logger.LogDebug
logInfo = logger.LogInfo
logWarning = logger.LogWarning
@@ -59,6 +59,9 @@ func InitHandleRouter(cfg *config.Config, router *gin.Engine, version string) {
apiRouter.GET("/rate_limit/limit", func(c *gin.Context) {
RateLimitLimitHandler(c, cfg)
})
+ apiRouter.GET("/smartgit/status", func(c *gin.Context) {
+ SmartGitStatusHandler(c, cfg)
+ })
}
logInfo("API router Init success")
}
@@ -127,3 +130,11 @@ func RateLimitLimitHandler(c *gin.Context, cfg *config.Config) {
"RatePerMinute": cfg.RateLimit.RatePerMinute,
})
}
+
+func SmartGitStatusHandler(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{}{
+ "enabled": cfg.GitClone.Mode == "cache",
+ })
+}
diff --git a/auth/auth.go b/auth/auth.go
index d7cad1a..3fbe1a3 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -10,7 +10,7 @@ import (
var (
logw = logger.Logw
- LogDump = logger.LogDump
+ logDump = logger.LogDump
logDebug = logger.LogDebug
logInfo = logger.LogInfo
logWarning = logger.LogWarning
diff --git a/config/config.go b/config/config.go
index ee2ab4a..a338ec8 100644
--- a/config/config.go
+++ b/config/config.go
@@ -24,7 +24,6 @@ host = "0.0.0.0" # 监听地址
port = 8080 # 监听端口
sizeLimit = 125 # 125MB
H2C = true # 是否开启H2C传输
-enableH2C = "on" # 是否开启H2C传输(latest和dev版本请开启) on/off (2.4.0弃用)
*/
type ServerConfig struct {
@@ -33,7 +32,6 @@ type ServerConfig struct {
SizeLimit int `toml:"sizeLimit"`
H2C bool `toml:"H2C"`
Cors string `toml:"cors"`
- EnableH2C string `toml:"enableH2C"`
Debug bool `toml:"debug"`
}
@@ -54,7 +52,7 @@ type HttpcConfig struct {
/*
[gitclone]
mode = "bypass" # bypass / cache
-smartGitAddr = ":8080"
+smartGitAddr = "http://127.0.0.1:8080"
ForceH2C = true
*/
type GitCloneConfig struct {
@@ -74,13 +72,11 @@ type ShellConfig struct {
/*
[pages]
mode = "internal" # "internal" or "external"
-enabled = false
theme = "bootstrap" # "bootstrap" or "nebula"
staticDir = "/data/www"
*/
type PagesConfig struct {
Mode string `toml:"mode"`
- Enabled bool `toml:"enabled"`
Theme string `toml:"theme"`
StaticDir string `toml:"staticDir"`
}
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/main.go b/main.go
index f8adcc2..1c03372 100644
--- a/main.go
+++ b/main.go
@@ -7,6 +7,7 @@ import (
"io"
"io/fs"
"net/http"
+ "os"
"time"
"ghproxy/api"
@@ -23,22 +24,22 @@ import (
)
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
+ router *gin.Engine
+ configfile = "/data/ghproxy/config/config.toml"
+ cfgfile string
+ version string
+ dev string
+ runMode string
+ limiter *rate.RateLimiter
+ iplimiter *rate.IPRateLimiter
+ showVersion bool
+ showHelp bool
)
var (
- //go:embed pages/bootstrap/*
+ //go:embed pages/*
pagesFS embed.FS
- //go:embed pages/nebula/*
- NebulaPagesFS embed.FS
)
var (
@@ -52,6 +53,38 @@ var (
func readFlag() {
flag.StringVar(&cfgfile, "cfg", configfile, "config file path")
+ 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 +92,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)
}
@@ -110,12 +146,19 @@ func InitReq(cfg *config.Config) {
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")
+ pages, err = fs.Sub(pagesFS, "pages/nebula")
+ case "design":
+ pages, err = fs.Sub(pagesFS, "pages/design")
+ case "metro":
+ pages, err = fs.Sub(pagesFS, "pages/metro")
+ case "classic":
+ pages, err = fs.Sub(pagesFS, "pages/classic")
+ case "mino":
+ pages, err = fs.Sub(pagesFS, "pages/mino")
default:
pages, err = fs.Sub(pagesFS, "pages/bootstrap") // 默认主题
logWarning("Invalid Pages Theme: %s, using default theme 'bootstrap'", cfg.Pages.Theme)
@@ -184,6 +227,19 @@ func setupPages(cfg *config.Config, router *gin.Engine) {
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)
@@ -275,6 +331,9 @@ func init() {
}
func main() {
+ if showVersion || showHelp {
+ return
+ }
err := router.Run(fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port))
if err != nil {
logError("Failed to start server: %v\n", err)
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文件加速
-
为访问Github文件进行加速
-
-
-
-
-
-
-
GitHub 链接带不带协议头均可,支持 release、archive 以及文件,转换后链接均可使用。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pages/bootstrap/script.js b/pages/bootstrap/script.js
deleted file mode 100644
index a1c333e..0000000
--- a/pages/bootstrap/script.js
+++ /dev/null
@@ -1,84 +0,0 @@
-const githubForm = document.getElementById('github-form');
-const githubLinkInput = document.getElementById('githubLinkInput');
-const formattedLinkOutput = document.getElementById('formattedLinkOutput');
-const output = document.getElementById('output');
-const copyButton = document.getElementById('copyButton');
-const openButton = document.getElementById('openButton');
-const toast = new bootstrap.Toast(document.getElementById('toast'));
-
-function showToast(message) {
- const toastBody = document.querySelector('.toast-body');
- toastBody.textContent = message;
- toast.show();
-}
-
-function formatGithubLink(githubLink) {
- const currentHost = window.location.host;
- let formattedLink = "";
-
- if (githubLink.startsWith("https://github.com/") || githubLink.startsWith("http://github.com/")) {
- formattedLink = window.location.protocol + "//" + currentHost + "/github.com" + githubLink.substring(githubLink.indexOf("/", 8));
- } else if (githubLink.startsWith("github.com/")) {
- formattedLink = window.location.protocol + "//" + currentHost + "/" + githubLink;
- } else if (githubLink.startsWith("https://raw.githubusercontent.com/") || githubLink.startsWith("http://raw.githubusercontent.com/")) {
- formattedLink = window.location.protocol + "//" + currentHost + githubLink.substring(githubLink.indexOf("/", 7));
- } else if (githubLink.startsWith("raw.githubusercontent.com/")) {
- formattedLink = window.location.protocol + "//" + currentHost + "/" + githubLink;
- } else if (githubLink.startsWith("https://gist.githubusercontent.com/") || githubLink.startsWith("http://gist.githubusercontent.com/")) {
- formattedLink = window.location.protocol + "//" + currentHost + "/gist.github.com" + githubLink.substring(githubLink.indexOf("/", 18));
- } else if (githubLink.startsWith("gist.githubusercontent.com/")) {
- formattedLink = window.location.protocol + "//" + currentHost + "/" + githubLink;
- } else {
- showToast('请输入有效的GitHub链接');
- return null;
- }
-
- return formattedLink;
-}
-
-githubForm.addEventListener('submit', function (e) {
- e.preventDefault();
- const formattedLink = formatGithubLink(githubLinkInput.value);
- if (formattedLink) {
- formattedLinkOutput.textContent = formattedLink;
- output.style.display = 'block';
- }
-});
-
-copyButton.addEventListener('click', function () {
- navigator.clipboard.writeText(formattedLinkOutput.textContent).then(() => {
- showToast('链接已复制到剪贴板');
- });
-});
-
-openButton.addEventListener('click', function () {
- window.open(formattedLinkOutput.textContent, '_blank');
-});
-
-function fetchAPI() {
- fetch('/api/size_limit')
- .then(response => response.json())
- .then(data => {
- document.getElementById('sizeLimitDisplay').textContent = `${data.MaxResponseBodySize} MB`;
- });
-
- fetch('/api/whitelist/status')
- .then(response => response.json())
- .then(data => {
- document.getElementById('whiteListStatus').textContent = data.Whitelist ? '已开启' : '已关闭';
- });
-
- fetch('/api/blacklist/status')
- .then(response => response.json())
- .then(data => {
- document.getElementById('blackListStatus').textContent = data.Blacklist ? '已开启' : '已关闭';
- });
-
- fetch('/api/version')
- .then(response => response.json())
- .then(data => {
- document.getElementById('versionBadge').textContent = data.Version;
- });
-}
-
-document.addEventListener('DOMContentLoaded', fetchAPI);
\ No newline at end of file
diff --git a/pages/bootstrap/style.css b/pages/bootstrap/style.css
deleted file mode 100644
index c6e7041..0000000
--- a/pages/bootstrap/style.css
+++ /dev/null
@@ -1,259 +0,0 @@
- /* 通用样式 */
- :root {
- --primary-color: #007aff;
- /* 主要按钮颜色 */
- --secondary-color: #34c759;
- /* 次要按钮颜色 */
- --background-color: #f9f9f9;
- /* 亮色模式背景 */
- --card-background: #ffffff;
- /* 卡片背景 */
- --text-color: #333333;
- /* 亮色模式文本颜色 */
- --border-color: #e0e0e0;
- /* 边框颜色 */
- --input-background: #ffffff;
- /* 输入框背景 */
- --input-border: #d1d1d6;
- /* 输入框边框 */
- --pre-background: #f1f3f4;
- /* 代码块背景 */
- --pre-text-color: #333333;
- /* 代码块文本颜色 */
- --version-badge-hover: #39c5bb;
- /* 说明文字颜色 */
- --muted-text-color: #6c757d;
- }
-
- body {
- background-color: var(--background-color);
- color: var(--text-color);
- font-family: sans-serif;
- line-height: 1.8;
- font-size: 15px;
- margin: 0;
- padding: 0;
- }
-
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- color: var(--text-color);
- font-weight: 800;
- letter-spacing: 0.5px;
- margin: 1rem 0;
- }
-
- p,
- span,
- a,
- li {
- color: var(--text-color);
- margin-bottom: 1rem;
- }
-
- a {
- text-decoration: none;
- color: var(--primary-color);
- }
-
- a:hover {
- color: #0056b3;
- }
-
- .card {
- background-color: var(--card-background);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- padding: 16px;
- margin: 16px 0;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- transition: transform 0.2s, box-shadow 0.2s;
- }
-
- .card:hover {
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
- transform: translateY(-4px);
- }
-
- .btn-outline-secondary {
- border-radius: 50%;
- padding: 6px;
- transition: #e9e9e9 0.3s ease-in-out, color 0.3s ease-in-out;
- }
-
- .btn-outline-secondary:hover {
- background-color: var(--primary-color);
- color: white;
- }
-
- .form-control {
- background-color: var(--input-background);
- border: 1px solid var(--input-border);
- color: var(--text-color);
- padding: 10px;
- border-radius: 4px;
- font-size: 14px;
- outline: none;
- transition: border-color 0.2s, box-shadow 0.2s;
- }
-
- .form-control:focus {
- border-color: var(--primary-color);
- box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3);
- }
-
- .text-muted {
- color: var(--muted-text-color) !important;
- }
-
- .bg-light {
- background-color: var(--card-background) !important;
- }
-
- pre {
- background-color: var(--pre-background);
- color: var(--pre-text-color);
- padding: 16px;
- border-radius: 8px;
- overflow-x: auto;
- font-size: 14px;
- line-height: 1.6;
- }
-
- .version-badge {
- position: fixed;
- bottom: 20px;
- right: 20px;
- background-color: var(--secondary-color);
- color: white;
- padding: 6px 12px;
- border-radius: 20px;
- font-size: 0.8rem;
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
- transition: background-color 0.3s ease-in-out;
- }
-
- .version-badge:hover {
- background-color: var(--version-badge-hover);
- }
-
- footer {
- padding: 16px;
- text-align: center;
- color: var(--text-color);
- font-size: 0.9rem;
- background-color: var(--card-background);
- }
-
- footer a {
- color: var(--primary-color);
- }
-
- footer a:hover {
- color: #0056b3;
- }
-
- /* 暗色模式 */
- @media (prefers-color-scheme: dark) {
- :root {
- --background-color: #121212;
- /* 深灰色背景 */
- --card-background: #1e1e1e;
- /* 卡片背景稍浅 */
- --text-color: #ffffff;
- /* 纯白文本 */
- --primary-color: #0a84ff;
- /* 按钮蓝色 */
- --secondary-color: #30d158;
- /* 次要按钮绿色 */
- --border-color: #3a3a3a;
- /* 边框颜色 */
- --input-background: #2c2c2c;
- /* 输入框背景 */
- --input-border: #4a4a4a;
- /* 输入框边框 */
- --pre-background: #3b3636;
- /* 代码块背景 */
- --pre-text-color: #ffffff;
- /* 代码块文本颜色 */
- --version-badge-hover: #39c5bc9a;
- /* 说明文字颜色 */
- --muted-text-color: #a0a0a0;
- }
-
- body {
- background-color: var(--background-color);
- color: var(--text-color);
- }
-
- h1,
- h2,
- h3,
- h4,
- h5,
- h6,
- p,
- span,
- a,
- li {
- color: var(--text-color);
- }
-
- .card {
- background-color: var(--card-background);
- color: var(--text-color);
- border: 1px solid var(--border-color);
- }
-
- .btn-outline-secondary {
- border-radius: 50%;
- padding: 6px;
- transition: background-color 0.3s ease-in-out, color 0.3s ease-in-out;
- }
-
- .btn-outline-secondary:hover {
- background-color: var(--primary-color);
- color: white;
- }
-
- .toast {
- background-color: var(--card-background);
- color: var(--text-color);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
- }
-
- .toast-body {
- padding: 12px;
- }
-
-
- .form-control {
- background-color: var(--input-background);
- border: 1px solid var(--input-border);
- color: var(--text-color);
- }
-
- .bg-light {
- background-color: var(--card-background) !important;
- }
-
- pre {
- background-color: var(--pre-background);
- color: var(--pre-text-color);
- }
-
- footer {
- background-color: var(--card-background);
- color: var(--text-color);
- }
-
- footer a {
- color: var(--primary-color);
- }
- }
\ No newline at end of file
diff --git a/pages/nebula/favicon.ico b/pages/nebula/favicon.ico
deleted file mode 100644
index 9c04d31..0000000
Binary files a/pages/nebula/favicon.ico and /dev/null differ
diff --git a/pages/nebula/index.html b/pages/nebula/index.html
deleted file mode 100644
index 0d62b86..0000000
--- a/pages/nebula/index.html
+++ /dev/null
@@ -1,169 +0,0 @@
-
-
-
-
-
-
- GitHub加速服务
-
-
-
-
-
-
-
- GitHub加速服务
- 高速稳定的 GitHub 资源访问解决方案
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
文件大小限制
- 最大支持文件尺寸
-
-
...
-
-
-
-
-
-
-
-
白名单状态
- 访问控制列表
-
-
...
-
-
-
-
-
-
-
-
黑名单状态
- 屏蔽列表状态
-
-
...
-
-
-
-
-
-
📚 详细使用指南
-
-
-
支持的工具
-
- - ✅ 支持域名:github.com
- - ✅ 支持域名:raw.githubusercontent.com
- - ✅ 支持域名:gist.githubusercontent.com
- - ✅ 支持HTTPS Git Clone
- - ❌ 不支持 SSH Git Clone
-
-
-
-
基础用法示例
-
-
Git 克隆
- 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
-
-
-
-
-
Gist 文件
- https://gist.githubusercontent.com/user/gist_id/raw/file.txt
-
-
-
-
-
HTTPS Git Clone
- git clone https://github.com/user/repo.git
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pages/nebula/script.js b/pages/nebula/script.js
deleted file mode 100644
index 4516f9c..0000000
--- a/pages/nebula/script.js
+++ /dev/null
@@ -1,131 +0,0 @@
-(() => {
- 'use strict';
-
- // 初始化基础配置
- const currentYear = new Date().getFullYear();
- document.getElementById('currentYear').textContent = currentYear;
- const toast = new bootstrap.Toast('#liveToast');
-
- // DOM 元素
- const form = document.getElementById('mainForm');
- const input = document.getElementById('inputUrl');
- const output = document.getElementById('output');
- const outputLink = document.getElementById('outputLink');
-
- // 获取当前域名
- const CURRENT_PROTOCOL = window.location.protocol.replace(':', '');
- const CURRENT_HOST = window.location.host;
- // 替换协议部分
- document.querySelectorAll('code .protocol').forEach(span => {
- span.textContent = CURRENT_PROTOCOL;
- });
- // 替换域名部分
- document.querySelectorAll('code .host').forEach(span => {
- span.textContent = CURRENT_HOST;
- });
-
- // URL 转换规则
- const URL_RULES = [
- {
- regex: /^(?:https?:\/\/)?(?:www\.)?(github\.com\/.*)/i,
- build: path => `${location.protocol}//${location.host}/${path}`
- },
- {
- regex: /^(?:https?:\/\/)?(raw\.githubusercontent\.com\/.*)/i,
- build: path => `${location.protocol}//${location.host}/${path}`
- },
- {
- regex: /^(?:https?:\/\/)?(gist\.(?:githubusercontent|github)\.com\/.*)/i,
- build: path => `${location.protocol}//${location.host}/${path}`
- }
- ];
-
- // 核心功能:链接转换
- function transformGitHubURL(url) {
- const cleanURL = url.trim().replace(/^https?:\/\//i, '');
- for (const rule of URL_RULES) {
- const match = cleanURL.match(rule.regex);
- if (match) return rule.build(match[1]);
- }
- return null;
- }
-
- // 事件处理
- form.addEventListener('submit', e => {
- e.preventDefault();
-
- if (!input.checkValidity()) {
- input.classList.add('is-invalid');
- showToast('⚠️ 请输入有效的 GitHub 链接');
- return;
- }
-
- const result = transformGitHubURL(input.value);
- if (!result) {
- showToast('❌ 不支持的链接格式');
- return;
- }
-
- outputLink.textContent = result;
- output.hidden = false;
- window.scrollTo({ top: output.offsetTop - 100, behavior: 'smooth' });
- });
-
- document.getElementById('copyBtn').addEventListener('click', async () => {
- try {
- await navigator.clipboard.writeText(outputLink.textContent);
- showToast('✅ 链接已复制');
- } catch {
- showToast('❌ 复制失败');
- }
- });
-
- document.getElementById('openBtn').addEventListener('click', () => {
- window.open(outputLink.textContent, '_blank', 'noopener,noreferrer');
- });
-
- // 服务状态监控
- async function loadServiceStatus() {
- try {
- const [size, whitelist, blacklist, version] = await Promise.all([
- fetchJSON('/api/size_limit'),
- fetchJSON('/api/whitelist/status'),
- fetchJSON('/api/blacklist/status'),
- fetchJSON('/api/version')
- ]);
-
- updateStatus('sizeLimit', `${size.MaxResponseBodySize}MB`);
- updateStatus('whitelistStatus', whitelist.Whitelist ? '已开启' : '已关闭');
- updateStatus('blacklistStatus', blacklist.Blacklist ? '已开启' : '已关闭');
- updateStatus('version', `Version ${version.Version}`);
- } catch {
- showToast('⚠️ 服务状态获取失败');
- }
- }
-
- async function fetchJSON(url) {
- const response = await fetch(url);
- if (!response.ok) throw new Error('API Error');
- return response.json();
- }
-
- function updateStatus(elementId, text) {
- const element = document.getElementById(elementId);
- if (element) element.textContent = text;
- }
-
- // 工具函数
- function showToast(message) {
- const toastBody = document.querySelector('.toast-body');
- toastBody.textContent = message;
- toast.show();
- }
-
- // 初始化
- input.addEventListener('input', () => {
- input.classList.remove('is-invalid');
- if (output.hidden === false) output.hidden = true;
- });
-
- loadServiceStatus();
-})();
\ No newline at end of file
diff --git a/pages/nebula/style.css b/pages/nebula/style.css
deleted file mode 100644
index 81f40a8..0000000
--- a/pages/nebula/style.css
+++ /dev/null
@@ -1,157 +0,0 @@
-:root {
- --primary: #007aff;
- --secondary: #007aff;
- --background: #f9f9f9;
- --card-bg: #ffffff;
- --text: #333333;
- --border: #d1d1d6;
- --muted: #64748b;
- --success: #00a83ed2;
- --danger: #ce0000dd;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #121212;
- --card-bg: #1e1e1e;
- --text: #ffffff;
- --border: #334155;
- --muted: #94a3b8;
- }
-
- .form-control::placeholder {
- color: var(--muted);
- }
-
- .text-muted {
- color: var(--muted) !important;
- }
-
- .btn-outline-secondary {
- color: var(--muted);
- border-color: var(--border);
- }
-
- .status-badge {
- color: var(--text) !important;
- }
-
- .code-block {
- background: rgba(255, 255, 255, 0.05);
- }
-
- .command-example {
- background: rgba(255, 255, 255, 0.03);
- }
-
- .btn-primary {
- --bs-btn-bg: var(--primary);
- --bs-btn-border-color: var(--primary);
- --bs-btn-hover-bg: var(--secondary);
- --bs-btn-hover-border-color: var(--secondary);
- }
-
- .form-control {
- background-color: rgba(255, 255, 255, 0.05);
- border-color: var(--border);
- color: var(--text);
- }
-
- a {
- color: var(--secondary);
- }
-}
-
-body {
- background: var(--background);
- color: var(--text);
- font-family: 'Inter', system-ui, sans-serif;
- line-height: 1.6;
-}
-
-.main-card {
- background: var(--card-bg);
- border: 1px solid var(--border);
- border-radius: 12px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
- transition: transform 0.2s, box-shadow 0.2s;
-}
-
-.main-card:hover {
- transform: translateY(-2px);
- box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
-}
-
-.code-block {
- background: rgba(37, 99, 235, 0.05);
- border-left: 3px solid var(--primary);
- border-radius: 6px;
- padding: 1rem;
- position: relative;
- overflow-x: auto;
-}
-
-.status-badge {
- padding: 6px 12px;
- border-radius: 20px;
- font-size: 0.9rem;
- font-weight: 500;
-}
-
-.guide-section {
- border-left: 3px solid var(--primary);
- padding-left: 1rem;
- margin: 2rem 0;
-}
-
-.command-example {
- position: relative;
- padding: 1.25rem;
- background: rgba(37, 99, 235, 0.03);
- border-radius: 8px;
- margin: 1rem 0;
-}
-
-.command-example::before {
- content: "➜";
- position: absolute;
- left: -1.5rem;
- color: var(--muted);
-}
-
-.toast {
- position: fixed;
- top: 1%;
- right: 1%;
- background-color: var(--card-background);
- color: var(--text-color);
- border: 1px solid var(--border-color);
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-}
-
-.toast-body {
- padding: 12px;
-}
-
-#version {
- text-align: center;
- font-size: 13px;
- line-height: 26px;
- color: #747474;
-}
-
-.bg-primary {
- --bs-bg-opacity: 1;
- background-color: #2c82de !important;
-}
-
-.bg-success {
- --bs-bg-opacity: 1;
- background-color: #2c82de !important;
-}
-
-.bg-danger {
- --bs-bg-opacity: 1;
- background-color: #2c82de !important;
-}
\ No newline at end of file
diff --git a/proxy/chunkreq.go b/proxy/chunkreq.go
index 70af6ed..d135d17 100644
--- a/proxy/chunkreq.go
+++ b/proxy/chunkreq.go
@@ -108,17 +108,6 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, matcher s
resp.Header.Del(header)
}
- //c.Header("Accept-Encoding", "gzip")
- //c.Header("Content-Encoding", "gzip")
-
- /*
- if cfg.CORS.Enabled {
- c.Header("Access-Control-Allow-Origin", "*")
- } else {
- c.Header("Access-Control-Allow-Origin", "")
- }
- */
-
switch cfg.Server.Cors {
case "*":
c.Header("Access-Control-Allow-Origin", "*")
@@ -150,7 +139,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, matcher s
}
} else {
//_, err = io.CopyBuffer(c.Writer, resp.Body, nil)
- _, err = copyb.CopyBuffer(c.Writer, resp.Body, nil)
+ _, err = copyb.Copy(c.Writer, resp.Body)
if err != nil {
logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
return
diff --git a/proxy/gitreq.go b/proxy/gitreq.go
index 551ab87..78f6b32 100644
--- a/proxy/gitreq.go
+++ b/proxy/gitreq.go
@@ -6,9 +6,7 @@ import (
"ghproxy/config"
"io"
"net/http"
- "net/url"
"strconv"
- "strings"
"github.com/WJQSERVER-STUDIO/go-utils/copyb"
"github.com/gin-gonic/gin"
@@ -51,6 +49,8 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
return
}
setRequestHeaders(c, req)
+ removeWSHeader(req)
+ reWriteEncodeHeader(req)
AuthPassThrough(c, cfg, req)
resp, err = gitclient.Do(req)
@@ -65,6 +65,8 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
return
}
setRequestHeaders(c, req)
+ removeWSHeader(req)
+ reWriteEncodeHeader(req)
AuthPassThrough(c, cfg, req)
resp, err = client.Do(req)
@@ -120,21 +122,7 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
}
c.Status(resp.StatusCode)
- /*
- // 使用固定32KB缓冲池
- buffer := BufferPool.Get().([]byte)
- defer BufferPool.Put(buffer)
-
- _, err = io.CopyBuffer(c.Writer, resp.Body, buffer)
- if err != nil {
- logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
- return
- } else {
- c.Writer.Flush() // 确保刷入
- }
- */
-
- _, err = copyb.CopyBuffer(c.Writer, resp.Body, nil)
+ _, err = copyb.Copy(c.Writer, resp.Body)
if err != nil {
logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
@@ -145,35 +133,3 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
}
}
-
-// extractParts 从给定的 URL 中提取所需的部分
-func extractParts(rawURL string) (string, string, string, url.Values, error) {
- // 解析 URL
- parsedURL, err := url.Parse(rawURL)
- if err != nil {
- return "", "", "", nil, err
- }
-
- // 获取路径部分并分割
- pathParts := strings.Split(parsedURL.Path, "/")
-
- // 提取所需的部分
- if len(pathParts) < 3 {
- return "", "", "", nil, fmt.Errorf("URL path is too short")
- }
-
- // 提取 /WJQSERVER-STUDIO 和 /go-utils.git
- repoOwner := "/" + pathParts[1]
- repoName := "/" + pathParts[2]
-
- // 剩余部分
- remainingPath := strings.Join(pathParts[3:], "/")
- if remainingPath != "" {
- remainingPath = "/" + remainingPath
- }
-
- // 查询参数
- queryParams := parsedURL.Query()
-
- return repoOwner, repoName, remainingPath, queryParams, nil
-}
diff --git a/proxy/handler.go b/proxy/handler.go
index 6c8af38..9d0ef80 100644
--- a/proxy/handler.go
+++ b/proxy/handler.go
@@ -14,16 +14,6 @@ import (
)
var re = regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 匹配http://或https://开头的路径
-/*
-var exps = []*regexp.Regexp{
- regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`), // 匹配 GitHub Releases 或 Archive 链接
- regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*`), // 匹配 GitHub Blob 或 Raw 链接
- regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*`), // 匹配 GitHub Info 或 Git 相关链接 (例如 .gitattributes, .gitignore)
- regexp.MustCompile(`^(?:https?://)?raw\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.+?/.+`), // 匹配 raw.githubusercontent.com 链接
- regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`), // 匹配 gist.githubusercontent.com 链接
- regexp.MustCompile(`^(?:https?://)?api\.github\.com/repos/([^/]+)/([^/]+)/.*`), // 匹配 api.github.com/repos 链接 (GitHub API)
-}
-*/
func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc {
return func(c *gin.Context) {
@@ -110,15 +100,6 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra
}
}
- /*
- matches = CheckURL(rawPath, c)
- if matches == nil {
- c.AbortWithStatus(http.StatusNotFound)
- logWarning("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
- return
- }
- */
-
// 若匹配api.github.com/repos/用户名/仓库名/路径, 则检查是否开启HeaderAuth
// 处理blob/raw路径
@@ -151,16 +132,3 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra
}
}
}
-
-/*
-func CheckURL(u string, c *gin.Context) []string {
- for _, exp := range exps {
- if matches := exp.FindStringSubmatch(u); matches != nil {
- return matches[1:]
- }
- }
- errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
- logError(errMsg)
- return nil
-}
-*/
diff --git a/proxy/matchrepo.go b/proxy/match.go
similarity index 90%
rename from proxy/matchrepo.go
rename to proxy/match.go
index 7030ebe..37f4aa4 100644
--- a/proxy/matchrepo.go
+++ b/proxy/match.go
@@ -6,6 +6,7 @@ import (
"fmt"
"ghproxy/config"
"io"
+ "net/url"
"regexp"
"strings"
)
@@ -284,3 +285,35 @@ func processLinks(input io.Reader, output io.Writer, compress string, host strin
return written, nil
}
+
+// extractParts 从给定的 URL 中提取所需的部分
+func extractParts(rawURL string) (string, string, string, url.Values, error) {
+ // 解析 URL
+ parsedURL, err := url.Parse(rawURL)
+ if err != nil {
+ return "", "", "", nil, err
+ }
+
+ // 获取路径部分并分割
+ pathParts := strings.Split(parsedURL.Path, "/")
+
+ // 提取所需的部分
+ if len(pathParts) < 3 {
+ return "", "", "", nil, fmt.Errorf("URL path is too short")
+ }
+
+ // 提取 /WJQSERVER-STUDIO 和 /go-utils.git
+ repoOwner := "/" + pathParts[1]
+ repoName := "/" + pathParts[2]
+
+ // 剩余部分
+ remainingPath := strings.Join(pathParts[3:], "/")
+ if remainingPath != "" {
+ remainingPath = "/" + remainingPath
+ }
+
+ // 查询参数
+ queryParams := parsedURL.Query()
+
+ return repoOwner, repoName, remainingPath, queryParams, nil
+}
diff --git a/proxy/proxyreq.go b/proxy/proxyreq.go
deleted file mode 100644
index c6b6222..0000000
--- a/proxy/proxyreq.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package proxy
-
-/*
-func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string, runMode string) {
- method := c.Request.Method
- logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
-
- client := createHTTPClient(mode)
- if runMode == "dev" {
- client.DevMode()
- }
-
- // 发送HEAD请求, 预获取Content-Length
- headReq := client.R()
- setRequestHeaders(c, headReq)
- AuthPassThrough(c, cfg, headReq)
-
- headResp, err := headReq.Head(u)
- if err != nil {
- HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
- return
- }
- defer headResp.Body.Close()
-
- if err := HandleResponseSize(headResp, cfg, c); err != nil {
- logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
- return
- }
-
- body, err := readRequestBody(c)
- if err != nil {
- HandleError(c, err.Error())
- return
- }
-
- req := client.R().SetBody(body)
- setRequestHeaders(c, req)
- AuthPassThrough(c, cfg, req)
-
- resp, err := SendRequest(c, req, method, u)
- if err != nil {
- HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
- return
- }
- defer resp.Body.Close()
-
- if err := HandleResponseSize(resp, cfg, c); err != nil {
- logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
- return
- }
-
- CopyResponseHeaders(resp, c, cfg)
- c.Status(resp.StatusCode)
- if err := copyResponseBody(c, resp.Body); err != nil {
- logError("%s %s %s %s %s Response-Copy-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
- }
-}
-
-// 复制响应体
-func copyResponseBody(c *gin.Context, respBody io.Reader) error {
- _, err := io.Copy(c.Writer, respBody)
- return err
-}
-
-// 判断并选择TLS指纹
-func createHTTPClient(mode string) *req.Client {
- client := req.C()
- switch mode {
- case "chrome":
- client.SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36").
- SetTLSFingerprintChrome().
- ImpersonateChrome()
- case "git":
- client.SetUserAgent("git/2.33.1")
- }
- return client
-}
-
-*/