Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bf075fcaf | ||
|
|
208a239af3 | ||
|
|
1fb97b5347 | ||
|
|
95c2e4fd68 | ||
|
|
79fa21321f | ||
|
|
c4c5993bd1 | ||
|
|
d46fd3fec4 | ||
|
|
279b48d432 |
27
README.md
27
README.md
@@ -4,6 +4,10 @@
|
|||||||
|
|
||||||
一个轻量级、高性能的多功能代理服务,提供 Docker 镜像加速、GitHub 文件加速、下载离线镜像、在线搜索 Docker 镜像等功能。
|
一个轻量级、高性能的多功能代理服务,提供 Docker 镜像加速、GitHub 文件加速、下载离线镜像、在线搜索 Docker 镜像等功能。
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://count.getloli.com/get/@sky22333.hubproxy?theme=rule34" alt="Visitors">
|
||||||
|
</p>
|
||||||
|
|
||||||
## ✨ 特性
|
## ✨ 特性
|
||||||
|
|
||||||
- 🐳 **Docker 镜像加速** - 单域名实现 Docker Hub、GHCR、Quay 等多个镜像仓库加速,流式传输优化拉取速度。
|
- 🐳 **Docker 镜像加速** - 单域名实现 Docker Hub、GHCR、Quay 等多个镜像仓库加速,流式传输优化拉取速度。
|
||||||
@@ -63,6 +67,21 @@ docker pull yourdomain.com/ghcr.io/sky22333/hubproxy
|
|||||||
# 符合Docker Registry API v2标准的仓库都支持
|
# 符合Docker Registry API v2标准的仓库都支持
|
||||||
```
|
```
|
||||||
|
|
||||||
|
当然也支持配置为全局镜像加速,在主机上新建(或编辑)`/etc/docker/daemon.json`
|
||||||
|
|
||||||
|
在 `"registry-mirrors"` 中加入域名:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registry-mirrors": [
|
||||||
|
"https://yourdomain.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
若已设置其他加速地址,直接并列添加后保存,
|
||||||
|
再执行 `sudo systemctl restart docker` 重启docker服务让配置生效。
|
||||||
|
|
||||||
### GitHub 文件加速
|
### GitHub 文件加速
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -79,9 +98,9 @@ git clone https://yourdomain.com/https://github.com/sky22333/hubproxy.git
|
|||||||
## ⚙️ 配置
|
## ⚙️ 配置
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>config.toml配置说明</summary>
|
<summary>config.toml 配置说明</summary>
|
||||||
|
|
||||||
此配置是默认配置
|
*此配置是默认配置,已经内置在程序中了,可以不用添加。*
|
||||||
|
|
||||||
```
|
```
|
||||||
[server]
|
[server]
|
||||||
@@ -224,3 +243,7 @@ example.com {
|
|||||||
**⭐ 如果这个项目对你有帮助,请给个 Star!⭐**
|
**⭐ 如果这个项目对你有帮助,请给个 Star!⭐**
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[](https://www.star-history.com/#sky22333/hubproxy&Date)
|
||||||
|
|||||||
112
src/main.go
112
src/main.go
@@ -143,12 +143,18 @@ func handler(c *gin.Context) {
|
|||||||
for strings.HasPrefix(rawPath, "/") {
|
for strings.HasPrefix(rawPath, "/") {
|
||||||
rawPath = strings.TrimPrefix(rawPath, "/")
|
rawPath = strings.TrimPrefix(rawPath, "/")
|
||||||
}
|
}
|
||||||
|
// 自动补全协议头
|
||||||
if !strings.HasPrefix(rawPath, "http") {
|
if !strings.HasPrefix(rawPath, "https://") {
|
||||||
c.String(http.StatusForbidden, "无效输入")
|
// 修复 http:/ 和 https:/ 的情况
|
||||||
return
|
if strings.HasPrefix(rawPath, "http:/") || strings.HasPrefix(rawPath, "https:/") {
|
||||||
|
rawPath = strings.Replace(rawPath, "http:/", "", 1)
|
||||||
|
rawPath = strings.Replace(rawPath, "https:/", "", 1)
|
||||||
|
} else if strings.HasPrefix(rawPath, "http://") {
|
||||||
|
rawPath = strings.TrimPrefix(rawPath, "http://")
|
||||||
|
}
|
||||||
|
rawPath = "https://" + rawPath
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := checkURL(rawPath)
|
matches := checkURL(rawPath)
|
||||||
if matches != nil {
|
if matches != nil {
|
||||||
// GitHub仓库访问控制检查
|
// GitHub仓库访问控制检查
|
||||||
@@ -308,72 +314,50 @@ func checkURL(u string) []string {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化健康监控路由
|
// 简单的健康检查
|
||||||
|
func formatBeijingTime(t time.Time) string {
|
||||||
|
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||||
|
if err != nil {
|
||||||
|
loc = time.FixedZone("CST", 8*3600) // 兜底时区
|
||||||
|
}
|
||||||
|
return t.In(loc).Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为可读时间
|
||||||
|
func formatDuration(d time.Duration) string {
|
||||||
|
if d < time.Minute {
|
||||||
|
return fmt.Sprintf("%d秒", int(d.Seconds()))
|
||||||
|
} else if d < time.Hour {
|
||||||
|
return fmt.Sprintf("%d分钟%d秒", int(d.Minutes()), int(d.Seconds())%60)
|
||||||
|
} else if d < 24*time.Hour {
|
||||||
|
return fmt.Sprintf("%d小时%d分钟", int(d.Hours()), int(d.Minutes())%60)
|
||||||
|
} else {
|
||||||
|
days := int(d.Hours()) / 24
|
||||||
|
hours := int(d.Hours()) % 24
|
||||||
|
return fmt.Sprintf("%d天%d小时", days, hours)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func initHealthRoutes(router *gin.Engine) {
|
func initHealthRoutes(router *gin.Engine) {
|
||||||
// 健康检查端点
|
|
||||||
router.GET("/health", func(c *gin.Context) {
|
router.GET("/health", func(c *gin.Context) {
|
||||||
|
uptime := time.Since(serviceStartTime)
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"timestamp": time.Now().Unix(),
|
"timestamp_unix": serviceStartTime.Unix(),
|
||||||
"uptime": time.Since(serviceStartTime).Seconds(),
|
"uptime_sec": uptime.Seconds(),
|
||||||
"service": "hubproxy",
|
"service": "hubproxy",
|
||||||
|
"start_time_bj": formatBeijingTime(serviceStartTime),
|
||||||
|
"uptime_human": formatDuration(uptime),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 就绪检查端点
|
|
||||||
router.GET("/ready", func(c *gin.Context) {
|
router.GET("/ready", func(c *gin.Context) {
|
||||||
checks := make(map[string]string)
|
uptime := time.Since(serviceStartTime)
|
||||||
allReady := true
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"ready": true,
|
||||||
if GetConfig() != nil {
|
"timestamp_unix": time.Now().Unix(),
|
||||||
checks["config"] = "ok"
|
"uptime_sec": uptime.Seconds(),
|
||||||
} else {
|
"uptime_human": formatDuration(uptime),
|
||||||
checks["config"] = "failed"
|
|
||||||
allReady = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查全局缓存状态
|
|
||||||
if globalCache != nil {
|
|
||||||
checks["cache"] = "ok"
|
|
||||||
} else {
|
|
||||||
checks["cache"] = "failed"
|
|
||||||
allReady = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查限流器状态
|
|
||||||
if globalLimiter != nil {
|
|
||||||
checks["ratelimiter"] = "ok"
|
|
||||||
} else {
|
|
||||||
checks["ratelimiter"] = "failed"
|
|
||||||
allReady = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查镜像下载器状态
|
|
||||||
if globalImageStreamer != nil {
|
|
||||||
checks["imagestreamer"] = "ok"
|
|
||||||
} else {
|
|
||||||
checks["imagestreamer"] = "failed"
|
|
||||||
allReady = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查HTTP客户端状态
|
|
||||||
if GetGlobalHTTPClient() != nil {
|
|
||||||
checks["httpclient"] = "ok"
|
|
||||||
} else {
|
|
||||||
checks["httpclient"] = "failed"
|
|
||||||
allReady = false
|
|
||||||
}
|
|
||||||
|
|
||||||
status := http.StatusOK
|
|
||||||
if !allReady {
|
|
||||||
status = http.StatusServiceUnavailable
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(status, gin.H{
|
|
||||||
"ready": allReady,
|
|
||||||
"checks": checks,
|
|
||||||
"timestamp": time.Now().Unix(),
|
|
||||||
"uptime": time.Since(serviceStartTime).Seconds(),
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user