Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11ba7cb0e5 | ||
|
|
f1ff3e9d27 | ||
|
|
b1e3a9c1bd | ||
|
|
267dfafcb9 | ||
|
|
e32adadaff | ||
|
|
942dda86e7 | ||
|
|
bbfc7c0d6d | ||
|
|
5af898b3e6 | ||
|
|
3c162bf9d7 | ||
|
|
1eed79463d | ||
|
|
6f67f6f5b4 | ||
|
|
2a5570a447 |
20
.github/ISSUE_TEMPLATE/features_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/features_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Features request
|
||||||
|
about: 提出新功能建议
|
||||||
|
title: "[Features]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 功能描述
|
||||||
|
|
||||||
|
请简要描述您希望增加的功能。
|
||||||
|
|
||||||
|
### 功能原因
|
||||||
|
|
||||||
|
请说明您为什么需要这个功能。
|
||||||
|
|
||||||
|
### 功能实现
|
||||||
|
|
||||||
|
请详细描述您期望的功能实现。
|
||||||
21
.github/workflows/build-dev.yml
vendored
21
.github/workflows/build-dev.yml
vendored
@@ -21,31 +21,36 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Load VERSION
|
- name: 加载版本号
|
||||||
run: |
|
run: |
|
||||||
if [ -f DEV-VERSION ]; then
|
if [ -f DEV-VERSION ]; then
|
||||||
echo "VERSION=$(cat DEV-VERSION)" >> $GITHUB_ENV
|
echo "VERSION=$(cat DEV-VERSION)" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "DEV-VERSION file not found!" && exit 1
|
echo "DEV-VERSION file not found!" && exit 1
|
||||||
fi
|
fi
|
||||||
- name: Set up Go
|
- name: 安装 Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- name: Install UPX
|
- name: 安装UPX
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install upx -y
|
sudo apt install upx -y
|
||||||
- name: Build
|
- name: 编译
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=0 go build -o ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./main.go
|
CGO_ENABLED=0 go build -ldflags "-X main.version=${{ env.VERSION }}" -o ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./main.go
|
||||||
upx -9 ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
upx -9 ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
||||||
- name: Package
|
- name: 打包
|
||||||
run: |
|
run: |
|
||||||
tar -czvf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz ./${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
mkdir ghproxyd
|
||||||
|
cp ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./ghproxyd/
|
||||||
|
mv ./ghproxyd/${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./ghproxyd/${{ env.OUTPUT_BINARY }}
|
||||||
|
cp LICENSE ./ghproxyd/
|
||||||
|
tar -czf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz -C ghproxyd .
|
||||||
|
ls
|
||||||
- name: Upload to GitHub Artifacts
|
- name: Upload to GitHub Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -57,7 +62,7 @@ jobs:
|
|||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
name: ${{ env.VERSION }}
|
name: ${{ env.VERSION }}
|
||||||
artifacts: ./${{ env.OUTPUT_BINARY }}*
|
artifacts: ./${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ env.VERSION }}
|
tag: ${{ env.VERSION }}
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
|
|||||||
51
.github/workflows/build-nocache.yml
vendored
51
.github/workflows/build-nocache.yml
vendored
@@ -1,51 +0,0 @@
|
|||||||
name: Build NoCache Docker Image
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
paths:
|
|
||||||
- 'VERSION'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
env:
|
|
||||||
IMAGE_NAME: wjqserver/ghproxy # 定义镜像名称变量
|
|
||||||
DOCKERFILE: docker/dockerfile/nocache/Dockerfile # 定义 Dockerfile 路径变量
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Load VERSION
|
|
||||||
run: |
|
|
||||||
if [ -f VERSION ]; then
|
|
||||||
echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "VERSION file not found!" && exit 1
|
|
||||||
fi
|
|
||||||
- name: Wait for Compile
|
|
||||||
run: sleep 300s
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: 构建镜像
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
file: ./${{ env.DOCKERFILE }}
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
${{ env.IMAGE_NAME }}:${{ env.VERSION }}-nocache
|
|
||||||
${{ env.IMAGE_NAME }}:nocache
|
|
||||||
65
.github/workflows/build.yml
vendored
65
.github/workflows/build.yml
vendored
@@ -21,31 +21,35 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Load VERSION
|
- name: 加载版本号
|
||||||
run: |
|
run: |
|
||||||
if [ -f VERSION ]; then
|
if [ -f VERSION ]; then
|
||||||
echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
|
echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo "VERSION file not found!" && exit 1
|
echo "VERSION file not found!" && exit 1
|
||||||
fi
|
fi
|
||||||
- name: Set up Go
|
- name: 安装 Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: ${{ env.GO_VERSION }}
|
go-version: ${{ env.GO_VERSION }}
|
||||||
- name: Install UPX
|
- name: 安装 UPX
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y upx
|
sudo apt-get install -y upx
|
||||||
- name: Build
|
- name: 编译
|
||||||
env:
|
env:
|
||||||
GOOS: ${{ matrix.goos }}
|
GOOS: ${{ matrix.goos }}
|
||||||
GOARCH: ${{ matrix.goarch }}
|
GOARCH: ${{ matrix.goarch }}
|
||||||
run: |
|
run: |
|
||||||
CGO_ENABLED=0 go build -ldflags="-s -w" -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}} ./main.go
|
||||||
upx -9 ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
upx -9 ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
||||||
- name: Package
|
- name: 打包
|
||||||
run: |
|
run: |
|
||||||
tar -czvf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz ./${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
mkdir ghproxyd
|
||||||
|
cp ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./ghproxyd/
|
||||||
|
mv ./ghproxyd/${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./ghproxyd/${{ env.OUTPUT_BINARY }}
|
||||||
|
cp LICENSE ./ghproxyd/
|
||||||
|
tar -czf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz -C ghproxyd .
|
||||||
- name: Upload to GitHub Artifacts
|
- name: Upload to GitHub Artifacts
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -57,12 +61,13 @@ jobs:
|
|||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
name: ${{ env.VERSION }}
|
name: ${{ env.VERSION }}
|
||||||
artifacts: ./${{ env.OUTPUT_BINARY }}*
|
artifacts: ./${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ env.VERSION }}
|
tag: ${{ env.VERSION }}
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
env:
|
env:
|
||||||
export PATH: $PATH:/usr/local/go/bin
|
export PATH: $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: build # 确保这个作业在 build 作业完成后运行
|
needs: build # 确保这个作业在 build 作业完成后运行
|
||||||
@@ -102,3 +107,45 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
${{ env.IMAGE_NAME }}:${{ env.VERSION }}
|
${{ env.IMAGE_NAME }}:${{ env.VERSION }}
|
||||||
${{ env.IMAGE_NAME }}:latest
|
${{ env.IMAGE_NAME }}:latest
|
||||||
|
|
||||||
|
docker-nocache:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build # 确保这个作业在 build 作业完成后运行
|
||||||
|
env:
|
||||||
|
IMAGE_NAME: wjqserver/ghproxy # 定义镜像名称变量
|
||||||
|
DOCKERFILE: docker/dockerfile/nocache/Dockerfile # 定义 Dockerfile 路径变量
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Load VERSION
|
||||||
|
run: |
|
||||||
|
if [ -f VERSION ]; then
|
||||||
|
echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "VERSION file not found!" && exit 1
|
||||||
|
fi
|
||||||
|
- name: Wait for Compile
|
||||||
|
run: sleep 300s
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: 构建镜像
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
file: ./${{ env.DOCKERFILE }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.IMAGE_NAME }}:${{ env.VERSION }}-nocache
|
||||||
|
${{ env.IMAGE_NAME }}:nocache
|
||||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -1,5 +1,45 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
v1.7.0
|
||||||
|
---
|
||||||
|
- ADD: 加入`rate`模块,实现内置速率限制
|
||||||
|
- CHANGE: 优化`blacklist`与`whitelist`模块的匹配算法,提升性能;由原先的完整匹配改为切片匹配,提升匹配效率
|
||||||
|
- ADD: 加入`version`相关表示与API接口
|
||||||
|
- ADD: 加入`rate`相关API接口
|
||||||
|
- CHANGE: 优化前端界面,优化部分样式
|
||||||
|
- CHANGE: 更新相关依赖库
|
||||||
|
- CHANGE: 对编译打包进行改进,此后不再提供独立可执行文件,请改为拉取`tar.gz`压缩包
|
||||||
|
|
||||||
|
24w21d
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v1.7.0的预发布版本,请勿在生产环境中使用
|
||||||
|
- ADD: 新增`ratePerMinute` API可供查询
|
||||||
|
- ADD: 前端新增 version 标识
|
||||||
|
- ADD: 前端新增 `重定向` 按钮,用于重定向到代理后的链接
|
||||||
|
- CHANGE: 优化输出代码块,使样式更加美观
|
||||||
|
- CHANGE: 更新相关依赖库
|
||||||
|
- CHANGE: 对黑名单模块进行实验性功能优化,提升性能(改进匹配算法,在切片后优先匹配user,减少无效匹配)
|
||||||
|
|
||||||
|
24w21c
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v1.7.0的预发布版本,请勿在生产环境中使用
|
||||||
|
- CHANGE: 对编译打包进行改进,此后不再提供独立可执行文件,请改为拉取`tar.gz`压缩包
|
||||||
|
- CHANGE: 由于上述原因,对Docker打包进行相应改进
|
||||||
|
|
||||||
|
24w21b
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v1.7.0的预发布版本,请勿在生产环境中使用
|
||||||
|
- ADD: 加入版本号标识与对应API接口
|
||||||
|
- ADD: 加入速率限制API接口
|
||||||
|
- CHANGE: 修改打包部分
|
||||||
|
|
||||||
|
24w21a
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v1.7.0的预发布版本,请勿在生产环境中使用
|
||||||
|
- ADD: 尝试加入程序内置速率限制
|
||||||
|
- CHANGE: 更新相关依赖库
|
||||||
|
- CHANGE: 更换Dev版本底包,于release版本保持一致
|
||||||
|
|
||||||
v1.6.2
|
v1.6.2
|
||||||
---
|
---
|
||||||
- CHANGE: 优化前端界面,优化部分样式
|
- CHANGE: 优化前端界面,优化部分样式
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24w20b
|
24w21d
|
||||||
37
api/api.go
37
api/api.go
@@ -13,7 +13,6 @@ var (
|
|||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
// 日志模块
|
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
@@ -21,8 +20,7 @@ var (
|
|||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitHandleRouter(cfg *config.Config, router *gin.Engine) {
|
func InitHandleRouter(cfg *config.Config, router *gin.Engine, version string) {
|
||||||
// 设置路由
|
|
||||||
apiRouter := router.Group("api")
|
apiRouter := router.Group("api")
|
||||||
{
|
{
|
||||||
apiRouter.GET("/size_limit", func(c *gin.Context) {
|
apiRouter.GET("/size_limit", func(c *gin.Context) {
|
||||||
@@ -40,6 +38,15 @@ func InitHandleRouter(cfg *config.Config, router *gin.Engine) {
|
|||||||
apiRouter.GET("/healthcheck", func(c *gin.Context) {
|
apiRouter.GET("/healthcheck", func(c *gin.Context) {
|
||||||
HealthcheckHandler(c)
|
HealthcheckHandler(c)
|
||||||
})
|
})
|
||||||
|
apiRouter.GET("/version", func(c *gin.Context) {
|
||||||
|
VersionHandler(c, version)
|
||||||
|
})
|
||||||
|
apiRouter.GET("/rate_limit/status", func(c *gin.Context) {
|
||||||
|
RateLimitStatusHandler(c, cfg)
|
||||||
|
})
|
||||||
|
apiRouter.GET("/rate_limit/limit", func(c *gin.Context) {
|
||||||
|
RateLimitLimitHandler(c, cfg)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
logInfo("API router Init success")
|
logInfo("API router Init success")
|
||||||
}
|
}
|
||||||
@@ -84,3 +91,27 @@ func HealthcheckHandler(c *gin.Context) {
|
|||||||
"Status": "OK",
|
"Status": "OK",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{}{
|
||||||
|
"Version": version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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{}{
|
||||||
|
"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{}{
|
||||||
|
"RatePerMinute": cfg.RateLimit.RatePerMinute,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 日志模块
|
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
@@ -16,7 +15,6 @@ var (
|
|||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auth Init
|
|
||||||
func Init(cfg *config.Config) {
|
func Init(cfg *config.Config) {
|
||||||
if cfg.Blacklist.Enabled {
|
if cfg.Blacklist.Enabled {
|
||||||
LoadBlacklist(cfg)
|
LoadBlacklist(cfg)
|
||||||
@@ -28,17 +26,13 @@ func Init(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) {
|
func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) {
|
||||||
// 如果身份验证未启用,直接返回 true
|
|
||||||
if !cfg.Auth.Enabled {
|
if !cfg.Auth.Enabled {
|
||||||
return true, ""
|
return true, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 auth_token 参数
|
|
||||||
authToken := c.Query("auth_token")
|
authToken := c.Query("auth_token")
|
||||||
// IP METHOD URL USERAGENT PROTO TOKEN
|
|
||||||
logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken)
|
logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken)
|
||||||
|
|
||||||
// 验证 token
|
|
||||||
if authToken == "" {
|
if authToken == "" {
|
||||||
err := "Auth token == nil"
|
err := "Auth token == nil"
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ func LoadBlacklist(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fullrepo: "owner/repo" or "owner/*"
|
func CheckBlacklist(repouser string, user string, repo string) bool {
|
||||||
func CheckBlacklist(fullrepo string) bool {
|
return forRangeCheckBlacklist(blacklist.Blacklist, repouser, user)
|
||||||
return forRangeCheckBlacklist(blacklist.Blacklist, fullrepo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func sliceRepoName_Blacklist(fullrepo string) (string, string) {
|
func sliceRepoName_Blacklist(fullrepo string) (string, string) {
|
||||||
@@ -45,11 +44,16 @@ func sliceRepoName_Blacklist(fullrepo string) (string, string) {
|
|||||||
return s[0], s[1]
|
return s[0], s[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func forRangeCheckBlacklist(blist []string, fullrepo string) bool {
|
func forRangeCheckBlacklist(blist []string, fullrepo string, user string) bool {
|
||||||
repoUser, _ := sliceRepoName_Blacklist(fullrepo)
|
|
||||||
for _, blocked := range blist {
|
for _, blocked := range blist {
|
||||||
if blocked == fullrepo || (strings.HasSuffix(blocked, "/*") && strings.HasPrefix(repoUser, blocked[:len(blocked)-2])) {
|
users, _ := sliceRepoName_Blacklist(blocked)
|
||||||
return true
|
if user == users {
|
||||||
|
if strings.HasSuffix(blocked, "/*") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if fullrepo == blocked {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ func LoadWhitelist(cfg *config.Config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckWhitelist(fullrepo string) bool {
|
func CheckWhitelist(fullrepo string, user string, repo string) bool {
|
||||||
return forRangeCheckWhitelist(whitelist.Whitelist, fullrepo)
|
return forRangeCheckWhitelist(whitelist.Whitelist, fullrepo, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sliceRepoName_Whitelist(fullrepo string) (string, string) {
|
func sliceRepoName_Whitelist(fullrepo string) (string, string) {
|
||||||
@@ -43,11 +43,16 @@ func sliceRepoName_Whitelist(fullrepo string) (string, string) {
|
|||||||
return s[0], s[1]
|
return s[0], s[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func forRangeCheckWhitelist(wlist []string, fullrepo string) bool {
|
func forRangeCheckWhitelist(wlist []string, fullrepo string, user string) bool {
|
||||||
repoUser, _ := sliceRepoName_Whitelist(fullrepo)
|
for _, passd := range wlist {
|
||||||
for _, blocked := range wlist {
|
users, _ := sliceRepoName_Whitelist(passd)
|
||||||
if blocked == fullrepo || (strings.HasSuffix(blocked, "/*") && strings.HasPrefix(repoUser, blocked[:len(blocked)-2])) {
|
if users == user {
|
||||||
return true
|
if strings.HasSuffix(passd, "/*") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if fullrepo == passd {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type Config struct {
|
|||||||
Auth AuthConfig
|
Auth AuthConfig
|
||||||
Blacklist BlacklistConfig
|
Blacklist BlacklistConfig
|
||||||
Whitelist WhitelistConfig
|
Whitelist WhitelistConfig
|
||||||
|
RateLimit RateLimitConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
@@ -49,6 +50,12 @@ type WhitelistConfig struct {
|
|||||||
WhitelistFile string `toml:"whitelistFile"`
|
WhitelistFile string `toml:"whitelistFile"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RateLimitConfig struct {
|
||||||
|
Enabled bool `toml:"enabled"`
|
||||||
|
RatePerMinute int `toml:"ratePerMinute"`
|
||||||
|
Burst int `toml:"burst"`
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfig 从 TOML 配置文件加载配置
|
// LoadConfig 从 TOML 配置文件加载配置
|
||||||
func LoadConfig(filePath string) (*Config, error) {
|
func LoadConfig(filePath string) (*Config, error) {
|
||||||
var config Config
|
var config Config
|
||||||
|
|||||||
@@ -25,3 +25,8 @@ enabled = false
|
|||||||
[whitelist]
|
[whitelist]
|
||||||
enabled = false
|
enabled = false
|
||||||
whitelistFile = "/data/ghproxy/config/whitelist.json"
|
whitelistFile = "/data/ghproxy/config/whitelist.json"
|
||||||
|
|
||||||
|
[rateLimit]
|
||||||
|
enabled = false
|
||||||
|
ratePerMinute = 180
|
||||||
|
burst = 5
|
||||||
|
|||||||
@@ -25,3 +25,8 @@ enabled = false
|
|||||||
[whitelist]
|
[whitelist]
|
||||||
enabled = false
|
enabled = false
|
||||||
whitelistFile = "/usr/local/ghproxy/config/whitelist.json"
|
whitelistFile = "/usr/local/ghproxy/config/whitelist.json"
|
||||||
|
|
||||||
|
[rateLimit]
|
||||||
|
enabled = false
|
||||||
|
ratePerMinute = 180
|
||||||
|
burst = 5
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ VERSION=$(curl -s https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/mai
|
|||||||
wget -q -O ${ghproxy_dir}/VERSION https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/main/DEV-VERSION
|
wget -q -O ${ghproxy_dir}/VERSION https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/main/DEV-VERSION
|
||||||
|
|
||||||
# 下载ghproxy
|
# 下载ghproxy
|
||||||
wget -q -O ${ghproxy_dir}/ghproxy https://github.com/WJQSERVER-STUDIO/ghproxy/releases/download/$VERSION/ghproxy-linux-$ARCH
|
wget -q -O ${ghproxy_dir}/ghproxy https://github.com/WJQSERVER-STUDIO/ghproxy/releases/download/$VERSION/ghproxy-linux-$ARCH.tar.gz
|
||||||
|
install tar
|
||||||
|
tar -zxvf ${ghproxy_dir}/ghproxy-linux-$ARCH.tar.gz -C ${ghproxy_dir}
|
||||||
chmod +x ${ghproxy_dir}/ghproxy
|
chmod +x ${ghproxy_dir}/ghproxy
|
||||||
|
|
||||||
# 下载pages
|
# 下载pages
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ VERSION=$(curl -s https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/mai
|
|||||||
wget -q -O ${ghproxy_dir}/VERSION https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/main/VERSION
|
wget -q -O ${ghproxy_dir}/VERSION https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/main/VERSION
|
||||||
|
|
||||||
# 下载ghproxy
|
# 下载ghproxy
|
||||||
wget -q -O ${ghproxy_dir}/ghproxy https://github.com/WJQSERVER-STUDIO/ghproxy/releases/download/$VERSION/ghproxy-linux-$ARCH
|
wget -q -O ${ghproxy_dir}/ghproxy https://github.com/WJQSERVER-STUDIO/ghproxy/releases/download/$VERSION/ghproxy-linux-$ARCH.tar.gz
|
||||||
|
install tar
|
||||||
|
tar -zxvf ${ghproxy_dir}/ghproxy-linux-$ARCH.tar.gz -C ${ghproxy_dir}
|
||||||
chmod +x ${ghproxy_dir}/ghproxy
|
chmod +x ${ghproxy_dir}/ghproxy
|
||||||
|
|
||||||
# 下载pages
|
# 下载pages
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM wjqserver/caddy:daily-alpine AS builder
|
FROM wjqserver/caddy:2.9.0-rc-alpine AS builder
|
||||||
|
|
||||||
ARG USER=WJQSERVER-STUDIO
|
ARG USER=WJQSERVER-STUDIO
|
||||||
ARG REPO=ghproxy
|
ARG REPO=ghproxy
|
||||||
@@ -13,7 +13,7 @@ RUN mkdir -p /data/${APPLICATION}/config
|
|||||||
RUN mkdir -p /data/${APPLICATION}/log
|
RUN mkdir -p /data/${APPLICATION}/log
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
RUN apk add --no-cache curl wget
|
RUN apk add --no-cache curl wget tar
|
||||||
|
|
||||||
# 前端
|
# 前端
|
||||||
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
||||||
@@ -21,7 +21,9 @@ RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${RE
|
|||||||
|
|
||||||
# 后端
|
# 后端
|
||||||
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/DEV-VERSION) && \
|
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/DEV-VERSION) && \
|
||||||
wget -O /data/${APPLICATION}/${APPLICATION} https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION}-${TARGETOS}-${TARGETARCH}
|
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/dev/init.sh
|
RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/dev/init.sh
|
||||||
|
|
||||||
# 拉取配置
|
# 拉取配置
|
||||||
@@ -34,7 +36,7 @@ RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.co
|
|||||||
RUN chmod +x /data/${APPLICATION}/${APPLICATION}
|
RUN chmod +x /data/${APPLICATION}/${APPLICATION}
|
||||||
RUN chmod +x /usr/local/bin/init.sh
|
RUN chmod +x /usr/local/bin/init.sh
|
||||||
|
|
||||||
FROM wjqserver/caddy:daily-alpine
|
FROM wjqserver/caddy:2.9.0-rc-alpine
|
||||||
|
|
||||||
COPY --from=builder /data/www /data/www
|
COPY --from=builder /data/www /data/www
|
||||||
COPY --from=builder /data/caddy /data/caddy
|
COPY --from=builder /data/caddy /data/caddy
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN mkdir -p /data/${APPLICATION}/config
|
|||||||
RUN mkdir -p /data/${APPLICATION}/log
|
RUN mkdir -p /data/${APPLICATION}/log
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
RUN apk add --no-cache curl wget
|
RUN apk add --no-cache curl wget tar
|
||||||
|
|
||||||
# 前端
|
# 前端
|
||||||
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
||||||
@@ -21,7 +21,9 @@ RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${RE
|
|||||||
|
|
||||||
# 后端
|
# 后端
|
||||||
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \
|
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \
|
||||||
wget -O /data/${APPLICATION}/${APPLICATION} https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION}-${TARGETOS}-${TARGETARCH}
|
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/nocache/init.sh
|
RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/nocache/init.sh
|
||||||
|
|
||||||
# 拉取配置
|
# 拉取配置
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN mkdir -p /data/${APPLICATION}/config
|
|||||||
RUN mkdir -p /data/${APPLICATION}/log
|
RUN mkdir -p /data/${APPLICATION}/log
|
||||||
|
|
||||||
# 安装依赖
|
# 安装依赖
|
||||||
RUN apk add --no-cache curl wget
|
RUN apk add --no-cache curl wget tar
|
||||||
|
|
||||||
# 前端
|
# 前端
|
||||||
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
||||||
@@ -21,7 +21,9 @@ RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${RE
|
|||||||
|
|
||||||
# 后端
|
# 后端
|
||||||
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \
|
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \
|
||||||
wget -O /data/${APPLICATION}/${APPLICATION} https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION}-${TARGETOS}-${TARGETARCH}
|
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 /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/release/init.sh
|
||||||
|
|
||||||
# 拉取配置
|
# 拉取配置
|
||||||
|
|||||||
5
go.mod
5
go.mod
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/BurntSushi/toml v1.4.0
|
github.com/BurntSushi/toml v1.4.0
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/imroc/req/v3 v3.48.0
|
github.com/imroc/req/v3 v3.48.0
|
||||||
|
golang.org/x/time v0.7.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -22,7 +23,7 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.3 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241023014458-598669927662 // indirect
|
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
@@ -32,7 +33,7 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20 // 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/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2 // indirect
|
github.com/onsi/ginkgo/v2 v2.21.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.48.1 // indirect
|
github.com/quic-go/quic-go v0.48.1 // indirect
|
||||||
|
|||||||
9
go.sum
9
go.sum
@@ -50,6 +50,10 @@ github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jn
|
|||||||
github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA=
|
github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA=
|
||||||
github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||||
|
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
|
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 h1:sAGdeJj0bnMgUNVeUpp6AYlVdCt3/GdI3pGRqsNSQLs=
|
||||||
|
github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@@ -76,8 +80,11 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
|
||||||
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
|
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
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/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
@@ -132,6 +139,8 @@ golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
|||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ func Log(customMessage string) {
|
|||||||
logChannel <- customMessage
|
logChannel <- customMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
// 格式化日志记录
|
|
||||||
func Logw(format string, args ...interface{}) {
|
func Logw(format string, args ...interface{}) {
|
||||||
message := fmt.Sprintf(format, args...)
|
message := fmt.Sprintf(format, args...)
|
||||||
Log(message)
|
Log(message)
|
||||||
@@ -82,7 +81,6 @@ func LogError(format string, args ...interface{}) {
|
|||||||
Log(message)
|
Log(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭日志文件
|
|
||||||
func Close() {
|
func Close() {
|
||||||
logFileMutex.Lock()
|
logFileMutex.Lock()
|
||||||
defer logFileMutex.Unlock()
|
defer logFileMutex.Unlock()
|
||||||
|
|||||||
20
main.go
20
main.go
@@ -5,12 +5,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"ghproxy/api"
|
"ghproxy/api"
|
||||||
"ghproxy/auth"
|
"ghproxy/auth"
|
||||||
"ghproxy/config"
|
"ghproxy/config"
|
||||||
"ghproxy/logger"
|
"ghproxy/logger"
|
||||||
"ghproxy/proxy"
|
"ghproxy/proxy"
|
||||||
|
"ghproxy/rate"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -20,6 +22,8 @@ var (
|
|||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
configfile = "/data/ghproxy/config/config.toml"
|
configfile = "/data/ghproxy/config/config.toml"
|
||||||
cfgfile string
|
cfgfile string
|
||||||
|
limiter *rate.RateLimiter
|
||||||
|
version string
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -58,8 +62,15 @@ func loadlist(cfg *config.Config) {
|
|||||||
auth.Init(cfg)
|
auth.Init(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupApi(cfg *config.Config, router *gin.Engine) {
|
func setupApi(cfg *config.Config, router *gin.Engine, version string) {
|
||||||
api.InitHandleRouter(cfg, router)
|
api.InitHandleRouter(cfg, router, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupRateLimit(cfg *config.Config) {
|
||||||
|
if cfg.RateLimit.Enabled {
|
||||||
|
limiter = rate.New(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute)
|
||||||
|
logInfo("Rate Limit Loaded")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -68,13 +79,14 @@ func init() {
|
|||||||
loadConfig()
|
loadConfig()
|
||||||
setupLogger(cfg)
|
setupLogger(cfg)
|
||||||
loadlist(cfg)
|
loadlist(cfg)
|
||||||
|
setupRateLimit(cfg)
|
||||||
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
router = gin.Default()
|
router = gin.Default()
|
||||||
router.UseH2C = true
|
router.UseH2C = true
|
||||||
|
|
||||||
setupApi(cfg, router)
|
setupApi(cfg, router, version)
|
||||||
|
|
||||||
if cfg.Pages.Enabled {
|
if cfg.Pages.Enabled {
|
||||||
indexPagePath := fmt.Sprintf("%s/index.html", cfg.Pages.StaticDir)
|
indexPagePath := fmt.Sprintf("%s/index.html", cfg.Pages.StaticDir)
|
||||||
@@ -92,7 +104,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.NoRoute(func(c *gin.Context) {
|
router.NoRoute(func(c *gin.Context) {
|
||||||
proxy.NoRouteHandler(cfg)(c)
|
proxy.NoRouteHandler(cfg, limiter)(c)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
118
pages/index.html
118
pages/index.html
@@ -6,12 +6,11 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="Github文件加速">
|
<meta name="description" content="Github文件加速">
|
||||||
<meta name="keywords" content="Github,文件加速,ghproxy">
|
<meta name="keywords" content="Github,文件加速,ghproxy">
|
||||||
<meta name="color-scheme" content="dark light">
|
<meta name="color-scheme" content="dark light">
|
||||||
<title>Github文件加速</title>
|
<title>Github文件加速</title>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://font.sec.miui.com/font/css?family=MiSans:400,700:MiSans">
|
<link rel="stylesheet" href="https://font.sec.miui.com/font/css?family=MiSans:400,700:MiSans">
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--color: #dadada;
|
--color: #dadada;
|
||||||
--fontcolor: #333;
|
--fontcolor: #333;
|
||||||
@@ -41,6 +40,40 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
width: 12.5%;
|
||||||
|
height: 2%;
|
||||||
|
background-color: #39c5bb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0%;
|
||||||
|
right: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version p {
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
height: 10px;
|
||||||
|
margin-top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-track {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar-thumb {
|
||||||
|
background: #39c5bb;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -89,9 +122,9 @@
|
|||||||
pre {
|
pre {
|
||||||
background: #012333;
|
background: #012333;
|
||||||
color: #39c5bc;
|
color: #39c5bc;
|
||||||
padding: 20px 20px;
|
padding: 15px 20px 15px 20px;
|
||||||
margin: 10px 0;
|
margin: 0px 0;
|
||||||
border-radius: 8px;
|
border-radius: 0.5rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
@@ -100,10 +133,10 @@
|
|||||||
content: " ";
|
content: " ";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 6px;
|
||||||
left: 10px;
|
left: 6px;
|
||||||
width: 12px;
|
width: 10px;
|
||||||
height: 12px;
|
height: 10px;
|
||||||
background: #bd3c35;
|
background: #bd3c35;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 20px 0 0 #d69f27, 40px 0 0 #39c5bb;
|
box-shadow: 20px 0 0 #d69f27, 40px 0 0 #39c5bb;
|
||||||
@@ -111,7 +144,8 @@
|
|||||||
|
|
||||||
code {
|
code {
|
||||||
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||||
font-size: 1em;
|
font-size: 0.9em;
|
||||||
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -143,7 +177,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 80%;
|
max-width: 65%;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,9 +192,14 @@
|
|||||||
.status-container {
|
.status-container {
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-bottom: 10%;
|
margin-bottom: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
width: 7.5%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group {
|
.form-group {
|
||||||
@@ -201,7 +240,7 @@
|
|||||||
|
|
||||||
.copy-button {
|
.copy-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 5px;
|
top: 10px;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
background: rgba(0, 217, 224, 0.822);
|
background: rgba(0, 217, 224, 0.822);
|
||||||
color: white;
|
color: white;
|
||||||
@@ -211,6 +250,24 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redir-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 65px;
|
||||||
|
background: rgba(0, 217, 224, 0.822);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
z-index: 1;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre:hover .copy-button {
|
pre:hover .copy-button {
|
||||||
@@ -239,6 +296,9 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div class="version">
|
||||||
|
<p id="version"></p>
|
||||||
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Github文件加速</h1>
|
<h1>Github文件加速</h1>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -248,6 +308,7 @@
|
|||||||
|
|
||||||
<div class="code" id="outputBlock">
|
<div class="code" id="outputBlock">
|
||||||
<button class="copy-button" id="copyButton">复制</button>
|
<button class="copy-button" id="copyButton">复制</button>
|
||||||
|
<button class="redir-button" id="redirButton">打开</button>
|
||||||
<pre id="formattedLinkOutput"></pre>
|
<pre id="formattedLinkOutput"></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tips">
|
<div class="tips">
|
||||||
@@ -274,16 +335,22 @@
|
|||||||
|
|
||||||
if (githubLinkInput.value.startsWith("https://github.com/") || githubLinkInput.value.startsWith("http://github.com/")) {
|
if (githubLinkInput.value.startsWith("https://github.com/") || githubLinkInput.value.startsWith("http://github.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/github.com" + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 8));
|
formattedLink = "https://" + currentHost + "/github.com" + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 8));
|
||||||
|
displayButton();
|
||||||
} else if (githubLinkInput.value.startsWith("github.com/")) {
|
} else if (githubLinkInput.value.startsWith("github.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
|
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
|
||||||
|
displayButton();
|
||||||
} else if (githubLinkInput.value.startsWith("https://raw.githubusercontent.com/") || githubLinkInput.value.startsWith("http://raw.githubusercontent.com/")) {
|
} else if (githubLinkInput.value.startsWith("https://raw.githubusercontent.com/") || githubLinkInput.value.startsWith("http://raw.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 7));
|
formattedLink = "https://" + currentHost + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 7));
|
||||||
|
displayButton();
|
||||||
} else if (githubLinkInput.value.startsWith("raw.githubusercontent.com/")) {
|
} else if (githubLinkInput.value.startsWith("raw.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
|
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
|
||||||
|
displayButton();
|
||||||
} else if (githubLinkInput.value.startsWith("https://gist.githubusercontent.com/") || githubLinkInput.value.startsWith("http://gist.githubusercontent.com/")) {
|
} else if (githubLinkInput.value.startsWith("https://gist.githubusercontent.com/") || githubLinkInput.value.startsWith("http://gist.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/gist.github.com" + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 18));
|
formattedLink = "https://" + currentHost + "/gist.github.com" + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 18));
|
||||||
|
displayButton();
|
||||||
} else if (githubLinkInput.value.startsWith("gist.githubusercontent.com/")) {
|
} else if (githubLinkInput.value.startsWith("gist.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
|
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
|
||||||
|
displayButton();
|
||||||
} else {
|
} else {
|
||||||
showToast('请输入有效的GitHub链接');
|
showToast('请输入有效的GitHub链接');
|
||||||
}
|
}
|
||||||
@@ -291,6 +358,19 @@
|
|||||||
formattedLinkOutput.textContent = formattedLink;
|
formattedLinkOutput.textContent = formattedLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayButton() {
|
||||||
|
var copyButton = document.getElementById('copyButton');
|
||||||
|
var redirButton = document.getElementById('redirButton');
|
||||||
|
copyButton.style.display = 'block';
|
||||||
|
redirButton.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirToFormattedLink() {
|
||||||
|
var formattedLinkOutput = document.getElementById('formattedLinkOutput');
|
||||||
|
console.log(formattedLinkOutput.textContent);
|
||||||
|
window.open(formattedLinkOutput.textContent);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('formatButton').addEventListener('click', formatGithubLink);
|
document.getElementById('formatButton').addEventListener('click', formatGithubLink);
|
||||||
document.getElementById('copyButton').addEventListener('click', function () {
|
document.getElementById('copyButton').addEventListener('click', function () {
|
||||||
const output = document.getElementById('formattedLinkOutput');
|
const output = document.getElementById('formattedLinkOutput');
|
||||||
@@ -300,9 +380,9 @@
|
|||||||
window.getSelection().addRange(range);
|
window.getSelection().addRange(range);
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
window.getSelection().removeAllRanges();
|
window.getSelection().removeAllRanges();
|
||||||
//alert('链接已复制到剪贴板');
|
|
||||||
showToast('链接已复制到剪贴板');
|
showToast('链接已复制到剪贴板');
|
||||||
});
|
});
|
||||||
|
document.getElementById('redirButton').addEventListener('click', redirToFormattedLink);
|
||||||
|
|
||||||
function showToast(message) {
|
function showToast(message) {
|
||||||
const toast = document.getElementById('toast');
|
const toast = document.getElementById('toast');
|
||||||
@@ -360,10 +440,22 @@
|
|||||||
console.error('Error fetching API:', error);
|
console.error('Error fetching API:', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function fetchVersion() {
|
||||||
|
fetch(window.location.origin + '/api/version')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const version = document.getElementById('version');
|
||||||
|
version.textContent = `${data.Version}`;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching API:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
function fetchAPI() {
|
function fetchAPI() {
|
||||||
fetchSizeLimit();
|
fetchSizeLimit();
|
||||||
fetchWhiteList();
|
fetchWhiteList();
|
||||||
fetchBlackList();
|
fetchBlackList();
|
||||||
|
fetchVersion();
|
||||||
}
|
}
|
||||||
document.addEventListener('DOMContentLoaded', fetchAPI);
|
document.addEventListener('DOMContentLoaded', fetchAPI);
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"ghproxy/auth"
|
"ghproxy/auth"
|
||||||
"ghproxy/config"
|
"ghproxy/config"
|
||||||
"ghproxy/logger"
|
"ghproxy/logger"
|
||||||
|
"ghproxy/rate"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
@@ -32,8 +33,18 @@ var exps = []*regexp.Regexp{
|
|||||||
regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`),
|
regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`),
|
||||||
}
|
}
|
||||||
|
|
||||||
func NoRouteHandler(cfg *config.Config) gin.HandlerFunc {
|
func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
// 限制访问频率
|
||||||
|
if cfg.RateLimit.Enabled {
|
||||||
|
logInfo("Rate_Limit Enabled")
|
||||||
|
if !limiter.Allow() {
|
||||||
|
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"})
|
||||||
|
logWarning("%s %s %s %s %s 429-TooManyRequests", c.ClientIP(), c.Request.Method, c.Request.URL.RequestURI(), c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/")
|
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/")
|
||||||
re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`)
|
re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`)
|
||||||
matches := re.FindStringSubmatch(rawPath)
|
matches := re.FindStringSubmatch(rawPath)
|
||||||
@@ -50,14 +61,14 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc {
|
|||||||
username, repo := MatchUserRepo(rawPath, cfg, c, matches)
|
username, repo := MatchUserRepo(rawPath, cfg, c, matches)
|
||||||
|
|
||||||
logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo)
|
logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo)
|
||||||
fullrepo := fmt.Sprintf("%s/%s", username, repo)
|
repouser := fmt.Sprintf("%s/%s", username, repo)
|
||||||
|
|
||||||
// 白名单检查
|
// 白名单检查
|
||||||
if cfg.Whitelist.Enabled {
|
if cfg.Whitelist.Enabled {
|
||||||
whitelist := auth.CheckWhitelist(fullrepo)
|
whitelist := auth.CheckWhitelist(repouser, username, repo)
|
||||||
if !whitelist {
|
if !whitelist {
|
||||||
logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, fullrepo)
|
logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser)
|
||||||
errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", fullrepo)
|
errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", repouser)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
|
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
|
||||||
logWarning(logErrMsg)
|
logWarning(logErrMsg)
|
||||||
return
|
return
|
||||||
@@ -66,10 +77,10 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc {
|
|||||||
|
|
||||||
// 黑名单检查
|
// 黑名单检查
|
||||||
if cfg.Blacklist.Enabled {
|
if cfg.Blacklist.Enabled {
|
||||||
blacklist := auth.CheckBlacklist(fullrepo)
|
blacklist := auth.CheckBlacklist(repouser, username, repo)
|
||||||
if blacklist {
|
if blacklist {
|
||||||
logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, fullrepo)
|
logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser)
|
||||||
errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", fullrepo)
|
errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", repouser)
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
|
c.JSON(http.StatusForbidden, gin.H{"error": errMsg})
|
||||||
logWarning(logErrMsg)
|
logWarning(logErrMsg)
|
||||||
return
|
return
|
||||||
|
|||||||
21
rate/rate.go
Normal file
21
rate/rate.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package rate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RateLimiter struct {
|
||||||
|
limiter *rate.Limiter
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(limit int, burst int, duration time.Duration) *RateLimiter {
|
||||||
|
return &RateLimiter{
|
||||||
|
limiter: rate.NewLimiter(rate.Limit(float64(limit)/duration.Seconds()), burst),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *RateLimiter) Allow() bool {
|
||||||
|
return rl.limiter.Allow()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user