mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 16:21:11 +08:00
24w19d
This commit is contained in:
parent
c31e887ad3
commit
575e36ef90
7 changed files with 252 additions and 23 deletions
51
.github/workflows/build-nocache.yml
vendored
Normal file
51
.github/workflows/build-nocache.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
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
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
24w19d
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v1.6.1的预发布版本,请勿在生产环境中使用
|
||||||
|
- ADD: 新增nocache版本,供由用户自行优化缓存策略
|
||||||
|
- CHANGE: 优化`Proxy`核心模块内部结构,提升性能
|
||||||
|
- REMOVE: 移除`Proxy`模块内部分无用`logInfo`
|
||||||
|
|
||||||
24w19c
|
24w19c
|
||||||
---
|
---
|
||||||
- PRE-RELEASE: 此版本是v1.6.1的预发布版本,请勿在生产环境中使用
|
- PRE-RELEASE: 此版本是v1.6.1的预发布版本,请勿在生产环境中使用
|
||||||
|
|
|
||||||
99
caddyfile/nocache/Caddyfile
Normal file
99
caddyfile/nocache/Caddyfile
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
debug
|
||||||
|
http_port 80
|
||||||
|
https_port 443
|
||||||
|
order cache before rewrite
|
||||||
|
cache {
|
||||||
|
cache_name GhProxyCache
|
||||||
|
}
|
||||||
|
log {
|
||||||
|
level INFO
|
||||||
|
output file /data/caddy/log/caddy.log {
|
||||||
|
roll_size 5MB
|
||||||
|
roll_keep 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server :80 {
|
||||||
|
protocols h1 h2 h2c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(log) {
|
||||||
|
log {
|
||||||
|
format transform `{request>headers>X-Forwarded-For>[0]:request>remote_ip} - {user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}"` {
|
||||||
|
time_format "02/Jan/2006:15:04:05 -0700"
|
||||||
|
}
|
||||||
|
output file /data/caddy/log/{args[0]}/access.log {
|
||||||
|
roll_size 5MB
|
||||||
|
roll_keep 10
|
||||||
|
roll_keep_for 24h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(error_page) {
|
||||||
|
handle_errors {
|
||||||
|
rewrite * /{err.status_code}.html
|
||||||
|
root * /data/caddy/pages/errors
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(encode) {
|
||||||
|
encode {
|
||||||
|
zstd best
|
||||||
|
br 5 v2
|
||||||
|
gzip 5
|
||||||
|
minimum_length 256
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(cache) {
|
||||||
|
cache {
|
||||||
|
allowed_http_verbs GET
|
||||||
|
stale {args[0]}
|
||||||
|
ttl {args[1]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(header_realip) {
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
|
||||||
|
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
|
||||||
|
header_up X-Forwarded-Proto {http.request.header.CF-Visitor}
|
||||||
|
}
|
||||||
|
|
||||||
|
(rate_limit) {
|
||||||
|
route /* {
|
||||||
|
rate_limit {remote.ip} {args[0]}r/m 10000 429
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:80 {
|
||||||
|
reverse_proxy {
|
||||||
|
to h2c://127.0.0.1:8080
|
||||||
|
import header_realip
|
||||||
|
}
|
||||||
|
import log ghproxy
|
||||||
|
import error_page
|
||||||
|
import encode
|
||||||
|
import rate_limit 60
|
||||||
|
route / {
|
||||||
|
root /data/www
|
||||||
|
file_server
|
||||||
|
import cache 300s
|
||||||
|
}
|
||||||
|
route /favicon.ico {
|
||||||
|
root /data/www
|
||||||
|
file_server
|
||||||
|
import cache 300s
|
||||||
|
}
|
||||||
|
|
||||||
|
route /api* {
|
||||||
|
rate_limit {remote.ip} 15r/m 10000 429
|
||||||
|
import cache 300s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
import /data/caddy/config.d/*
|
||||||
49
docker/dockerfile/nocache/Dockerfile
Normal file
49
docker/dockerfile/nocache/Dockerfile
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
FROM wjqserver/caddy:2.9.0-rc-alpine AS builder
|
||||||
|
|
||||||
|
ARG USER=WJQSERVER-STUDIO
|
||||||
|
ARG REPO=ghproxy
|
||||||
|
ARG APPLICATION=ghproxy
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
|
||||||
|
# 创建文件夹
|
||||||
|
RUN mkdir -p /data/www
|
||||||
|
RUN mkdir -p /data/${APPLICATION}/config
|
||||||
|
RUN mkdir -p /data/${APPLICATION}/log
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
RUN apk add --no-cache curl wget
|
||||||
|
|
||||||
|
# 前端
|
||||||
|
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
|
||||||
|
RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/favicon.ico
|
||||||
|
|
||||||
|
# 后端
|
||||||
|
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}
|
||||||
|
RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/nocache/init.sh
|
||||||
|
|
||||||
|
# 拉取配置
|
||||||
|
RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/nocache/Caddyfile
|
||||||
|
RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/config.toml
|
||||||
|
RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/blacklist.json
|
||||||
|
RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/whitelist.json
|
||||||
|
|
||||||
|
# 权限
|
||||||
|
RUN chmod +x /data/${APPLICATION}/${APPLICATION}
|
||||||
|
RUN chmod +x /usr/local/bin/init.sh
|
||||||
|
|
||||||
|
FROM wjqserver/caddy:2.9.0-rc-alpine
|
||||||
|
|
||||||
|
COPY --from=builder /data/www /data/www
|
||||||
|
COPY --from=builder /data/caddy /data/caddy
|
||||||
|
COPY --from=builder /data/${APPLICATION} /data/${APPLICATION}
|
||||||
|
COPY --from=builder /usr/local/bin/init.sh /usr/local/bin/init.sh
|
||||||
|
|
||||||
|
# 权限
|
||||||
|
RUN chmod +x /data/${APPLICATION}/${APPLICATION}
|
||||||
|
RUN chmod +x /usr/local/bin/init.sh
|
||||||
|
|
||||||
|
CMD ["/usr/local/bin/init.sh"]
|
||||||
|
|
||||||
27
docker/dockerfile/nocache/init.sh
Normal file
27
docker/dockerfile/nocache/init.sh
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
APPLICATION=ghproxy
|
||||||
|
|
||||||
|
if [ ! -f /data/caddy/config/Caddyfile ]; then
|
||||||
|
cp /data/caddy/Caddyfile /data/caddy/config/Caddyfile
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /data/${APPLICATION}/config/blacklist.json ]; then
|
||||||
|
cp /data/${APPLICATION}/blacklist.json /data/${APPLICATION}/config/blacklist.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /data/${APPLICATION}/config/whitelist.json ]; then
|
||||||
|
cp /data/${APPLICATION}/whitelist.json /data/${APPLICATION}/config/whitelist.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f /data/${APPLICATION}/config/config.toml ]; then
|
||||||
|
cp /data/${APPLICATION}/config.toml /data/${APPLICATION}/config/config.toml
|
||||||
|
fi
|
||||||
|
|
||||||
|
/data/caddy/caddy run --config /data/caddy/config/Caddyfile > /data/${APPLICATION}/log/caddy.log 2>&1 &
|
||||||
|
|
||||||
|
/data/${APPLICATION}/${APPLICATION} -cfg /data/${APPLICATION}/config/config.toml > /data/${APPLICATION}/log/run.log 2>&1 &
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
@ -22,7 +22,7 @@ var (
|
||||||
logFilePath = "/data/ghproxy/log/ghproxy.log"
|
logFilePath = "/data/ghproxy/log/ghproxy.log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 初始化,接受日志文件路径作为参数
|
// 初始化
|
||||||
func Init(logFilePath_input string, maxLogsize int) error {
|
func Init(logFilePath_input string, maxLogsize int) error {
|
||||||
logFileMutex.Lock()
|
logFileMutex.Lock()
|
||||||
defer logFileMutex.Unlock()
|
defer logFileMutex.Unlock()
|
||||||
|
|
@ -121,7 +121,6 @@ func rotateLogFile(logFilePath string) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开当前日志文件
|
|
||||||
logFile, err := os.Open(logFilePath)
|
logFile, err := os.Open(logFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open log file: %s, error: %w", logFilePath, err)
|
return fmt.Errorf("failed to open log file: %s, error: %w", logFilePath, err)
|
||||||
|
|
@ -168,7 +167,6 @@ func rotateLogFile(logFilePath string) error {
|
||||||
return fmt.Errorf("failed to truncate log file: %s, error: %w", logFilePath, err)
|
return fmt.Errorf("failed to truncate log file: %s, error: %w", logFilePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新打开日志文件
|
|
||||||
logFile, err = os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
logFile, err = os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to reopen log file: %s, error: %w", logFilePath, err)
|
return fmt.Errorf("failed to reopen log file: %s, error: %w", logFilePath, err)
|
||||||
|
|
|
||||||
|
|
@ -94,10 +94,8 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath):
|
case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath):
|
||||||
logInfo("%s Matched - USE proxy-chrome", rawPath)
|
|
||||||
ProxyRequest(c, rawPath, cfg, "chrome")
|
ProxyRequest(c, rawPath, cfg, "chrome")
|
||||||
case exps[2].MatchString(rawPath):
|
case exps[2].MatchString(rawPath):
|
||||||
logInfo("%s Matched - USE proxy-git", rawPath)
|
|
||||||
ProxyRequest(c, rawPath, cfg, "git")
|
ProxyRequest(c, rawPath, cfg, "git")
|
||||||
default:
|
default:
|
||||||
c.String(http.StatusForbidden, "Invalid input.")
|
c.String(http.StatusForbidden, "Invalid input.")
|
||||||
|
|
@ -106,7 +104,7 @@ func NoRouteHandler(cfg *config.Config) gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提取用户名和仓库名,格式为 handle/<username>/<repo>/*
|
// 提取用户名和仓库名
|
||||||
func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) {
|
func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) {
|
||||||
var gistregex = regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.*`)
|
var gistregex = regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.*`)
|
||||||
var gistmatches []string
|
var gistmatches []string
|
||||||
|
|
@ -115,16 +113,16 @@ func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches [
|
||||||
logInfo("Gist Matched > Username: %s, URL: %s", gistmatches[1], rawPath)
|
logInfo("Gist Matched > Username: %s, URL: %s", gistmatches[1], rawPath)
|
||||||
return gistmatches[1], ""
|
return gistmatches[1], ""
|
||||||
}
|
}
|
||||||
pathmatches := regexp.MustCompile(`^([^/]+)/([^/]+)/([^/]+)/.*`)
|
// 定义路径匹配的正则表达式
|
||||||
pathParts := pathmatches.FindStringSubmatch(matches[2])
|
pathRegex := regexp.MustCompile(`^([^/]+)/([^/]+)/([^/]+)/.*`)
|
||||||
|
if pathMatches := pathRegex.FindStringSubmatch(matches[2]); len(pathMatches) >= 4 {
|
||||||
|
return pathMatches[2], pathMatches[3]
|
||||||
|
}
|
||||||
|
|
||||||
if len(pathParts) < 4 {
|
// 返回错误信息
|
||||||
logWarning("Invalid path: %s", rawPath)
|
logWarning("Invalid path: %s", rawPath)
|
||||||
c.String(http.StatusForbidden, "Invalid path; expected username/repo.")
|
c.String(http.StatusForbidden, "Invalid path; expected username/repo.")
|
||||||
return "", ""
|
return "", ""
|
||||||
} else {
|
|
||||||
return pathParts[2], pathParts[3]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string) {
|
func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string) {
|
||||||
|
|
@ -166,7 +164,7 @@ func createHTTPClient(mode string) *req.Client {
|
||||||
client := req.C()
|
client := req.C()
|
||||||
switch mode {
|
switch mode {
|
||||||
case "chrome":
|
case "chrome":
|
||||||
client.SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36").
|
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().
|
SetTLSFingerprintChrome().
|
||||||
ImpersonateChrome()
|
ImpersonateChrome()
|
||||||
case "git":
|
case "git":
|
||||||
|
|
@ -175,7 +173,7 @@ func createHTTPClient(mode string) *req.Client {
|
||||||
return client
|
return client
|
||||||
}
|
}
|
||||||
|
|
||||||
// readRequestBody 读取请求体
|
// 读取请求体
|
||||||
func readRequestBody(c *gin.Context) ([]byte, error) {
|
func readRequestBody(c *gin.Context) ([]byte, error) {
|
||||||
body, err := io.ReadAll(c.Request.Body)
|
body, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -185,7 +183,7 @@ func readRequestBody(c *gin.Context) ([]byte, error) {
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setRequestHeaders 设置请求头
|
// 设置请求头
|
||||||
func setRequestHeaders(c *gin.Context, req *req.Request) {
|
func setRequestHeaders(c *gin.Context, req *req.Request) {
|
||||||
for key, values := range c.Request.Header {
|
for key, values := range c.Request.Header {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
|
|
@ -194,7 +192,7 @@ func setRequestHeaders(c *gin.Context, req *req.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyResponseBody 复制响应体到客户端
|
// 复制响应体
|
||||||
func copyResponseBody(c *gin.Context, respBody io.Reader) error {
|
func copyResponseBody(c *gin.Context, respBody io.Reader) error {
|
||||||
_, err := io.Copy(c.Writer, respBody)
|
_, err := io.Copy(c.Writer, respBody)
|
||||||
return err
|
return err
|
||||||
|
|
@ -242,7 +240,7 @@ func CopyResponseHeaders(resp *req.Response, c *gin.Context, cfg *config.Config)
|
||||||
setDefaultHeaders(c)
|
setDefaultHeaders(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeHeaders 移除指定的响应头
|
// 移除指定响应头
|
||||||
func removeHeaders(resp *req.Response) {
|
func removeHeaders(resp *req.Response) {
|
||||||
headersToRemove := map[string]struct{}{
|
headersToRemove := map[string]struct{}{
|
||||||
"Content-Security-Policy": {},
|
"Content-Security-Policy": {},
|
||||||
|
|
@ -255,7 +253,7 @@ func removeHeaders(resp *req.Response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyHeaders 复制响应头到 Gin 上下文
|
// 复制响应头
|
||||||
func copyHeaders(resp *req.Response, c *gin.Context) {
|
func copyHeaders(resp *req.Response, c *gin.Context) {
|
||||||
for key, values := range resp.Header {
|
for key, values := range resp.Header {
|
||||||
for _, value := range values {
|
for _, value := range values {
|
||||||
|
|
@ -264,7 +262,7 @@ func copyHeaders(resp *req.Response, c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setCORSHeaders 设置 CORS 相关的响应头
|
// CORS配置
|
||||||
func setCORSHeaders(c *gin.Context, cfg *config.Config) {
|
func setCORSHeaders(c *gin.Context, cfg *config.Config) {
|
||||||
if cfg.CORS.Enabled {
|
if cfg.CORS.Enabled {
|
||||||
c.Header("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
|
@ -273,7 +271,7 @@ func setCORSHeaders(c *gin.Context, cfg *config.Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDefaultHeaders 设置默认的响应头
|
// 默认响应
|
||||||
func setDefaultHeaders(c *gin.Context) {
|
func setDefaultHeaders(c *gin.Context) {
|
||||||
c.Header("Age", "10")
|
c.Header("Age", "10")
|
||||||
c.Header("Cache-Control", "max-age=300")
|
c.Header("Cache-Control", "max-age=300")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue