├── .gitattributes ├── .github └── workflows │ └── docker-push.yaml ├── .gitignore ├── Dockerfile ├── README.md ├── config.json ├── config.json.dist ├── docker-entrypoint.sh ├── go.mod ├── go.sum ├── main.go └── public ├── favicon.ico ├── index.html └── logo.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/docker-push.yaml: -------------------------------------------------------------------------------- 1 | name: mtlogin Docker Push 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | # pull_request: 9 | # branches: 10 | # - main 11 | workflow_dispatch: 12 | 13 | env: 14 | REGISTRY: ghcr.io 15 | IMAGE_NAME: ${{ github.repository }} 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | packages: write 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - name: 获取分支名称 27 | id: branch-names 28 | uses: tj-actions/branch-names@v8 29 | 30 | - name: Set up QEMU 31 | uses: docker/setup-qemu-action@v3 32 | 33 | - name: Set up Docker Buildx 34 | uses: docker/setup-buildx-action@v3 35 | 36 | - name: Login to Github Registry 37 | uses: docker/login-action@v3 38 | if: github.event_name != 'pull_request' 39 | with: 40 | registry: ${{ env.REGISTRY }} 41 | username: ${{ github.actor }} 42 | password: ${{ secrets.GITHUB_TOKEN }} 43 | #password: ${{ secrets.GIT_TOKEN }} 44 | 45 | - name: Extract metadata (tags, labels) for Docker 46 | id: meta 47 | uses: docker/metadata-action@v5 48 | with: 49 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 50 | tags: | 51 | type=edge 52 | type=ref,event=branch 53 | type=sha,event=branch 54 | type=ref,event=tag 55 | 56 | 57 | - name: Build and push 58 | uses: docker/build-push-action@v5 59 | with: 60 | context: . 61 | file: "Dockerfile" 62 | push: ${{ github.event_name != 'pull_request' }} 63 | tags: ${{ steps.meta.outputs.tags }} 64 | labels: ${{ steps.meta.outputs.labels }} 65 | platforms: linux/amd64, linux/arm64, linux/arm 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /.idea 3 | buiid.bat 4 | main_linux 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.22-alpine AS builder 2 | RUN go env -w GO111MODULE=auto \ 3 | && go env -w CGO_ENABLED=0 4 | # && go env -w GOPROXY=https://goproxy.cn,direct 5 | 6 | WORKDIR /build 7 | 8 | COPY ./ . 9 | 10 | RUN set -ex \ 11 | && cd /build \ 12 | && go build -ldflags "-s -w -extldflags '-static'" -o gh-proxy 13 | 14 | FROM alpine:latest 15 | 16 | COPY docker-entrypoint.sh /docker-entrypoint.sh 17 | 18 | RUN chmod +x /docker-entrypoint.sh && \ 19 | apk add --no-cache --update \ 20 | coreutils \ 21 | shadow \ 22 | su-exec \ 23 | tzdata && \ 24 | rm -rf /var/cache/apk/* && \ 25 | mkdir -p /app && \ 26 | mkdir -p /config && \ 27 | useradd -d /config -s /bin/sh abc && \ 28 | chown -R abc /config 29 | 30 | ENV TZ="Asia/Shanghai" 31 | ENV UID=99 32 | ENV GID=100 33 | ENV UMASK=002 34 | ENV WHITE_LIST="" 35 | ENV BLACK_LIST="\"example3\",\"example4\"" 36 | ENV ALLOW_PROXY_ALL="false" 37 | ENV OTHER_WHITE_LIST="" 38 | ENV OTHER_BLACK_LIST="\"example3\",\"example4\"" 39 | ENV HTTP_HOST="" 40 | ENV HTTP_PORT="8080" 41 | ENV SIZE_LIMIT="10737418240" 42 | 43 | COPY --from=builder /build/gh-proxy /app/ 44 | COPY --from=builder /build/config.json.dist /app/ 45 | 46 | WORKDIR /app 47 | 48 | VOLUME [ "/app" ] 49 | 50 | ENTRYPOINT [ "/docker-entrypoint.sh" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gh-proxy-go 2 | 3 | 这是 [gh-proxy](https://github.com/hunshcn/gh-proxy) 的 Go 语言版本。 4 | 5 | ## 部署详情 6 | 7 | [github.moeyy.xyz](https://github.moeyy.xyz/) 正在使用 **gh-proxy-go**,托管在 [BuyVM](https://buyvm.net/) 每月 3.5 美元的 1 核 1G 内存、10Gbps 带宽服务器上。 8 | 9 | ### 服务器概况: 10 | 11 | - **日流量处理**:约 3TB 12 | - **CPU 平均使用率**:20% 13 | - **带宽平均占用**:400Mbps 14 | 15 | ![服务器数据](https://github.com/user-attachments/assets/6fe37f41-aa35-4efc-b0b8-8c3339529326) 16 | ![Cloudflare 数据](https://github.com/user-attachments/assets/ae310b1f-96e9-42e9-a77c-0d8c1b8d6344) 17 | 18 | --- 19 | 20 | 如有问题或改进建议,欢迎提交 issue 或 PR! 21 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "0.0.0.0", 3 | "port": 8080, 4 | "sizeLimit": 10737418240, 5 | "whiteList": [ 6 | ], 7 | "blackList": [ 8 | "example3", 9 | "example4" 10 | ], 11 | "AllowProxyAll": false, 12 | "otherWhiteList": [ 13 | ], 14 | "otherBlackList": [ 15 | "example3", 16 | "example4" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /config.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "host": "%HTTP_HOST%", 3 | "port": %HTTP_PORT%, 4 | "sizeLimit": %SIZE_LIMIT%, 5 | "whiteList": [ 6 | %WHITE_LIST% 7 | ], 8 | "blackList": [ 9 | %BLACK_LIST% 10 | ], 11 | "AllowProxyAll": %ALLOW_PROXY_ALL%, 12 | "otherWhiteList": [ 13 | %OTHER_WHITE_LIST% 14 | ], 15 | "otherBlackList": [ 16 | %OTHER_BLACK_LIST% 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | USER=abc 4 | 5 | echo "---Setup Timezone to ${TZ}---" 6 | echo "${TZ}" > /etc/timezone 7 | echo "---Checking if UID: ${UID} matches user---" 8 | usermod -o -u ${UID} ${USER} 9 | echo "---Checking if GID: ${GID} matches user---" 10 | groupmod -o -g ${GID} ${USER} > /dev/null 2>&1 ||: 11 | usermod -g ${GID} ${USER} 12 | echo "---Setting umask to ${UMASK}---" 13 | umask ${UMASK} 14 | 15 | echo "---Taking ownership of data...---" 16 | chown -R ${UID}:${GID} /app /data 17 | chmod +x /app/gh-proxy 18 | 19 | echo "make config" 20 | cp /app/config.json.dist /app/config.json 21 | sed -i "s|%WHITE_LIST%|$WHITE_LIST|g" /app/config.json 22 | sed -i "s|%BLACK_LIST%|$BLACK_LIST|g" /app/config.json 23 | sed -i "s|%ALLOW_PROXY_ALL%|$ALLOW_PROXY_ALL|g" /app/config.json 24 | sed -i "s|%OTHER_WHITE_LIST%|$OTHER_WHITE_LIST|g" /app/config.json 25 | sed -i "s|%OTHER_BLACK_LIST%|$OTHER_BLACK_LIST|g" /app/config.json 26 | sed -i "s|%HTTP_HOST%|$HTTP_HOST|g" /app/config.json 27 | sed -i "s|%HTTP_PORT%|$HTTP_PORT|g" /app/config.json 28 | sed -i "s|%SIZE_LIMIT%|$SIZE_LIMIT|g" /app/config.json 29 | 30 | echo "Starting..." 31 | su-exec ${USER} /app/gh-proxy "$@" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gh-proxy-go 2 | 3 | go 1.22.5 4 | 5 | require github.com/gin-gonic/gin v1.10.0 6 | 7 | require ( 8 | github.com/bytedance/sonic v1.11.6 // indirect 9 | github.com/bytedance/sonic/loader v0.1.1 // indirect 10 | github.com/cloudwego/base64x v0.1.4 // indirect 11 | github.com/cloudwego/iasm v0.2.0 // indirect 12 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 13 | github.com/gin-contrib/sse v0.1.0 // indirect 14 | github.com/go-playground/locales v0.14.1 // indirect 15 | github.com/go-playground/universal-translator v0.18.1 // indirect 16 | github.com/go-playground/validator/v10 v10.20.0 // indirect 17 | github.com/goccy/go-json v0.10.2 // indirect 18 | github.com/json-iterator/go v1.1.12 // indirect 19 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 20 | github.com/leodido/go-urn v1.4.0 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 23 | github.com/modern-go/reflect2 v1.0.2 // indirect 24 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 25 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 26 | github.com/ugorji/go/codec v1.2.12 // indirect 27 | golang.org/x/arch v0.8.0 // indirect 28 | golang.org/x/crypto v0.23.0 // indirect 29 | golang.org/x/net v0.25.0 // indirect 30 | golang.org/x/sys v0.20.0 // indirect 31 | golang.org/x/text v0.15.0 // indirect 32 | google.golang.org/protobuf v1.34.1 // indirect 33 | gopkg.in/yaml.v3 v3.0.1 // indirect 34 | ) 35 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= 2 | github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= 3 | github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= 4 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 5 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 6 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 7 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 8 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= 13 | github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= 14 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 15 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 16 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 17 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 18 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 19 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 20 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 21 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 22 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 23 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 24 | github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= 25 | github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 26 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 27 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 28 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 29 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 30 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 31 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 32 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 33 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 34 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= 35 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 36 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 37 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 38 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 39 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 40 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 41 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 44 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 45 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 46 | github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 47 | github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= 48 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 49 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 50 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 51 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 52 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 53 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 54 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 55 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 56 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 57 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 58 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 59 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 60 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 61 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 62 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 63 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 64 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 65 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 66 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 67 | golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= 68 | golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 69 | golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= 70 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 71 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 72 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 73 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 74 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 75 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 76 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 77 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= 78 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 79 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 80 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 81 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= 82 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 83 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 84 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 85 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 86 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 87 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 88 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 89 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 90 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "embed" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "io" 9 | "io/fs" 10 | "net" 11 | "net/http" 12 | "os" 13 | "regexp" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | "time" 18 | ) 19 | 20 | const ( 21 | sizeLimit int64 = 1024 * 1024 * 1024 * 10 // 允许的文件大小,默认10GB 22 | host = "0.0.0.0" // 监听地址 23 | port = 8080 // 监听端口 24 | ) 25 | 26 | //go:embed public/* 27 | var public embed.FS 28 | 29 | var ( 30 | exps = []*regexp.Regexp{ 31 | regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*$`), 32 | regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*$`), 33 | regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*$`), 34 | regexp.MustCompile(`^(?:https?://)?raw\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.+?/.+$`), 35 | regexp.MustCompile(`^(?:https?://)?gist\.github\.com/([^/]+)/.+?/.+$`), 36 | } 37 | httpClient *http.Client 38 | config *Config 39 | configLock sync.RWMutex 40 | ) 41 | 42 | type Config struct { 43 | Host string `json:"host"` 44 | Port int64 `json:"port"` 45 | SizeLimit int64 `json:"sizeLimit"` 46 | WhiteList []string `json:"whiteList"` 47 | BlackList []string `json:"blackList"` 48 | AllowProxyAll bool `json:"allowProxyAll"` // 是否允许代理非github的其他地址 49 | OtherWhiteList []string `json:"otherWhiteList"` 50 | OtherBlackList []string `json:"otherBlackList"` 51 | } 52 | 53 | func main() { 54 | gin.SetMode(gin.ReleaseMode) 55 | router := gin.Default() 56 | 57 | httpClient = &http.Client{ 58 | Transport: &http.Transport{ 59 | DialContext: (&net.Dialer{ 60 | Timeout: 30 * time.Second, 61 | KeepAlive: 30 * time.Second, 62 | }).DialContext, 63 | MaxIdleConns: 1000, 64 | MaxIdleConnsPerHost: 1000, 65 | IdleConnTimeout: 90 * time.Second, 66 | TLSHandshakeTimeout: 10 * time.Second, 67 | ExpectContinueTimeout: 1 * time.Second, 68 | ResponseHeaderTimeout: 300 * time.Second, 69 | }, 70 | } 71 | 72 | loadConfig() 73 | go func() { 74 | for { 75 | time.Sleep(10 * time.Minute) 76 | loadConfig() 77 | } 78 | }() 79 | // if config.Host==""{ 80 | // config.Host=host 81 | // } 82 | if config.Port == 0 { 83 | config.Port = port 84 | } 85 | if config.SizeLimit <= 0 { 86 | config.SizeLimit = sizeLimit 87 | } 88 | // 修改静态文件服务方式 89 | subFS, err := fs.Sub(public, "public") 90 | if err != nil { 91 | panic(fmt.Sprintf("无法创建子文件系统: %v", err)) 92 | } 93 | 94 | // 使用 StaticFS 提供静态文件 95 | router.StaticFS("/", http.FS(subFS)) 96 | // router.StaticFile("/", "./public/index.html") 97 | // router.StaticFile("/favicon.ico", "./public/favicon.ico") 98 | // router.StaticFile("/logo.png", "./public/logo.png") 99 | router.NoRoute(handler) 100 | fmt.Printf("starting http server on %s:%d \n", config.Host, config.Port) 101 | err = router.Run(fmt.Sprintf("%s:%d", config.Host, config.Port)) 102 | if err != nil { 103 | fmt.Printf("Error starting server: %v\n", err) 104 | } 105 | } 106 | 107 | func handler(c *gin.Context) { 108 | rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") 109 | 110 | for strings.HasPrefix(rawPath, "/") { 111 | rawPath = strings.TrimPrefix(rawPath, "/") 112 | } 113 | 114 | if !strings.HasPrefix(rawPath, "http") { 115 | // c.String(http.StatusForbidden, "Invalid input.") 116 | // return 117 | rawPath = fmt.Sprintf("https://%s", rawPath) 118 | } 119 | 120 | matches := checkURL(rawPath) 121 | if matches != nil { 122 | if len(config.WhiteList) > 0 && !checkList(matches, config.WhiteList) { 123 | c.String(http.StatusForbidden, "Forbidden by white list.") 124 | return 125 | } 126 | if len(config.BlackList) > 0 && checkList(matches, config.BlackList) { 127 | c.String(http.StatusForbidden, "Forbidden by black list.") 128 | return 129 | } 130 | } else { 131 | if !config.AllowProxyAll { 132 | c.String(http.StatusForbidden, "Invalid input.") 133 | return 134 | } 135 | if len(config.OtherWhiteList) > 0 && !checkOhterList(rawPath, config.OtherWhiteList) { 136 | c.String(http.StatusForbidden, "Forbidden by white list.") 137 | return 138 | } 139 | if len(config.OtherBlackList) > 0 && checkOhterList(rawPath, config.OtherBlackList) { 140 | c.String(http.StatusForbidden, "Forbidden by black list.") 141 | return 142 | } 143 | } 144 | 145 | if exps[1].MatchString(rawPath) { 146 | rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1) 147 | } 148 | 149 | proxy(c, rawPath) 150 | } 151 | 152 | func proxy(c *gin.Context, u string) { 153 | req, err := http.NewRequest(c.Request.Method, u, c.Request.Body) 154 | if err != nil { 155 | c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", err)) 156 | return 157 | } 158 | 159 | for key, values := range c.Request.Header { 160 | for _, value := range values { 161 | req.Header.Add(key, value) 162 | } 163 | } 164 | req.Header.Del("Host") 165 | 166 | resp, err := httpClient.Do(req) 167 | if err != nil { 168 | c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", err)) 169 | return 170 | } 171 | defer func(Body io.ReadCloser) { 172 | err := Body.Close() 173 | if err != nil { 174 | 175 | } 176 | }(resp.Body) 177 | 178 | if contentLength, ok := resp.Header["Content-Length"]; ok { 179 | if size, err := strconv.ParseInt(contentLength[0], 10, 64); err == nil && size > config.SizeLimit { 180 | c.String(http.StatusRequestEntityTooLarge, "File too large.") 181 | return 182 | } 183 | } 184 | 185 | resp.Header.Del("Content-Security-Policy") 186 | resp.Header.Del("Referrer-Policy") 187 | resp.Header.Del("Strict-Transport-Security") 188 | 189 | for key, values := range resp.Header { 190 | for _, value := range values { 191 | c.Header(key, value) 192 | } 193 | } 194 | 195 | if location := resp.Header.Get("Location"); location != "" { 196 | if checkURL(location) != nil { 197 | c.Header("Location", "/"+location) 198 | } else { 199 | proxy(c, location) 200 | return 201 | } 202 | } 203 | 204 | c.Status(resp.StatusCode) 205 | if _, err := io.Copy(c.Writer, resp.Body); err != nil { 206 | return 207 | } 208 | } 209 | 210 | func loadConfig() { 211 | file, err := os.Open("config.json") 212 | if err != nil { 213 | fmt.Printf("Error loading config: %v\n", err) 214 | return 215 | } 216 | defer func(file *os.File) { 217 | err := file.Close() 218 | if err != nil { 219 | 220 | } 221 | }(file) 222 | 223 | var newConfig Config 224 | decoder := json.NewDecoder(file) 225 | if err := decoder.Decode(&newConfig); err != nil { 226 | fmt.Printf("Error decoding config: %v\n", err) 227 | return 228 | } 229 | 230 | configLock.Lock() 231 | config = &newConfig 232 | configLock.Unlock() 233 | } 234 | 235 | func checkURL(u string) []string { 236 | for _, exp := range exps { 237 | if matches := exp.FindStringSubmatch(u); matches != nil { 238 | return matches[1:] 239 | } 240 | } 241 | return nil 242 | } 243 | 244 | func checkList(matches, list []string) bool { 245 | for _, item := range list { 246 | if strings.HasPrefix(matches[0], item) { 247 | return true 248 | } 249 | } 250 | return false 251 | } 252 | 253 | func checkOhterList(url string, list []string) bool { 254 | for _, item := range list { 255 | if strings.Contains(url, item) { 256 | return true 257 | } 258 | } 259 | return false 260 | } 261 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moeyy01/gh-proxy-go/098cf661a843186a7efff1ece400512d76ef5ce3/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | GitHub 文件加速 19 | 20 | 21 | 22 |

GitHub 文件加速

23 |
24 | 29 | 30 |

GitHub文件链接带不带协议头都可以,支持release、archive以及文件,右键复制出来的链接都是符合标准的。

31 |

注意,不支持项目文件夹,请使用Git。

32 |
33 |

分支源码:https://github.moeyy.xyz/https://github.com/moeyy/project/archive/master.zip

34 |

release源码:https://github.moeyy.xyz/https://github.com/moeyy/project/archive/v0.1.0.tar.gz

35 |

release文件:https://github.moeyy.xyz/https://github.com/moeyy/project/releases/download/v0.1.0/example.zip

36 |

分支文件:https://github.moeyy.xyz/https://github.com/moeyy/project/blob/master/filename

37 |

Raw:https://github.moeyy.xyz/https://raw.githubusercontent.com/moeyy/project/archive/master.zip

38 |

使用Git: git clone https://github.moeyy.xyz/https://github.com/moeyy/project

39 |
40 |
41 |

基于 hunshcn/gh-proxy 修改,使用Golang重构。开源于GitHub moeyy01/gh-proxy-go 42 |

43 | 44 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moeyy01/gh-proxy-go/098cf661a843186a7efff1ece400512d76ef5ce3/public/logo.png --------------------------------------------------------------------------------