This commit is contained in:
WJQSERVER 2024-09-24 17:16:21 +08:00
parent c21149b17f
commit 749093c8d5
20 changed files with 1282 additions and 1 deletions

77
.github/workflows/build-dev.yml vendored Normal file
View file

@ -0,0 +1,77 @@
name: Build Dev
on:
workflow_dispatch:
push:
branches:
- 'main'
paths:
- 'DEV-VERSION'
jobs:
build:
runs-on: ubuntu-latest
env:
OUTPUT_BINARY: ghproxy
OUTPUT_ARCHIVE: ghproxy.tar.gz
GO_VERSION: 1.23.1
steps:
- uses: actions/checkout@v3
- name: Load VERSION
run: |
if [ -f DEV-VERSION ]; then
echo "VERSION=$(cat DEV-VERSION)" >> $GITHUB_ENV
else
echo "DEV-VERSION file not found!" && exit 1
fi
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Build
run: |
go build -o ${{ env.OUTPUT_BINARY }} ./main.go
- name: Package
run: |
tar -czvf ${{ env.OUTPUT_ARCHIVE }} ./${{ env.OUTPUT_BINARY }}
- name: Upload to GitHub Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.OUTPUT_BINARY }}
path: |
./${{ env.OUTPUT_ARCHIVE }}
./${{ env.OUTPUT_BINARY }}
docker:
runs-on: ubuntu-latest
needs: build
env:
IMAGE_NAME: wjqserver/ghproxy-test
DOCKERFILE: docker/dockerfile/dev/Dockerfile
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 构建镜像
uses: docker/build-push-action@v5
with:
file: ./${{ env.DOCKERFILE }}
platforms: linux/amd64
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ env.VERSION }}
${{ env.IMAGE_NAME }}:dev

77
.github/workflows/build.yml vendored Normal file
View file

@ -0,0 +1,77 @@
name: Build
on:
workflow_dispatch:
push:
branches:
- 'main'
paths:
- 'VERSION'
jobs:
build:
runs-on: ubuntu-latest
env:
OUTPUT_BINARY: ghproxy
OUTPUT_ARCHIVE: ghproxy.tar.gz
GO_VERSION: 1.23.1
steps:
- uses: actions/checkout@v3
- name: Load VERSION
run: |
if [ -f VERSION ]; then
echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
else
echo "VERSION file not found!" && exit 1
fi
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: ${{ env.GO_VERSION }}
- name: Build
run: |
go build -o ${{ env.OUTPUT_BINARY }} ./main.go
- name: Package
run: |
tar -czvf ${{ env.OUTPUT_ARCHIVE }} ./${{ env.OUTPUT_BINARY }}
- name: Upload to GitHub Artifacts
uses: actions/upload-artifact@v3
with:
name: ${{ env.OUTPUT_BINARY }}
path: |
./${{ env.OUTPUT_ARCHIVE }}
./${{ env.OUTPUT_BINARY }}
docker:
runs-on: ubuntu-latest
needs: build # 确保这个作业在 build 作业完成后运行
env:
IMAGE_NAME: wjqserver/ghproxy-test # 定义镜像名称变量
DOCKERFILE: docker/dockerfile/release/Dockerfile
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: 构建镜像
uses: docker/build-push-action@v5
with:
file: ./Dockerfile
platforms: linux/amd64
push: true
tags: |
${{ env.IMAGE_NAME }}:${{ env.VERSION }}
${{ env.IMAGE_NAME }}:latest

1
DEV-VERSION Normal file
View file

@ -0,0 +1 @@
24w01a

107
LICENSE Normal file
View file

@ -0,0 +1,107 @@
WJQserver Studio 开源许可证
版本 1.2
版权所有 © WJQserver Studio 2024
定义
许可:指在本许可证内定义的使用、复制、分发与修改的条款与要求。
授权方:指拥有版权的个人或组织,亦或是拥有版权的个人或组织所指派的实体。
您:指行使本许可授予的权限的个人或法律实体。
开源与自由软件
本项目为开源软件,允许用户在遵循本许可证的前提下访问和使用源代码。
本项目不等同于自由软件,使用权限受到本许可证条款的限制。
强调版权所有,所有权利均由 WJQserver Studio 保留。
许可证条款
1. 使用权限
1.1 您被授予在私人环境中自由使用本软件的权限。
1.2 您可以在不修改关键声明的前提下进行商用。
2. 复制与分发
2.1 您可以复制和分发本软件的原始版本,前提是必须保留所有版权声明和本许可证。
3. 修改权限
3.1 您可以在非商业用途下修改本软件,前提是继承本许可证并保留原版权声明。
3.2 禁止在修改后进行商业用途。
4. 专利引用
4.1 若项目被专利相关引用,必须保留来源声明。
4.2 若为商业场景,需按照商用处理。
5. 免责声明
5.1 本软件按“现状”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途适用性及非侵权性。
5.2 在任何情况下,授权方均不对因使用或无法使用本软件而产生的任何直接、间接、偶然、特殊、惩罚性或后果性损害负责,即使已被告知可能发生此类损害。
5.3 用户需根据当地法律对待本项目,确保遵守所有适用法规。
6. 许可证期限
6.1 本许可证自2024年开始生效有效期暂为无限。
6.2 项目所有方有权修改许可证相关条例而不另行通知。
条款修订
7.1 授权方保留随时修改本许可证条款的权利,以便更好地适应法律和技术的发展。
7.2 修订后的条款将在发布时生效,继续使用本软件即表示接受修订后的条款。
其他
8.1 本许可证不影响您作为最终用户的法定权利。
8.2 若本许可证的某些条款被认定为不可执行,其余条款仍然有效。
WJQserver Studio Open Source License
Version 1.2
Copyright © WJQserver Studio 2024
Definitions
License: The terms and conditions defined within this license for use, copying, distribution, and modification.
Licensor: The individual or organization holding the copyright, or the entity designated by them.
You: The individual or legal entity exercising the permissions granted by this license.
Open Source vs. Free Software
This project is open source, allowing users to access and use the source code under the terms of this license.
This project is not equivalent to free software; usage rights are restricted by this license.
Copyright is emphasized, with all rights reserved by WJQserver Studio.
License Terms
1. Usage Rights
1.1 You are granted the right to use this software freely in a private environment.
1.2 You may use it commercially without modifying key statements.
2. Copying and Distribution
2.1 You may copy and distribute the original version of this software, provided all copyright notices and this license are retained.
3. Modification Rights
3.1 You may modify this software for non-commercial purposes, provided you inherit this license and retain the original copyright notice.
3.2 Modifications cannot be used commercially.
4. Patent References
4.1 If the project is cited in patent-related contexts, the source statement must be retained.
4.2 For commercial scenarios, it must be treated as a commercial use.
5. Disclaimer
5.1 This software is provided "as is", without any express or implied warranties, including but not limited to merchantability, fitness for a particular purpose, and non-infringement.
5.2 In no event shall the licensor be liable for any direct, indirect, incidental, special, punitive, or consequential damages arising out of the use or inability to use this software, even if advised of the possibility of such damages.
5.3 Users must comply with all applicable laws regarding this project.
6. License Duration
6.1 This license is effective from 2024, with an indefinite duration.
6.2 The project owner reserves the right to modify the license terms without prior notice.
Amendments
7.1 The licensor reserves the right to amend this license at any time to better adapt to legal and technological developments.
7.2 Revised terms become effective upon publication, and continued use of the software indicates acceptance of the revised terms.
Miscellaneous
8.1 This license does not affect your statutory rights as an end user.
8.2 If any provision of this license is held to be unenforceable, the remaining provisions shall remain in effect.

View file

@ -1 +1 @@
# ghproxy # golang-temp

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.0.1

99
caddyfile/dev/Caddyfile Normal file
View file

@ -0,0 +1,99 @@
{
debug
http_port 80
https_port 443
order cache before rewrite
cache {
cache_name W-Cache
}
log {
level INFO
output file /data/caddy/log/caddy.log {
roll_size 5MB
roll_keep 10
}
}
}
(log) {
log {
format transform `{request>headers>X-Forwarded-For>[0]:request>remote_ip} - {user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}"` {
time_format "02/Jan/2006:15:04:05 -0700"
}
output file /data/caddy/log/{args.0}/access.log {
roll_size 5MB
roll_keep 10
roll_keep_for 24h
}
}
}
(error_page) {
handle_errors {
rewrite * /{err.status_code}.html
root * /data/caddy/pages/errors
file_server
}
}
(encode) {
encode {
zstd best
br 5 v2
gzip 5
minimum_length 512
}
}
(cache) {
cache {
allowed_http_verbs GET
stale {args.0}
ttl {args.1}
}
}
(header_realip) {
header_up X-Real-IP {remote_host}
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
header_up X-Forwarded-Proto {http.request.header.CF-Visitor}
}
(buffer) {
flush_interval 2000s
buffer_responses
max_buffer_size 256k
}
(rate_limit) {
route /* {
rate_limit {remote.ip} {args.0}r/m 10000 429
}
}
:80 {
reverse_proxy {
to 127.0.0.1:8080
import header_realip
}
import log go
import cache 0s 600s
import error_page
import encode
route /* {
rate_limit {remote.ip} 60r/m 10000 429
}
route / {
root /data/www
file_server
import cache 0s 24h
}
route /favicon.ico {
root /data/www
file_server
import cache 60s 24h
}
}
import /data/caddy/config.d/*

View file

@ -0,0 +1,99 @@
{
debug
http_port 80
https_port 443
order cache before rewrite
cache {
cache_name W-Cache
}
log {
level INFO
output file /data/caddy/log/caddy.log {
roll_size 5MB
roll_keep 10
}
}
}
(log) {
log {
format transform `{request>headers>X-Forwarded-For>[0]:request>remote_ip} - {user_id} [{ts}] "{request>method} {request>uri} {request>proto}" {status} {size} "{request>headers>Referer>[0]}" "{request>headers>User-Agent>[0]}"` {
time_format "02/Jan/2006:15:04:05 -0700"
}
output file /data/caddy/log/{args.0}/access.log {
roll_size 5MB
roll_keep 10
roll_keep_for 24h
}
}
}
(error_page) {
handle_errors {
rewrite * /{err.status_code}.html
root * /data/caddy/pages/errors
file_server
}
}
(encode) {
encode {
zstd best
br 5 v2
gzip 5
minimum_length 512
}
}
(cache) {
cache {
allowed_http_verbs GET
stale {args.0}
ttl {args.1}
}
}
(header_realip) {
header_up X-Real-IP {remote_host}
header_up X-Real-IP {http.request.header.CF-Connecting-IP}
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
header_up X-Forwarded-Proto {http.request.header.CF-Visitor}
}
(buffer) {
flush_interval 2000s
buffer_responses
max_buffer_size 256k
}
(rate_limit) {
route /* {
rate_limit {remote.ip} {args.0}r/m 10000 429
}
}
:80 {
reverse_proxy {
to 127.0.0.1:8080
import header_realip
}
import log go
import cache 0s 600s
import error_page
import encode
route /* {
rate_limit {remote.ip} 60r/m 10000 429
}
route / {
root /data/www
file_server
import cache 0s 24h
}
route /favicon.ico {
root /data/www
file_server
import cache 60s 24h
}
}
import /data/caddy/config.d/*

31
config/config.go Normal file
View file

@ -0,0 +1,31 @@
package config
import (
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Port int `yaml:"port"`
Host string `yaml:"host"`
SizeLimit int `yaml:"sizelimit"`
LogFilePath string `yaml:"logfilepath"`
CORSOrigin bool `yaml:"CorsAllowOrigins"`
Auth bool `yaml:"auth"`
AuthToken string `yaml:"authtoken"`
}
// LoadConfig 从 YAML 配置文件加载配置
func LoadConfig(filePath string) (*Config, error) {
var config Config
data, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(data, &config)
if err != nil {
return nil, err
}
return &config, nil
}

7
config/config.yaml Normal file
View file

@ -0,0 +1,7 @@
port: 8080
host: "127.0.0.1"
sizelimit: 131072000 # 125MB
logfilepath: "/data/ghproxy/log/ghproxy-0rtt.log"
CorsAllowOrigins: true
auth: true
authtoken: "test"

View file

@ -0,0 +1,11 @@
version: '3.9'
services:
ghproxy:
image: 'wjqserver/ghproxy:latest'
restart: always
volumes:
- './ghproxy/log/run:/data/ghproxy/log'
- './ghproxy/log/caddy:/data/caddy/log'
- './ghproxy/config:/data/ghproxy/config'
ports:
- '7210:80'

View file

@ -0,0 +1,21 @@
FROM wjqserver/caddy:daily
ARG USER=WJQSERVER-STUDIO
ARG REPO=ghproxy
ARG APPLICATION=ghproxy
RUN mkdir -p /data/www
RUN mkdir -p /data/${APPLICATION}/config
RUN mkdir -p /data/${APPLICATION}/log
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/favicon.ico
RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/release/Caddyfile
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/DEV-VERSION) && \
wget -O /data/${APPLICATION}/${APPLICATION} https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION} && \
RUN wget -O /data/${APPLICATION}/config.yaml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/config.yaml
RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/init.sh
RUN chmod +x /data/${APPLICATION}/${APPLICATION}
RUN chmod +x /usr/local/bin/init.sh
CMD ["/usr/local/bin/init.sh"]

View file

@ -0,0 +1,21 @@
FROM wjqserver/caddy:latest
ARG USER=WJQSERVER-STUDIO
ARG REPO=ghproxy
ARG APPLICATION=ghproxy
RUN mkdir -p /data/www
RUN mkdir -p /data/${APPLICATION}/config
RUN mkdir -p /data/${APPLICATION}/log
RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html
RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/favicon.ico
RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/release/Caddyfile
RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \
wget -O /data/${APPLICATION}/${APPLICATION} https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION} && \
RUN wget -O /data/${APPLICATION}/config.yaml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/config.yaml
RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/init.sh
RUN chmod +x /data/${APPLICATION}/${APPLICATION}
RUN chmod +x /usr/local/bin/init.sh
CMD ["/usr/local/bin/init.sh"]

50
go.mod Normal file
View file

@ -0,0 +1,50 @@
module ghproxy
go 1.23.1
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudflare/circl v1.4.0 // 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/gin-gonic/gin v1.10.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/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/imroc/req/v3 v3.46.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // 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/onsi/ginkgo/v2 v2.20.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/quic-go/quic-go v0.47.0 // indirect
github.com/refraction-networking/utls v1.6.7 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/mock v0.4.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.25.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

122
go.sum Normal file
View file

@ -0,0 +1,122 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
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/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY=
github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU=
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/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/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/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/imroc/req/v3 v3.46.1 h1:oahr2hBTb3AaFI4P6jkN0Elj2ZVKJcdQ/IjWqeIKjvc=
github.com/imroc/req/v3 v3.46.1/go.mod h1:weam9gmyb00QnOtu6HXSnk44dNFkIUQb5QdMx13FeUU=
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/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
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/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0=
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/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=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
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/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.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/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
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/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=

20
init.sh Normal file
View file

@ -0,0 +1,20 @@
#!/bin/bash
APPLICATON=ghproxy
if [ ! -f /data/caddy/config/Caddyfile ]; then
cp /data/caddy/Caddyfile /data/caddy/config/Caddyfile
fi
if [ ! -f /data/${APPLICATON}/config/config.yaml ]; then
cp /data/${APPLICATON}/config.yaml /data/${APPLICATON}/config/config.yaml
fi
/data/caddy/caddy run --config /data/caddy/config/Caddyfile > /data${APPLICATON}/log/caddy.log 2>&1 &
/data/${APPLICATON}/${APPLICATON} > /data/ghproxy/log/run.log 2>&1 &
while [[ true ]]; do
sleep 1
done

44
logger/logger.go Normal file
View file

@ -0,0 +1,44 @@
// logger/logger.go
package logger
import (
"fmt"
"log"
"os"
"time"
)
var logFile *os.File
var logger *log.Logger
// Init 初始化日志记录器,接受日志文件路径作为参数
func Init(logFilePath string) error {
var err error
logFile, err = os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
return err
}
logger = log.New(logFile, "", 0) // 不使用默认前缀
return nil
}
// Log 直接记录日志的函数,带有时间戳
func Log(customMessage string) {
if logger != nil {
timestamp := time.Now().Format("02/Jan/2006:15:04:05 -0700") // 使用自定义时间格式
logger.Println(timestamp + " - " + customMessage)
}
}
// Logw 用于格式化日志记录
func Logw(format string, args ...interface{}) {
message := fmt.Sprintf(format, args...) // 格式化消息
Log(message) // 记录日志
}
// Close 关闭日志文件
func Close() {
if logFile != nil {
logFile.Close()
}
}

267
main.go Normal file
View file

@ -0,0 +1,267 @@
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"regexp"
"strconv"
"strings"
"ghproxy/config"
"ghproxy/logger"
"github.com/gin-gonic/gin"
"github.com/imroc/req/v3"
)
var cfg *config.Config
var logw = logger.Logw
var router *gin.Engine
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/([^/]+)/.+?/.+`),
}
)
func loadConfig() {
var err error
// 初始化配置
cfg, err = config.LoadConfig("/data/go/config/config.yaml")
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
fmt.Printf("Loaded config: %v\n", cfg)
}
func setupLogger() {
// 初始化日志模块
var err error
err = logger.Init(cfg.LogFilePath) // 传递日志文件路径
if err != nil {
log.Fatalf("Failed to initialize logger: %v", err)
}
logw("Logger initialized")
logw("Init Completed")
}
func init() {
loadConfig()
setupLogger()
// 设置 Gin 模式
gin.SetMode(gin.ReleaseMode)
// 初始化路由
router = gin.Default()
// 定义路由
router.GET("/", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://ghproxy0rtt.1888866.xyz/")
})
router.GET("/api", api)
// 健康检查
router.GET("/api/healthcheck", func(c *gin.Context) {
c.String(http.StatusOK, "OK")
})
// 未匹配路由处理
router.NoRoute(noRouteHandler(cfg))
}
func main() {
// 启动服务器
err := router.Run(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
if err != nil {
log.Fatalf("Error starting server: %v\n", err)
}
fmt.Println("Program finished")
}
func api(c *gin.Context) {
// 设置响应头
c.Writer.Header().Set("Content-Type", "application/json")
json.NewEncoder(c.Writer).Encode(map[string]interface{}{
"MaxResponseBodySize": cfg.SizeLimit,
})
}
func authHandler(c *gin.Context) bool {
if cfg.Auth {
authToken := c.Query("auth_token")
return authToken == cfg.AuthToken
}
return true
}
func noRouteHandler(config *config.Config) gin.HandlerFunc {
return func(c *gin.Context) {
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/")
re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`)
matches := re.FindStringSubmatch(rawPath)
rawPath = "https://" + matches[2]
matches = checkURL(rawPath)
if matches == nil {
c.String(http.StatusForbidden, "Invalid input.")
return
}
if exps[1].MatchString(rawPath) {
rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1)
}
if !authHandler(c) {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
logw("Unauthorized request: %s", rawPath)
return
}
// 日志记录
logw("Request: %s %s", c.Request.Method, rawPath)
logw("Matches: %v", matches)
// 代理请求
switch {
case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath):
logw("%s Matched - USE proxy-chrome", rawPath)
proxyRequest(c, rawPath, config, "chrome")
case exps[2].MatchString(rawPath):
logw("%s Matched - USE proxy-git", rawPath)
proxyRequest(c, rawPath, config, "git")
default:
c.String(http.StatusForbidden, "Invalid input.")
return
}
}
}
func proxyRequest(c *gin.Context, u string, config *config.Config, mode string) {
method := c.Request.Method
logw("%s Method: %s", u, method)
client := req.C()
switch mode {
case "chrome":
client.SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36").
SetTLSFingerprintChrome().
ImpersonateChrome()
case "git":
client.SetUserAgent("git/2.33.1")
}
// 读取请求体
body, err := io.ReadAll(c.Request.Body)
if err != nil {
handleError(c, fmt.Sprintf("Failed to read request body: %v", err))
return
}
defer c.Request.Body.Close()
// 创建新的请求
req := client.R().SetBody(body)
// 复制请求头
for key, values := range c.Request.Header {
for _, value := range values {
req.SetHeader(key, value)
}
}
// 发送请求并处理响应
resp, err := sendRequest(req, method, u)
if err != nil {
handleError(c, fmt.Sprintf("Failed to send request: %v", err))
return
}
defer resp.Body.Close()
// 检查响应内容长度并处理重定向
if err := handleResponseSize(resp, config, c); err != nil {
logw("Error handling response size: %v", err)
return
}
copyResponseHeaders(resp, c, config)
c.Status(resp.StatusCode)
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
logw("Failed to copy response body: %v", err)
}
}
func sendRequest(req *req.Request, method, url string) (*req.Response, error) {
switch method {
case "GET":
return req.Get(url)
case "POST":
return req.Post(url)
case "PUT":
return req.Put(url)
case "DELETE":
return req.Delete(url)
default:
return nil, fmt.Errorf("unsupported method: %s", method)
}
}
func handleResponseSize(resp *req.Response, config *config.Config, c *gin.Context) error {
contentLength := resp.Header.Get("Content-Length")
if contentLength != "" {
size, err := strconv.Atoi(contentLength)
if err == nil && size > config.SizeLimit {
finalURL := resp.Request.URL.String()
c.Redirect(http.StatusMovedPermanently, finalURL)
logw("Redirecting to %s due to size limit (%d bytes)", finalURL, size)
return fmt.Errorf("response size exceeds limit")
}
}
return nil
}
func copyResponseHeaders(resp *req.Response, c *gin.Context, config *config.Config) {
headersToRemove := []string{"Content-Security-Policy", "Referrer-Policy", "Strict-Transport-Security"}
for _, header := range headersToRemove {
resp.Header.Del(header)
}
for key, values := range resp.Header {
for _, value := range values {
c.Header(key, value)
}
}
if config.CORSOrigin {
c.Header("Access-Control-Allow-Origin", "*")
} else {
c.Header("Access-Control-Allow-Origin", "")
}
}
func handleError(c *gin.Context, message string) {
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", message))
logw(message)
}
func checkURL(u string) []string {
for _, exp := range exps {
if matches := exp.FindStringSubmatch(u); matches != nil {
logw("URL matched: %s, Matches: %v", u, matches[1:])
return matches[1:]
}
}
logw("Invalid URL: %s", u)
return nil
}

BIN
pages/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

226
pages/index.html Normal file
View file

@ -0,0 +1,226 @@
<!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文件加速">
<title>Github文件加速</title>
<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>
body {
background-color: #f8f9fac5;
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;
}
.container {
max-width: 800px;
text-align: center;
min-height: 45vh;
}
h1 {
font-weight: bold;
margin-bottom: 75px;
}
.rounded-button {
border-radius: 6px;
transition: background-color 0.3s, transform 0.2s;
padding: 10px 30px;
background-color: #39c5bb;
color: white;
border: none;
margin-bottom: 10px;
}
.rounded-button:hover {
background-color: #39c5bcda;
transform: scale(1.05);
}
.tips>p:first-child::before {
position: sticky;
color: #7b7b7b;
margin-bottom: 5px;
}
footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
text-align: center;
}
pre {
background: #2d2d2d;
color: #f8f8f2;
padding: 20px 20px;
margin: 10px 0;
border-radius: 8px;
overflow-x: auto;
position: relative;
}
pre::before {
content: " ";
display: block;
position: absolute;
top: 10px;
left: 10px;
width: 12px;
height: 12px;
background: #ff5f56;
border-radius: 50%;
box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f;
}
code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
font-size: 0.875em;
}
.code {
position: relative;
padding-right: 0px;
}
.copy-button {
position: absolute;
top: 5px;
right: 10px;
background: rgba(118, 119, 121, 0.7);
color: white;
border: none;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
transition: opacity 0.3s;
z-index: 1;
}
pre:hover .copy-button {
opacity: 1;
}
#visitor-info {
margin-top: 10px;
text-align: center;
line-height: 0.5;
}
</style>
</head>
<body>
<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" onclick="copyCode(this)">Copy</button>
<pre id="formattedLinkOutput"></pre>
</div>
<div class="tips">
<p>GitHub链接带不带协议头均可支持release、archive以及文件转换后链接均可使用</a></p>
<p id="sizeLimitDisplay">文件大小限制: ...</p>
</div>
</div>
<script>
function formatGithubLink() {
var githubLinkInput = document.getElementById('githubLinkInput');
var currentHost = window.location.host;
var formattedLink = "";
if (githubLinkInput.value.startsWith("https://github.com/") || githubLinkInput.value.startsWith("http://github.com/")) {
formattedLink = "https://" + currentHost + "/github.com" + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 8));
} else if (githubLinkInput.value.startsWith("github.com/")) {
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
} else if (githubLinkInput.value.startsWith("https://raw.githubusercontent.com/") || githubLinkInput.value.startsWith("http://raw.githubusercontent.com/")) {
formattedLink = "https://" + currentHost + githubLinkInput.value.substring(githubLinkInput.value.indexOf("/", 7));
} else if (githubLinkInput.value.startsWith("raw.githubusercontent.com/")) {
formattedLink = "https://" + currentHost + "/" + githubLinkInput.value;
} else if (!githubLinkInput.value.trim()) {
alert('请输入有效的GitHub链接');
}
var formattedLinkOutput = document.getElementById('formattedLinkOutput');
formattedLinkOutput.textContent = formattedLink;
}
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();
alert('链接已复制到剪贴板');
});
function fetchAPI() {
fetch(window.location.origin + '/api')
.then(response => response.json())
.then(data => {
const sizeLimitDisplay = document.getElementById('sizeLimitDisplay');
const sizeInMB = (data.MaxResponseBodySize / (1024 * 1024)).toFixed(0);
sizeLimitDisplay.textContent = `文件大小限制: ${sizeInMB} MB`;
})
.catch(error => {
console.error('Error fetching API:', error);
});
}
document.addEventListener('DOMContentLoaded', fetchAPI);
</script>
</body>
<footer>
<p>
Copyright &copy; 2024 WJQSERVER-STUDIO
</p>
<p>
GitHub仓库地址<a
href="https://github.com/WJQSERVER-STUDIO/ghproxy">https://github.com/WJQSERVER-STUDIO/ghproxy</a>
</p>
<div id="visitor-info" style="text-align: center; margin-top: 15px;">
<p>您的IP地址: <span id="visitor-ip"></span></p>
<p>当前位置: <span id="visitor-country"></span> <img id="visitor-flag" src="" alt="" width="24" height="16"></p>
</div>
<script>
fetch('https://ip.1888866.xyz/ip-lookup')
.then(response => {
if (!response.ok) {
throw new Error('网络响应失败');
}
return response.json();
})
.then(data => {
document.getElementById('visitor-ip').textContent = data.ip;
document.getElementById('visitor-country').textContent = data.country_name;
document.getElementById('visitor-flag').src = `https://flagcdn.com/w20/${data.country_code.toLowerCase()}.png`;
})
.catch(error => {
console.error('获取地理位置信息失败:', error);
const visitorInfo = document.getElementById('visitor-info');
visitorInfo.innerHTML = '<p>无法获取您的地理位置信息,请稍后再试。</p>';
});
</script>
</footer>
</html>