mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 16:21:11 +08:00
update
This commit is contained in:
parent
c21149b17f
commit
749093c8d5
20 changed files with 1282 additions and 1 deletions
77
.github/workflows/build-dev.yml
vendored
Normal file
77
.github/workflows/build-dev.yml
vendored
Normal 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
77
.github/workflows/build.yml
vendored
Normal 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
1
DEV-VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
24w01a
|
||||||
107
LICENSE
Normal file
107
LICENSE
Normal 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.
|
||||||
|
|
@ -1 +1 @@
|
||||||
# ghproxy
|
# golang-temp
|
||||||
1
VERSION
Normal file
1
VERSION
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
0.0.1
|
||||||
99
caddyfile/dev/Caddyfile
Normal file
99
caddyfile/dev/Caddyfile
Normal 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/*
|
||||||
99
caddyfile/release/Caddyfile
Normal file
99
caddyfile/release/Caddyfile
Normal 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
31
config/config.go
Normal 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
7
config/config.yaml
Normal 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"
|
||||||
11
docker/compose/docker-compose.yml
Normal file
11
docker/compose/docker-compose.yml
Normal 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'
|
||||||
21
docker/dockerfile/dev/Dockerfile
Normal file
21
docker/dockerfile/dev/Dockerfile
Normal 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"]
|
||||||
|
|
||||||
21
docker/dockerfile/release/Dockerfile
Normal file
21
docker/dockerfile/release/Dockerfile
Normal 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
50
go.mod
Normal 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
122
go.sum
Normal 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
20
init.sh
Normal 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
44
logger/logger.go
Normal 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
267
main.go
Normal 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
BIN
pages/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
226
pages/index.html
Normal file
226
pages/index.html
Normal 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 © 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>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue