1
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
### Docker-proxy
|
||||
### hub-proxy
|
||||
|
||||
- 使用`docker`一键部署多种仓库的镜像加速
|
||||
- 支持github文件加速
|
||||
- 简化搭建步骤
|
||||
- 部署超级简单
|
||||
- 自动配置HTTPS
|
||||
|
||||
@@ -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
18
ghproxy/Dockerfile
Normal 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
8
ghproxy/config.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"whiteList": [
|
||||
],
|
||||
"blackList": [
|
||||
"example3",
|
||||
"example4"
|
||||
]
|
||||
}
|
||||
34
ghproxy/go.mod
Normal file
34
ghproxy/go.mod
Normal 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
89
ghproxy/go.sum
Normal 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
212
ghproxy/main.go
Normal 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
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
559
ghproxy/public/index.html
Normal 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">×</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 |
@@ -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>
|
||||
Reference in New Issue
Block a user