From dffeedea1e4bac21c68970dcafa81c5cd40d1c32 Mon Sep 17 00:00:00 2001 From: LiuShen <3162475700@qq.com> Date: Tue, 16 Sep 2025 11:21:13 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=98=8B=E6=9B=B4=E6=96=B0=E4=B8=A4?= =?UTF-8?q?=E4=B8=AA=E5=BA=94=E7=94=A8=EF=BC=8C=E5=B0=9D=E8=AF=95=E4=BB=A3?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hubproxy/README.md | 239 +++++++++++++++++++++++++ hubproxy/data.yml | 36 ++++ hubproxy/latest/data.yml | 18 ++ hubproxy/latest/docker-compose.yml | 22 +++ hubproxy/logo.png | Bin 0 -> 21511 bytes oauth2-proxy/7.12.0/data.yml | 193 ++++++++++++++++++++ oauth2-proxy/7.12.0/docker-compose.yml | 27 +++ oauth2-proxy/README.md | 106 +++++++++++ oauth2-proxy/data.yml | 32 ++++ oauth2-proxy/logo.png | Bin 0 -> 9087 bytes 10 files changed, 673 insertions(+) create mode 100644 hubproxy/README.md create mode 100644 hubproxy/data.yml create mode 100644 hubproxy/latest/data.yml create mode 100644 hubproxy/latest/docker-compose.yml create mode 100644 hubproxy/logo.png create mode 100644 oauth2-proxy/7.12.0/data.yml create mode 100644 oauth2-proxy/7.12.0/docker-compose.yml create mode 100644 oauth2-proxy/README.md create mode 100644 oauth2-proxy/data.yml create mode 100644 oauth2-proxy/logo.png diff --git a/hubproxy/README.md b/hubproxy/README.md new file mode 100644 index 000000000..ddb261ac5 --- /dev/null +++ b/hubproxy/README.md @@ -0,0 +1,239 @@ +# HubProxy + +🚀 **Docker 和 GitHub 加速代理服务器** + +一个轻量级、高性能的多功能代理服务,提供 Docker 镜像加速、GitHub 文件加速、下载离线镜像、在线搜索 Docker 镜像等功能。 + +## ✨ 特性 + +- 🐳 **Docker 镜像加速** - 支持 Docker Hub、GHCR、Quay 等多个镜像仓库加速,流式传输优化拉取速度。 +- 🐳 **离线镜像包** - 支持下载离线镜像包,流式传输加防抖设计。 +- 📁 **GitHub 文件加速** - 加速 GitHub Release、Raw 文件下载,支持`api.github.com`,脚本嵌套加速等等 +- 🤖 **AI 模型库支持** - 支持 Hugging Face 模型下载加速 +- 🛡️ **智能限流** - IP 限流保护,防止滥用 +- 🚫 **仓库审计** - 强大的自定义黑名单,白名单,同时审计镜像仓库,和GitHub仓库 +- 🔍 **镜像搜索** - 在线搜索 Docker 镜像 +- ⚡ **轻量高效** - 基于 Go 语言,单二进制文件运行,资源占用低。 +- 🔧 **统一配置** - 统一配置管理,便于维护。 +- 🛡️ **完全自托管** - 避免依赖免费第三方服务的不稳定性,例如`cloudflare`等等。 +- 🚀 **多服务统一加速** - 单个程序即可统一加速 Docker、GitHub、Hugging Face 等多种服务,简化部署与管理。 + + +## 🚀 快速开始 + +### Docker部署(推荐) +``` +docker run -d \ + --name hubproxy \ + -p 5000:5000 \ + --restart always \ + ghcr.io/sky22333/hubproxy +``` + + + +### 一键脚本安装 + +```bash +curl -fsSL https://raw.githubusercontent.com/sky22333/hubproxy/main/install.sh | sudo bash +``` + +也可以直接下载二进制文件执行`./hubproxy`使用,无需配置文件即可启动,内置默认配置,支持所有功能。 + +这个脚本会: +- 🔍 自动检测系统架构(AMD64/ARM64) +- 📥 从 GitHub Releases 下载最新版本 +- ⚙️ 自动配置系统服务 +- 🔄 保留现有配置(升级时) + + + +## 📖 使用方法 + +### Docker 镜像加速 + +```bash +# 原命令 +docker pull nginx + +# 使用加速 +docker pull yourdomain.com/nginx + +# ghcr加速 +docker pull yourdomain.com/ghcr.io/sky22333/hubproxy + +# 符合Docker Registry API v2标准的仓库都支持 +``` + +当然也支持配置为全局镜像加速,在主机上新建(或编辑)`/etc/docker/daemon.json` + +在 `"registry-mirrors"` 中加入域名: + +```json +{ + "registry-mirrors": [ + "https://yourdomain.com" + ] +} +``` + +若已设置其他加速地址,直接并列添加后保存,再执行 `sudo systemctl restart docker` 重启docker服务让配置生效。 + +### GitHub 文件加速 + +```bash +# 原链接 +https://github.com/user/repo/releases/download/v1.0.0/file.tar.gz + +# 加速链接 +https://yourdomain.com/https://github.com/user/repo/releases/download/v1.0.0/file.tar.gz + +# 加速下载仓库 +git clone https://yourdomain.com/https://github.com/sky22333/hubproxy.git +``` + +## ⚙️ 配置 + +
+ config.toml 配置说明 + +*此配置是默认配置,已经内置在程序中了* + +``` +[server] +host = "0.0.0.0" +# 监听端口 +port = 5000 +# Github文件大小限制(字节),默认2GB +fileSize = 2147483648 +# HTTP/2 多路复用,提升下载速度 +enableH2C = false + +[rateLimit] +# 每个IP每周期允许的请求数(注意Docker镜像会有多个层,会消耗多个次数) +requestLimit = 500 +# 限流周期(小时) +periodHours = 3.0 + +[security] +# IP白名单,支持单个IP或IP段 +# 白名单中的IP不受限流限制 +whiteList = [ + "127.0.0.1", + "172.17.0.0/16", + "192.168.1.0/24" +] + +# IP黑名单,支持单个IP或IP段 +# 黑名单中的IP将被直接拒绝访问 +blackList = [ + "192.168.100.1", + "192.168.100.0/24" +] + +[access] +# 代理服务白名单(支持GitHub仓库和Docker镜像,支持通配符) +# 只允许访问白名单中的仓库/镜像,为空时不限制 +whiteList = [] + +# 代理服务黑名单(支持GitHub仓库和Docker镜像,支持通配符) +# 禁止访问黑名单中的仓库/镜像 +blackList = [ + "baduser/malicious-repo", + "*/malicious-repo", + "baduser/*" +] + +# 代理配置,支持有用户名/密码认证和无认证模式 +# 无认证: socks5://127.0.0.1:1080 +# 有认证: socks5://username:password@127.0.0.1:1080 +# 留空不使用代理 +proxy = "" + +[download] +# 批量下载离线镜像数量限制 +maxImages = 10 + +# Registry映射配置,支持多种镜像仓库上游 +[registries] + +# GitHub Container Registry +[registries."ghcr.io"] +upstream = "ghcr.io" +authHost = "ghcr.io/token" +authType = "github" +enabled = true + +# Google Container Registry +[registries."gcr.io"] +upstream = "gcr.io" +authHost = "gcr.io/v2/token" +authType = "google" +enabled = true + +# Quay.io Container Registry +[registries."quay.io"] +upstream = "quay.io" +authHost = "quay.io/v2/auth" +authType = "quay" +enabled = true + +# Kubernetes Container Registry +[registries."registry.k8s.io"] +upstream = "registry.k8s.io" +authHost = "registry.k8s.io" +authType = "anonymous" +enabled = true + +[tokenCache] +# 是否启用缓存(同时控制Token和Manifest缓存)显著提升性能 +enabled = true +# 默认缓存时间(分钟) +defaultTTL = "20m" +``` + +
+ +容器内的配置文件位于 `/root/config.toml` + +脚本部署配置文件位于 `/opt/hubproxy/config.toml` + +为了IP限流能够正常运行,反向代理需要传递IP头用来获取访客真实IP,以caddy为例: +``` +example.com { + reverse_proxy { + to 127.0.0.1:5000 + header_up X-Real-IP {remote} + header_up X-Forwarded-For {remote} + header_up X-Forwarded-Proto {scheme} + } +} +``` +cloudflare CDN: +``` +example.com { + reverse_proxy 127.0.0.1:5000 { + header_up X-Forwarded-For {http.request.header.CF-Connecting-IP} + header_up X-Real-IP {http.request.header.CF-Connecting-IP} + header_up X-Forwarded-Proto https + header_up X-Forwarded-Host {host} + } +} +``` + +> 对于使用nginx反代的用户,Github加速提示`无效输入`的问题可以参见[issues/62](https://github.com/sky22333/hubproxy/issues/62#issuecomment-3219572440) + + +## ⚠️ 免责声明 + +- 本程序仅供学习交流使用,请勿用于非法用途 +- 使用本程序需遵守当地法律法规 +- 作者不对使用者的任何行为承担责任 + +--- + +
+ +**⭐ 如果这个项目对你有帮助,请给个 Star!⭐** + +
\ No newline at end of file diff --git a/hubproxy/data.yml b/hubproxy/data.yml new file mode 100644 index 000000000..7b8df473c --- /dev/null +++ b/hubproxy/data.yml @@ -0,0 +1,36 @@ +name: HubProxy +tags: + - Proxy + - Docker + - GitHub + - DevOps + - 加速 +title: 🚀 Docker 和 GitHub 加速代理服务器 +description: 一个轻量级、高性能的多功能代理服务,提供 Docker 镜像加速、GitHub 文件加速、离线镜像下载、AI 模型加速、智能限流等功能。 +additionalProperties: + key: hubproxy + name: HubProxy + tags: + - Tool + - DevOps + - Proxy + shortDescZh: Docker & GitHub 镜像加速代理服务器,支持多功能加速与离线镜像 + shortDescEn: Lightweight proxy server for accelerating Docker, GitHub, and AI model downloads + type: website + crossVersionUpdate: true + limit: 0 + website: https://github.com/sky22333/hubproxy + github: https://github.com/sky22333/hubproxy + document: https://github.com/sky22333/hubproxy + description: + en: A lightweight, high-performance proxy server for accelerating Docker images, GitHub files, Hugging Face models, and more. Supports offline image packages, intelligent rate limiting, and unified configuration. + zh: 一个轻量级、高性能的多功能代理服务,支持 Docker 镜像加速、GitHub 文件加速、Hugging Face 模型下载、离线镜像包、智能限流等功能。 + zh-Hant: 輕量級高效能多功能代理服務,支援 Docker 鏡像加速、GitHub 檔案加速、Hugging Face 模型下載、離線鏡像包、智慧限流等功能。 + ja: 軽量で高性能な多機能プロキシサーバー。Docker イメージや GitHub ファイル、Hugging Face モデルの加速、オフラインイメージパッケージ、インテリジェントレート制限などをサポート。 + ms: Pelayan proksi ringan dan berprestasi tinggi untuk mempercepat imej Docker, fail GitHub, model Hugging Face, serta menyokong pakej imej luar talian dan had kadar pintar. + pt-br: Servidor proxy leve e de alto desempenho para acelerar imagens Docker, arquivos do GitHub, modelos Hugging Face e muito mais. Suporta pacotes de imagens offline, limitação de taxa inteligente e configuração unificada. + ru: Легковесный высокопроизводительный прокси-сервер для ускорения загрузки Docker-образов, файлов GitHub, моделей Hugging Face и др. Поддерживает офлайн-пакеты, интеллектуальное ограничение скорости и унифицированную конфигурацию. + ko: 가볍고 고성능의 다기능 프록시 서버로 Docker 이미지, GitHub 파일, Hugging Face 모델 가속을 지원하며 오프라인 이미지 패키지, 지능형 속도 제한, 통합 설정을 제공합니다. + architectures: + - amd64 + - arm64 diff --git a/hubproxy/latest/data.yml b/hubproxy/latest/data.yml new file mode 100644 index 000000000..eb0cb6866 --- /dev/null +++ b/hubproxy/latest/data.yml @@ -0,0 +1,18 @@ +additionalProperties: + formFields: + - default: 13485 + envKey: PANEL_APP_PORT_HTTP + labelEn: HTTP Port + labelZh: HTTP 端口 + label: + en: HTTP Port + ja: ポート + ms: Port + pt-br: Porta + ru: Порт + ko: 포트 + zh: HTTP 端口 + zh-Hant: HTTP 連接埠 + required: true + rule: paramPort + type: number \ No newline at end of file diff --git a/hubproxy/latest/docker-compose.yml b/hubproxy/latest/docker-compose.yml new file mode 100644 index 000000000..bea1a6f0a --- /dev/null +++ b/hubproxy/latest/docker-compose.yml @@ -0,0 +1,22 @@ +services: + hubproxy: + image: ghcr.io/sky22333/hubproxy:latest + container_name: ${CONTAINER_NAME} + ports: + - "${PANEL_APP_PORT_HTTP}:8888" + volumes: + - ./src/config.toml:/root/config.toml + logging: + driver: json-file + options: + max-size: "1g" + max-file: "2" + labels: + createdBy: "Apps" + networks: + - 1panel-network + restart: always + +networks: + 1panel-network: + external: true diff --git a/hubproxy/logo.png b/hubproxy/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1302df0013953eebc2fcc60fb8540cf7f434aa25 GIT binary patch literal 21511 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

tzs5PM%Jh1yl;VD}v`C84#YEK{lu?eu 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 \ + -e OAUTH2_PROXY_CLIENT_SECRET= \ + -e OAUTH2_PROXY_COOKIE_SECRET=$(openssl rand -base64 32 | head -c 32 | base64) \ + -e OAUTH2_PROXY_REDIRECT_URL=https://your.domain.com/oauth2/callback \ + -e OAUTH2_PROXY_EMAIL_DOMAINS=* \ + quay.io/oauth2-proxy/oauth2-proxy:latest +``` + +容器启动后,访问 `http://localhost:4180` 即会跳转到 OAuth2 提供商登录。 + +--- + +### 使用 Helm (Kubernetes) + +```bash +helm repo add oauth2-proxy https://oauth2-proxy.github.io/manifests +helm repo update +helm install oauth2-proxy/oauth2-proxy \ + --generate-name \ + --set config.clientID= \ + --set config.clientSecret= \ + --set config.cookieSecret=$(openssl rand -base64 32 | head -c 32 | base64) \ + --set config.provider=google +``` + +--- + +## 配置示例 + +### 基本配置文件 `oauth2-proxy.cfg` + +```ini +provider = "oidc" +client_id = "your-client-id" +client_secret = "your-client-secret" +redirect_url = "https://your.domain.com/oauth2/callback" +oidc_issuer_url = "https://accounts.example.com" +email_domains = ["*"] +cookie_secret = "random-cookie-secret" +``` + +运行: + +```bash +oauth2-proxy --config ./oauth2-proxy.cfg +``` + +--- + +## Nginx 反向代理示例 + +```nginx +server { + listen 443 ssl; + server_name app.example.com; + + location / { + proxy_pass http://127.0.0.1:4180; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +--- + +## 常用环境变量 + +| 变量名 | 说明 | +| ---------------------------- | ---------------------------------- | +| `OAUTH2_PROXY_PROVIDER` | 身份提供商类型,如 `google`、`github`、`oidc` | +| `OAUTH2_PROXY_CLIENT_ID` | OAuth2 客户端 ID | +| `OAUTH2_PROXY_CLIENT_SECRET` | OAuth2 客户端密钥 | +| `OAUTH2_PROXY_COOKIE_SECRET` | 随机字符串,用于加密 Cookie | +| `OAUTH2_PROXY_REDIRECT_URL` | 回调地址 | +| `OAUTH2_PROXY_EMAIL_DOMAINS` | 允许的邮箱域,`*` 表示允许所有 | + +--- + +## 参考资料 + +* [官方文档](https://oauth2-proxy.github.io/oauth2-proxy/) +* [GitHub 仓库](https://github.com/oauth2-proxy/oauth2-proxy) diff --git a/oauth2-proxy/data.yml b/oauth2-proxy/data.yml new file mode 100644 index 000000000..1692bb03c --- /dev/null +++ b/oauth2-proxy/data.yml @@ -0,0 +1,32 @@ +name: OAuth2 Proxy +tags: + - 开发工具 + - 安全 +title: OAuth2身份认证代理,支持多种身份提供商的统一登录解决方案 +description: 一个支持多种OAuth2和OIDC身份提供商的认证代理工具,常用于在现有Web服务前添加统一登录认证。 +additionalProperties: + key: oauth2-proxy + name: OAuth2 Proxy + tags: + - DevTool + - Security + shortDescZh: OAuth2认证代理,支持多身份提供商的一站式登录认证解决方案 + shortDescEn: OAuth2 authentication proxy with multi-provider SSO support + type: website + crossVersionUpdate: true + limit: 0 + website: https://oauth2-proxy.github.io/oauth2-proxy/ + github: https://github.com/oauth2-proxy/oauth2-proxy + document: https://oauth2-proxy.github.io/oauth2-proxy/ + description: + en: A reverse proxy and authentication provider that provides login using OAuth2 and OpenID Connect providers, suitable for securing existing web services with SSO. + zh: 一个反向代理和认证中间件,支持多种OAuth2和OIDC身份提供商,为现有Web服务提供统一登录保护。 + zh-Hant: 一個反向代理與認證中介軟體,支援多種OAuth2和OIDC身份提供商,為既有Web服務提供單一登入保護。 + ja: 複数のOAuth2およびOIDCプロバイダーに対応した認証プロキシで、既存のWebサービスにSSO保護を追加可能。 + ms: Proksi pengesahan yang menyokong penyedia OAuth2 dan OIDC pelbagai, sesuai untuk melindungi perkhidmatan web sedia ada dengan SSO. + pt-br: Proxy de autenticação que suporta múltiplos provedores OAuth2 e OIDC, adequado para proteger serviços web existentes com SSO. + ru: Обратный прокси и провайдер аутентификации, поддерживающий OAuth2 и OIDC, обеспечивающий единую точку входа для существующих веб-сервисов. + ko: 다양한 OAuth2 및 OIDC 제공자를 지원하는 인증 프록시로, 기존 웹 서비스에 단일 로그인 보호를 제공합니다. + architectures: + - amd64 + - arm64 diff --git a/oauth2-proxy/logo.png b/oauth2-proxy/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f65af7b283f3eed941c248558b985814844b030a GIT binary patch literal 9087 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 literal 0 HcmV?d00001