This commit is contained in:
NewName
2024-12-22 19:26:44 +08:00
parent 5bbf7c744a
commit f76a13f4ee
12 changed files with 1000 additions and 233 deletions

View File

@@ -1,24 +1,23 @@
hub.{$DOMAIN} {
root * /srv
file_server
}
docker.{$DOMAIN} {
reverse_proxy * docker:5000
}
ghcr.{$DOMAIN} {
reverse_proxy * ghcr:5000
}
gcr.{$DOMAIN} {
reverse_proxy * gcr:5000
}
quay.{$DOMAIN} {
reverse_proxy * quay:5000
}
k8s.{$DOMAIN} {
reverse_proxy * k8s:5000
}
hub.{$DOMAIN} {
reverse_proxy * ghproxy:5000
}
docker.{$DOMAIN} {
reverse_proxy * docker:5000
}
ghcr.{$DOMAIN} {
reverse_proxy * ghcr:5000
}
gcr.{$DOMAIN} {
reverse_proxy * gcr:5000
}
quay.{$DOMAIN} {
reverse_proxy * quay:5000
}
k8s.{$DOMAIN} {
reverse_proxy * k8s:5000
}

View File

@@ -1,6 +1,7 @@
### Docker-proxy
### hub-proxy
- 使用`docker`一键部署多种仓库的镜像加速
- 支持github文件加速
- 简化搭建步骤
- 部署超级简单
- 自动配置HTTPS

View File

@@ -1,48 +1,55 @@
services:
caddy:
image: caddy:alpine
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./web/Caddyfile:/etc/caddy/Caddyfile
- ./web/index:/srv
environment:
- DOMAIN=example.com # 修改为你的根域名
restart: always
quay:
image: "registry:2.8.3"
container_name: "quay"
restart: "always"
volumes:
- "./quay/config.yml:/etc/docker/registry/config.yml"
ghcr:
image: "registry:2.8.3"
container_name: "ghcr"
restart: "always"
volumes:
- "./ghcr/config.yml:/etc/docker/registry/config.yml"
gcr:
image: "registry:2.8.3"
container_name: "gcr"
restart: "always"
volumes:
- "./gcr/config.yml:/etc/docker/registry/config.yml"
docker:
image: "registry:2.8.3"
container_name: "docker"
restart: "always"
volumes:
- "./docker/config.yml:/etc/docker/registry/config.yml"
k8s:
image: "registry:2.8.3"
container_name: "k8s"
restart: "always"
volumes:
- "./k8s/config.yml:/etc/docker/registry/config.yml"
services:
caddy:
image: caddy:alpine
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
environment:
- DOMAIN=example.com # 修改为你的根域名
restart: always
quay:
image: "registry:2.8.3"
container_name: "quay"
restart: "always"
volumes:
- "./quay/config.yml:/etc/docker/registry/config.yml"
ghcr:
image: "registry:2.8.3"
container_name: "ghcr"
restart: "always"
volumes:
- "./ghcr/config.yml:/etc/docker/registry/config.yml"
gcr:
image: "registry:2.8.3"
container_name: "gcr"
restart: "always"
volumes:
- "./gcr/config.yml:/etc/docker/registry/config.yml"
docker:
image: "registry:2.8.3"
container_name: "docker"
restart: "always"
volumes:
- "./docker/config.yml:/etc/docker/registry/config.yml"
k8s:
image: "registry:2.8.3"
container_name: "k8s"
restart: "always"
volumes:
- "./k8s/config.yml:/etc/docker/registry/config.yml"
ghproxy:
build:
context: ./ghproxy
container_name: "ghproxy"
volumes:
- ./ghproxy/public:/root/public # 前端页面
restart: "always"

18
ghproxy/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM golang:1.22.5-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
FROM alpine:3.20
WORKDIR /root/
COPY --from=builder /app/main .
COPY --from=builder /app/config.json .
COPY --from=builder /app/public ./public
CMD ["./main"]

8
ghproxy/config.json Normal file
View File

@@ -0,0 +1,8 @@
{
"whiteList": [
],
"blackList": [
"example3",
"example4"
]
}

34
ghproxy/go.mod Normal file
View File

@@ -0,0 +1,34 @@
module ghproxy
go 1.22.5
require github.com/gin-gonic/gin v1.10.0
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

89
ghproxy/go.sum Normal file
View File

@@ -0,0 +1,89 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

212
ghproxy/main.go Normal file
View File

@@ -0,0 +1,212 @@
package main
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"io"
"net"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
)
const (
sizeLimit = 1024 * 1024 * 1024 * 10 // 允许的文件大小默认10GB
host = "0.0.0.0" // 监听地址
port = 5000 // 监听端口
)
var (
exps = []*regexp.Regexp{
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*$`),
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*$`),
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*$`),
regexp.MustCompile(`^(?:https?://)?raw\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.+?/.+$`),
regexp.MustCompile(`^(?:https?://)?gist\.github\.com/([^/]+)/.+?/.+$`),
}
httpClient *http.Client
config *Config
configLock sync.RWMutex
)
type Config struct {
WhiteList []string `json:"whiteList"`
BlackList []string `json:"blackList"`
}
func main() {
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
httpClient = &http.Client{
Transport: &http.Transport{
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
MaxIdleConns: 1000,
MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 300 * time.Second,
},
}
loadConfig()
go func() {
for {
time.Sleep(10 * time.Minute)
loadConfig()
}
}()
router.StaticFile("/", "./public/index.html")
router.StaticFile("/favicon.ico", "./public/favicon.ico")
router.NoRoute(handler)
err := router.Run(fmt.Sprintf("%s:%d", host, port))
if err != nil {
fmt.Printf("Error starting server: %v\n", err)
}
}
func handler(c *gin.Context) {
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/")
for strings.HasPrefix(rawPath, "/") {
rawPath = strings.TrimPrefix(rawPath, "/")
}
if !strings.HasPrefix(rawPath, "http") {
c.String(http.StatusForbidden, "Invalid input.")
return
}
matches := checkURL(rawPath)
if matches != nil {
if len(config.WhiteList) > 0 && !checkList(matches, config.WhiteList) {
c.String(http.StatusForbidden, "Forbidden by white list.")
return
}
if len(config.BlackList) > 0 && checkList(matches, config.BlackList) {
c.String(http.StatusForbidden, "Forbidden by black list.")
return
}
} else {
c.String(http.StatusForbidden, "Invalid input.")
return
}
if exps[1].MatchString(rawPath) {
rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1)
}
proxy(c, rawPath)
}
func proxy(c *gin.Context, u string) {
req, err := http.NewRequest(c.Request.Method, u, c.Request.Body)
if err != nil {
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", err))
return
}
for key, values := range c.Request.Header {
for _, value := range values {
req.Header.Add(key, value)
}
}
req.Header.Del("Host")
resp, err := httpClient.Do(req)
if err != nil {
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", err))
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
}
}(resp.Body)
if contentLength, ok := resp.Header["Content-Length"]; ok {
if size, err := strconv.Atoi(contentLength[0]); err == nil && size > sizeLimit {
c.String(http.StatusRequestEntityTooLarge, "File too large.")
return
}
}
resp.Header.Del("Content-Security-Policy")
resp.Header.Del("Referrer-Policy")
resp.Header.Del("Strict-Transport-Security")
for key, values := range resp.Header {
for _, value := range values {
c.Header(key, value)
}
}
if location := resp.Header.Get("Location"); location != "" {
if checkURL(location) != nil {
c.Header("Location", "/"+location)
} else {
proxy(c, location)
return
}
}
c.Status(resp.StatusCode)
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
return
}
}
func loadConfig() {
file, err := os.Open("config.json")
if err != nil {
fmt.Printf("Error loading config: %v\n", err)
return
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
}
}(file)
var newConfig Config
decoder := json.NewDecoder(file)
if err := decoder.Decode(&newConfig); err != nil {
fmt.Printf("Error decoding config: %v\n", err)
return
}
configLock.Lock()
config = &newConfig
configLock.Unlock()
}
func checkURL(u string) []string {
for _, exp := range exps {
if matches := exp.FindStringSubmatch(u); matches != nil {
return matches[1:]
}
}
return nil
}
func checkList(matches, list []string) bool {
for _, item := range list {
if strings.HasPrefix(matches[0], item) {
return true
}
}
return false
}

BIN
ghproxy/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

559
ghproxy/public/index.html Normal file
View File

@@ -0,0 +1,559 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Github文件加速,docker镜像加速">
<meta name="keywords" content="Github,文件加速,ghproxy,docker镜像加速">
<meta name="color-scheme" content="dark light">
<title>Github文件加速</title>
<link rel="icon" href="./favicon.ico">
<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">
<style>
:root {
--color: #ffffff;
--fontcolor: #333;
--inputcolor: #f5f5f5;
--inputcolor-font: #333;
}
@media (prefers-color-scheme: dark) {
:root {
--color: #53535338;
--fontcolor: #b8b8b8;
--inputcolor: #012333;
--inputcolor-font: #969696d8;
}
}
body {
background-color: var(--color);
color: var(--fontcolor);
font-family: 'Misans', Arial, sans-serif;
padding: 30px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
min-height: 100vh;
margin: 0;
position: relative;
}
*::-webkit-scrollbar {
height: 10px;
margin-top: 0px;
}
*::-webkit-scrollbar-track {
background-color: black;
}
*::-webkit-scrollbar-thumb {
background: #39c5bb;
border-radius: 10px;
}
.container {
max-width: 80%;
text-align: center;
min-height: 65%;
line-height: 1.25;
}
h1 {
color: var(--fontcolor);
font-weight: bold;
margin-bottom: 20%;
}
.rounded-button {
border-radius: 6px;
transition: background-color 0.3s, transform 0.2s;
padding: 10px 30px;
background-color: #555c5c;
color: rgb(255, 255, 255);
border: none;
margin-bottom: 3%;
}
.rounded-button:hover {
background-color: #39c5bcda;
transform: scale(1.05);
}
.tips>p:first-child::before {
position: sticky;
color: #7b7b7b;
margin-bottom: 1%;
font-size: 60%;
}
footer {
line-height: 1.25;
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
font-size: 1rem;
}
pre {
background: #012333;
color: #39c5bc;
padding: 15px 20px 15px 20px;
margin: 0px 0;
border-radius: 0.5rem;
overflow-x: auto;
position: relative;
}
pre::before {
content: " ";
display: block;
position: absolute;
top: 6px;
left: 6px;
width: 10px;
height: 10px;
background: #bd3c35;
border-radius: 50%;
box-shadow: 20px 0 0 #d69f27, 40px 0 0 #39c5bb;
}
code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 0.9em;
margin-bottom: 0px;
}
@media (max-width: 768px) {
footer {
font-size: 0.85rem;
}
.container {
max-width: 100%;
font-size: 0.8rem;
}
.tips {
font-size: 0.8rem;
}
.tips-content {
font-size: 0.8rem;
}
.status-container {
font-size: 0.8rem;
}
}
@media (min-width: 768px) {
footer {
font-size: 1rem;
}
.container {
max-width: 65%;
font-size: 1rem;
}
.tips {
font-size: 1.1rem;
}
.tips-content {
font-size: 1.1rem;
}
.status-container {
font-size: 1.05rem;
}
h1 {
margin-bottom: 10%;
}
}
.form-group {
margin-bottom: 3%;
}
.form-control {
background-color: var(--inputcolor);
color: var(--inputcolor-font);
}
.form-control:focus {
background-color: var(--inputcolor);
color: var(--inputcolor-font);
}
.tips-content {
margin-bottom: 0px;
margin-top: 20px; /* 将提示文本往下移动两行左右 */
}
.status-container {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 1px;
margin-top: -2%;
}
.code {
position: relative;
padding-right: 0px;
}
.copy-button {
position: absolute;
top: 10px;
right: 10px;
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;
}
.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 {
opacity: 1;
}
#visitor-info {
margin-top: 10px;
text-align: center;
line-height: 0;
}
#toast {
position: fixed;
top: 10%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #39c5bcde;
color: white;
padding: 15px 20px;
border-radius: 10px;
font-size: 90%;
z-index: 1000;
}
.docker-button {
width: 200px;
height: 50px;
border-radius: 10px;
background-color: #f6f6f6;
border: 2px solid #ececec;
color: #333;
font-size: 16px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 20px;
}
.docker-button:hover {
background-color: #39c5bc;
color: white;
transform: scale(1.05);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: relative;
background-color: #fff;
margin: 10% auto;
padding: 30px;
width: 80%;
max-width: 600px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
text-align: center;
}
.close-button {
position: absolute;
right: 20px;
top: 15px;
font-size: 24px;
cursor: pointer;
color: #666;
}
.domain-container {
margin-top: 30px;
text-align: center;
}
.domain-item {
display: flex;
justify-content: center;
gap: 20px;
align-items: center;
padding: 15px;
margin: 10px 0;
background-color: #f5f5f5;
border-radius: 8px;
transition: all 0.3s ease;
}
.domain-item:hover {
background-color: #e9e9e9;
}
.domain-text {
font-family: 'Consolas', monospace;
color: #333;
font-size: 16px;
text-align: center;
}
.domain-item .copy-button {
background-color: #39c5bc;
color: white;
border: none;
padding: 8px 15px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s ease;
}
.domain-item .copy-button:hover {
background-color: #2ea89f;
transform: scale(1.05);
}
.modal h1 {
color: #333;
font-size: 24px;
margin-bottom: 20px;
text-align: center;
}
.hosts-button {
position: fixed;
top: 20px;
right: 20px;
padding: 4px 6px;
background-color: #f5f5f5;
border: 2px solid #eeeeee;
color: #333;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 14px;
text-decoration: none;
}
.hosts-button:hover {
background-color: #39c5bc;
color: white;
transform: scale(1.05);
text-decoration: none;
}
</style>
</head>
<body>
<a href="https://gitee.com/if-the-wind/github-hosts/raw/main/hosts" target="_blank" class="hosts-button">hosts</a>
<div class="container">
<h1>Github文件加速</h1>
<div class="form-group">
<input type="text" class="form-control" id="githubLinkInput" placeholder="键入需要加速Github链接">
</div>
<button class="btn rounded-button" id="formatButton">获取加速链接</button>
<div class="code" id="outputBlock">
<button class="copy-button" id="copyButton">复制</button>
<button class="redir-button" id="redirButton">打开</button>
<pre id="formattedLinkOutput"></pre>
</div>
<div class="tips">
<div class="tips-content">
<p>支持release、archive文件支持git clone、wget、curl等等操作转换后的链接可直接使用</p><br>
</div>
</div>
<button class="docker-button" id="dockerButton">Docker镜像加速</button>
</div>
<div id="dockerModal" class="modal">
<div class="modal-content">
<span class="close-button" id="closeModal">&times;</span>
<h1>Docker 镜像加速</h1>
<h5>请根据对应的仓库使用对应的加速域名</h5>
<div class="domain-container">
<div class="domain-item">
<div class="domain-text">docker.<span class="domain-base"></span></div>
<button class="copy-button" onclick="copyDomain(this)">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">ghcr.<span class="domain-base"></span></div>
<button class="copy-button" onclick="copyDomain(this)">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">gcr.<span class="domain-base"></span></div>
<button class="copy-button" onclick="copyDomain(this)">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">quay.<span class="domain-base"></span></div>
<button class="copy-button" onclick="copyDomain(this)">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">k8s.<span class="domain-base"></span></div>
<button class="copy-button" onclick="copyDomain(this)">复制</button>
</div>
</div>
<div id="modal-toast" style="display:none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #39c5bcde; color: white; padding: 15px 20px; border-radius: 10px; font-size: 90%; z-index: 1001;">
域名已复制到剪贴板
</div>
</div>
</div>
<div id="toast" style="display:none;">
链接已复制到剪贴板
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
function getRootDomain() {
let hostname = window.location.hostname;
let parts = hostname.split('.');
if(parts.length > 2) {
return parts.slice(-2).join('.');
}
return hostname;
}
let rootDomain = getRootDomain();
document.querySelectorAll('.domain-base').forEach(span => {
span.textContent = rootDomain;
});
const modal = document.getElementById('dockerModal');
const dockerButton = document.getElementById('dockerButton');
const closeButton = document.getElementById('closeModal');
dockerButton.onclick = function() {
modal.style.display = "block";
}
closeButton.onclick = function() {
modal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
});
function formatGithubLink() {
var githubLinkInput = document.getElementById('githubLinkInput');
var currentHost = window.location.host;
var formattedLink = "";
var link = githubLinkInput.value.trim();
if (link.startsWith("https://") || link.startsWith("http://")) {
formattedLink = "https://" + currentHost + "/" + link;
} else if (link.startsWith("github.com/") || link.startsWith("raw.githubusercontent.com/") || link.startsWith("gist.githubusercontent.com/")) {
formattedLink = "https://" + currentHost + "/https://" + link;
} else {
showToast('请输入有效的GitHub链接');
return;
}
var formattedLinkOutput = document.getElementById('formattedLinkOutput');
formattedLinkOutput.textContent = formattedLink;
displayButton();
}
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('copyButton').addEventListener('click', function () {
const output = document.getElementById('formattedLinkOutput');
const range = document.createRange();
range.selectNode(output);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
document.execCommand('copy');
window.getSelection().removeAllRanges();
showToast('链接已复制到剪贴板');
});
document.getElementById('redirButton').addEventListener('click', redirToFormattedLink);
function showToast(message) {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.style.display = 'block';
setTimeout(() => {
toast.style.display = 'none';
}, 3000);
}
function copyDomain(button) {
const domainText = button.parentElement.querySelector('.domain-text').textContent;
navigator.clipboard.writeText(domainText).then(() => {
const modalToast = document.getElementById('modal-toast');
modalToast.style.display = 'block';
setTimeout(() => {
modalToast.style.display = 'none';
}, 2000);
});
}
</script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

View File

@@ -1,160 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docker 镜像加速</title>
<link rel="icon" href="./favicon.ico">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0/dist/themes/light.css" />
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.0.0/dist/shoelace.js"></script>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background-color: #f4f4f4;
}
h1 {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 1rem;
}
.domain-container {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 2rem;
}
.domain-item {
display: flex;
align-items: center;
justify-content: center;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
padding: 1rem 2rem;
margin-bottom: 1rem;
width: 300px;
}
.domain-text {
flex-grow: 1;
font-size: 1.2rem;
font-weight: bold;
}
.copy-button {
font-size: 1rem;
padding: 0.2rem 0.5rem;
margin-left: 0.5rem;
color: #393e42;
background-color: transparent;
border: none;
cursor: pointer;
}
.copy-button:focus {
outline: none;
}
.footer {
font-size: 0.9rem;
color: #555;
}
.github-icon {
position: fixed;
bottom: 15px;
text-align: center;
width: 100%;
}
.github-icon a {
color: #333;
text-decoration: none;
}
.github-icon a:hover {
color: #555;
}
</style>
</head>
<body>
<!-- 添加标题 -->
<h1>Docker 镜像加速(多种仓库)</h1>
<div class="domain-container">
<div class="domain-item">
<div class="domain-text">docker.<span id="domain-base"></span></div>
<button class="copy-button">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">ghcr.<span id="domain-base"></span></div>
<button class="copy-button">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">gcr.<span id="domain-base"></span></div>
<button class="copy-button">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">quay.<span id="domain-base"></span></div>
<button class="copy-button">复制</button>
</div>
<div class="domain-item">
<div class="domain-text">k8s.<span id="domain-base"></span></div>
<button class="copy-button">复制</button>
</div>
</div>
<div class="footer">
🔔个人使用,请勿分享!
</div>
<div class="footer">
✨根据对应的仓库使用对应的加速域名。
</div>
<div class="footer">
🎈Docker官方镜像请注意用户名替换为library
</div>
<div class="github-icon">
<a href="https://github.com/sky22333/docker-proxy" target="_blank">
<sl-icon name="github" size="24"></sl-icon>
</a>
</div>
<script>
const domainBase = new URL(window.location.href).hostname.replace(/^(www\.|hub\.)/i, '');
document.querySelectorAll('#domain-base').forEach(el => {
el.textContent = domainBase;
});
document.querySelectorAll('.copy-button').forEach(button => {
button.addEventListener('click', async () => {
const domainText = button.previousElementSibling.textContent;
try {
await navigator.clipboard.writeText(domainText);
if (button.dataset.copied) return;
button.dataset.copied = true;
const originalText = button.textContent;
button.textContent = '已复制';
setTimeout(() => {
button.textContent = originalText;
delete button.dataset.copied;
}, 1000);
} catch (err) {
console.error('复制失败', err);
}
});
});
</script>
</body>
</html>