From a70df0d50522e7b77dd21ee86acb0358ea89f61a Mon Sep 17 00:00:00 2001 From: isboyjc Date: Sun, 29 Mar 2026 04:11:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20environment=20variabl?= =?UTF-8?q?e=20support=20for=20proxy=20authentication=20and=20geo-blocking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced `.env.example` for environment variable configuration. - Updated `docker-compose.yml` to utilize environment variables for proxy settings. - Implemented proxy authentication with Basic Auth and geo-blocking based on country codes. - Added `GEO_FILTER.md` for documentation on geo-filtering configuration. - Enhanced logging to indicate authentication status and blocked countries during proxy server startup. --- .env.example | 35 +++ .github/DOCKER_SETUP.md | 127 ++++++++++ .github/workflows/docker-image.yml | 72 ++++++ .gitignore | 3 + Dockerfile | 2 +- GEO_FILTER.md | 242 ++++++++++++++++++ README.md | 385 ++++++++++++++++++++++------- config/config.go | 44 ++++ docker-compose.yml | 34 +-- main.go | 8 +- proxy/server.go | 53 +++- storage/storage.go | 25 +- test/AUTH_TEST.md | 165 +++++++++++++ validator/validator.go | 13 +- 14 files changed, 1088 insertions(+), 120 deletions(-) create mode 100644 .env.example create mode 100644 .github/DOCKER_SETUP.md create mode 100644 .github/workflows/docker-image.yml create mode 100644 GEO_FILTER.md create mode 100644 test/AUTH_TEST.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..971c67f --- /dev/null +++ b/.env.example @@ -0,0 +1,35 @@ +# Docker 容器配置 +CONTAINER_NAME=goproxy + +# 端口绑定配置 +# PROXY_HOST: 代理服务绑定地址 +# - 127.0.0.1 = 仅本地访问(推荐,最安全) +# - 0.0.0.0 = 对外开放(如启用需配合认证或防火墙) +PROXY_HOST=127.0.0.1 +STABLE_PORT=7776 # 最低延迟代理端口 +RANDOM_PORT=7777 # 随机轮换代理端口 + +# 地理过滤配置 +# 屏蔽指定国家代码的出口代理(逗号分隔,如 CN,RU,KP) +# 留空 = 不屏蔽任何国家 +# 默认屏蔽中国大陆(CN),香港(HK)、澳门(MO)、台湾(TW)不受影响 +BLOCKED_COUNTRIES=CN + +# 代理服务认证配置 +# ⚠️ 当 PROXY_HOST=0.0.0.0(对外开放)时,强烈建议启用认证! +# 使用方式:curl -x http://username:password@host:port https://example.com +PROXY_AUTH_ENABLED=false # 是否启用代理认证(true/false) +PROXY_AUTH_USERNAME=proxy # 代理认证用户名 +PROXY_AUTH_PASSWORD= # 代理认证密码(留空=不启用认证) +# 示例:PROXY_AUTH_PASSWORD=my_secure_password_123 + +# WebUI 配置 +WEBUI_HOST=0.0.0.0 # WebUI 可对外访问(有登录认证保护) +WEBUI_PORT=7778 +WEBUI_PASSWORD=goproxy # ⚠️ 生产环境请修改为强密码 + +# 数据存储 +DATA_DIR=./data + +# 时区配置 +TZ=Asia/Shanghai diff --git a/.github/DOCKER_SETUP.md b/.github/DOCKER_SETUP.md new file mode 100644 index 0000000..27bdc9f --- /dev/null +++ b/.github/DOCKER_SETUP.md @@ -0,0 +1,127 @@ +# GitHub Actions Docker 镜像自动构建配置 + +本项目使用 GitHub Actions 自动构建并推送 Docker 镜像到多个镜像仓库。 + +## 📦 自动构建触发条件 + +- **Push to main 分支**:自动构建 `latest` 标签 +- **推送版本标签**(如 `v1.0.0`):自动构建版本标签(`1.0.0`, `1.0`, `1`, `latest`) +- **手动触发**:在 GitHub Actions 页面手动运行 + +## 🔧 配置 GitHub Secrets + +### 1. Docker Hub 配置(可选) + +如果要推送到 Docker Hub,需要配置以下 Secrets: + +1. 登录 [Docker Hub](https://hub.docker.com/) +2. 在 **Account Settings > Security** 创建 Access Token +3. 在 GitHub 仓库的 **Settings > Secrets and variables > Actions** 添加: + - `DOCKERHUB_USERNAME`: 你的 Docker Hub 用户名 + - `DOCKERHUB_TOKEN`: 刚才创建的 Access Token + +### 2. GitHub Container Registry(默认已配置) + +GitHub Container Registry (ghcr.io) 无需额外配置,workflow 使用自动提供的 `GITHUB_TOKEN`。 + +**镜像地址**:`ghcr.io/isboyjc/goproxy` + +## 🏗️ 构建特性 + +- ✅ **多架构支持**:`linux/amd64`、`linux/arm64` +- ✅ **多标签发布**:`latest`、版本号(如 `1.0.0`、`1.0`、`1`) +- ✅ **双仓库推送**:Docker Hub + GitHub Container Registry +- ✅ **构建缓存**:使用 GitHub Actions 缓存加速构建 + +## 📋 使用镜像 + +### 从 Docker Hub 拉取 + +```bash +docker pull isboyjc/goproxy:latest +docker run -d \ + --name proxygo \ + -p 127.0.0.1:7776:7776 \ + -p 127.0.0.1:7777:7777 \ + -p 7778:7778 \ + -e WEBUI_PASSWORD=your_password \ + -v "$(pwd)/data:/app/data" \ + isboyjc/goproxy:latest +``` + +### 从 GitHub Container Registry 拉取 + +```bash +docker pull ghcr.io/isboyjc/goproxy:latest +docker run -d \ + --name proxygo \ + -p 127.0.0.1:7776:7776 \ + -p 127.0.0.1:7777:7777 \ + -p 7778:7778 \ + -e WEBUI_PASSWORD=your_password \ + -v "$(pwd)/data:/app/data" \ + ghcr.io/isboyjc/goproxy:latest +``` + +### 使用特定版本 + +```bash +# 使用特定版本号 +docker pull ghcr.io/isboyjc/goproxy:1.0.0 + +# 使用主版本号(自动获取最新的 1.x.x) +docker pull ghcr.io/isboyjc/goproxy:1 +``` + +## 🚀 发布新版本 + +创建并推送版本标签即可触发自动构建: + +```bash +# 创建版本标签 +git tag -a v1.0.0 -m "Release version 1.0.0" + +# 推送标签到 GitHub +git push origin v1.0.0 +``` + +GitHub Actions 会自动: +1. 构建多架构镜像 +2. 推送到 Docker Hub 和 GHCR +3. 创建多个标签(`1.0.0`, `1.0`, `1`, `latest`) + +## 🌐 端口绑定说明 + +默认示例使用 `127.0.0.1:7776` 和 `127.0.0.1:7777`(仅本地访问): + +- **场景**:在部署机器上本地使用代理 +- **安全**:最安全,代理服务不对外暴露 +- **限制**:其他机器无法使用该代理 + +**如需对外开放代理服务**(局域网/VPS 多机共享),修改端口绑定: + +```bash +docker run -d \ + --name proxygo \ + -p 7776:7776 \ # ⚠️ 改为对外开放(无认证) + -p 7777:7777 \ # ⚠️ 改为对外开放(无认证) + -p 7778:7778 \ + -e WEBUI_PASSWORD=strong_password \ + -v "$(pwd)/data:/app/data" \ + ghcr.io/isboyjc/goproxy:latest +``` + +**⚠️ 安全警告**: +- 代理服务本身**无认证机制**,对外开放后任何人都可使用 +- 建议配合**防火墙白名单**或**云安全组**限制访问 IP +- WebUI 有登录认证,可以安全对外开放 + +更多部署场景说明,请查看 [README - 部署场景配置](../README.md#部署场景配置) + +--- + +## 📝 注意事项 + +- **权限配置**:首次推送到 GHCR 可能需要在仓库 **Settings > Actions > General** 中设置 **Workflow permissions** 为 `Read and write permissions` +- **GHCR 可见性**:默认镜像为私有,可在 **Packages** 页面修改为公开 +- **Docker Hub 可选**:如果不需要推送到 Docker Hub,可在 workflow 文件中删除相关配置,或不配置 DOCKERHUB_* secrets(workflow 会跳过 Docker Hub 登录失败的步骤) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..8d6ec0f --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,72 @@ +name: Docker Image CI + +on: + push: + branches: + - main + tags: + - 'v*' + workflow_dispatch: + +env: + REGISTRY_DOCKERHUB: docker.io + REGISTRY_GHCR: ghcr.io + IMAGE_NAME: isboyjc/goproxy + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + 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: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.REGISTRY_DOCKERHUB }}/${{ env.IMAGE_NAME }} + ${{ env.REGISTRY_GHCR }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,value=latest,enable={{is_default_branch}} + + - name: Login to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY_DOCKERHUB }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Login to GitHub Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY_GHCR }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 10c0f35..b16e952 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,9 @@ config.json data/ !data/.gitkeep +# Environment variables +.env + # Temporary files *.tmp *.bak diff --git a/Dockerfile b/Dockerfile index 42c03c1..4c2d488 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,6 @@ ENV TZ=Asia/Shanghai WORKDIR /app COPY --from=builder /app/proxy-pool . -EXPOSE 7777 7778 +EXPOSE 7776 7777 7778 CMD ["./proxy-pool"] diff --git a/GEO_FILTER.md b/GEO_FILTER.md new file mode 100644 index 0000000..b664d61 --- /dev/null +++ b/GEO_FILTER.md @@ -0,0 +1,242 @@ +# 地理过滤配置指南 + +GoProxy 支持通过国家代码过滤代理的出口位置,让你可以灵活控制代理池的地理分布。 + +## 🌍 配置方式 + +### 环境变量配置 + +通过 `BLOCKED_COUNTRIES` 环境变量设置需要屏蔽的国家代码: + +```bash +# 默认:屏蔽中国大陆(CN) +BLOCKED_COUNTRIES=CN + +# 屏蔽多个国家(逗号分隔) +BLOCKED_COUNTRIES=CN,RU,KP,IR + +# 不屏蔽任何国家(留空) +BLOCKED_COUNTRIES= +``` + +### Docker Compose 配置 + +编辑 `.env` 文件: + +```bash +# 屏蔽中国大陆和俄罗斯 +BLOCKED_COUNTRIES=CN,RU +``` + +启动服务: +```bash +docker compose up -d +``` + +### Docker Run 配置 + +```bash +docker run -d --name proxygo \ + -p 127.0.0.1:7776:7776 -p 127.0.0.1:7777:7777 -p 7778:7778 \ + -e BLOCKED_COUNTRIES=CN,RU \ + -e WEBUI_PASSWORD=your_password \ + -v "$(pwd)/data:/app/data" \ + ghcr.io/isboyjc/goproxy:latest +``` + +### 本地运行配置 + +```bash +export BLOCKED_COUNTRIES=CN,RU,KP +go run . +``` + +## 🗺️ 工作机制 + +### 双重过滤 + +地理过滤在两个阶段生效: + +**1. 启动清理阶段** +- 程序启动时自动扫描数据库 +- 删除所有屏蔽国家出口的代理 +- 日志输出:`🧹 已清理 X 个屏蔽国家出口代理 (屏蔽: [CN RU])` + +**2. 验证阶段** +- 新抓取的代理在验证时检查出口位置 +- 如果出口国家在屏蔽列表中,直接拒绝入池 +- 不会占用池子容量 + +### 国家代码识别 + +系统使用 **ISO 3166-1 alpha-2** 标准的两位国家代码: + +``` +出口位置格式:CC City +示例: + CN Beijing → 国家代码 CN(中国大陆) + HK Hong Kong → 国家代码 HK(香港) + US New York → 国家代码 US(美国) + RU Moscow → 国家代码 RU(俄罗斯) +``` + +匹配规则:`exit_location LIKE 'CC %'`(国家代码 + 空格 + 城市) + +## 📋 常用国家代码 + +### 亚洲 +| 代码 | 国家/地区 | 代码 | 国家/地区 | +|------|----------|------|----------| +| `CN` | 中国大陆 | `HK` | 香港 | +| `TW` | 台湾 | `MO` | 澳门 | +| `JP` | 日本 | `KR` | 韩国 | +| `SG` | 新加坡 | `IN` | 印度 | +| `TH` | 泰国 | `VN` | 越南 | +| `KP` | 朝鲜 | `IR` | 伊朗 | + +### 欧洲 +| 代码 | 国家 | 代码 | 国家 | +|------|------|------|------| +| `RU` | 俄罗斯 | `GB` | 英国 | +| `DE` | 德国 | `FR` | 法国 | +| `NL` | 荷兰 | `SE` | 瑞典 | +| `UA` | 乌克兰 | `PL` | 波兰 | + +### 美洲 +| 代码 | 国家 | 代码 | 国家 | +|------|------|------|------| +| `US` | 美国 | `CA` | 加拿大 | +| `BR` | 巴西 | `MX` | 墨西哥 | +| `AR` | 阿根廷 | `CL` | 智利 | + +完整国家代码列表:[ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) + +## 🎯 使用场景 + +### 场景 1:屏蔽中国大陆(默认) + +```bash +BLOCKED_COUNTRIES=CN +``` + +**适用**: +- 需要海外 IP 代理 +- 避免被识别为中国大陆流量 +- 保留香港、澳门、台湾代理 + +### 场景 2:屏蔽多个敏感地区 + +```bash +BLOCKED_COUNTRIES=CN,RU,KP,IR,SY +``` + +**适用**: +- 合规要求(避免某些国家的 IP) +- 地缘政治考虑 +- 防止特定地区的代理质量问题 + +### 场景 3:仅使用欧美代理 + +```bash +# 屏蔽亚洲、非洲、中东等地区(示例,需根据实际需求调整) +BLOCKED_COUNTRIES=CN,IN,TH,VN,ID,PH,BD,PK,IR,IQ,SA,EG,NG,ZA +``` + +### 场景 4:不做地理限制 + +```bash +BLOCKED_COUNTRIES= +``` + +**适用**: +- 需要最大化代理池容量 +- 对地理位置无特殊要求 +- 测试和开发环境 + +## 📊 实时查看 + +### 查看当前屏蔽配置 + +启动日志会显示: +``` +[main] 🧹 已清理 15 个屏蔽国家出口代理 (屏蔽: [CN RU KP]) +``` + +### 查看池中国家分布 + +通过 WebUI 的**出口国家筛选器**可以看到当前池中所有国家的代理分布。 + +### 数据库查询 + +```bash +# 查看所有代理的国家分布 +sqlite3 data/proxy.db " + SELECT SUBSTR(exit_location, 1, 2) AS country, COUNT(*) AS count + FROM proxies + GROUP BY country + ORDER BY count DESC; +" + +# 查看特定国家的代理 +sqlite3 data/proxy.db " + SELECT address, exit_ip, exit_location, latency + FROM proxies + WHERE exit_location LIKE 'US %'; +" +``` + +## ⚠️ 注意事项 + +1. **大小写不敏感**:国家代码会自动转为大写(`cn` → `CN`) +2. **空格自动处理**:前后空格会自动去除 +3. **重启生效**:修改配置后需要重启服务 +4. **已有代理清理**:启动时会清理数据库中的屏蔽国家代理 +5. **香港独立识别**: + - 中国大陆代码:`CN` + - 香港代码:`HK`(独立的国家代码) + - 设置 `BLOCKED_COUNTRIES=CN` 不会影响香港代理 + +## 🧪 测试验证 + +### 测试 1:屏蔽中国大陆 + +```bash +# 启动服务 +export BLOCKED_COUNTRIES=CN +go run . + +# 查看日志(应该显示清理信息) +# [main] 🧹 已清理 X 个屏蔽国家出口代理 (屏蔽: [CN]) + +# 查看 WebUI 的代理列表(不应该有 CN 开头的出口位置) +``` + +### 测试 2:屏蔽多个国家 + +```bash +export BLOCKED_COUNTRIES=CN,RU,KP +go run . + +# 使用测试脚本验证 +./test/test_proxy.sh + +# 观察输出的国旗 emoji(不应该有 🇨🇳 🇷🇺 🇰🇵) +``` + +### 测试 3:不屏蔽任何国家 + +```bash +export BLOCKED_COUNTRIES= +go run . + +# 查看日志(不应该有清理信息) +# 代理列表中可能出现各种国家的代理 +``` + +## 💡 最佳实践 + +1. **默认配置**:保持默认 `BLOCKED_COUNTRIES=CN`,适合大多数场景 +2. **生产环境**:根据业务合规要求设置屏蔽国家 +3. **测试环境**:可以设置为空(`BLOCKED_COUNTRIES=`)以获取更多代理 +4. **定期调整**:根据代理质量和可用性调整屏蔽列表 +5. **配合筛选**:利用 WebUI 的国家筛选器查看各国代理分布,辅助决策 diff --git a/README.md b/README.md index 8996f35..475ed45 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,39 @@ > **智能代理池系统** — 基于 Go 的轻量级、低资源消耗、自适应的代理池服务 +[![Docker Hub](https://img.shields.io/docker/v/isboyjc/goproxy?label=Docker%20Hub&logo=docker)](https://hub.docker.com/r/isboyjc/goproxy) +[![GitHub Container Registry](https://img.shields.io/badge/GHCR-latest-blue?logo=github)](https://github.com/isboyjc/GoProxy/pkgs/container/goproxy) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) +[![Go Version](https://img.shields.io/badge/Go-1.25-00ADD8?logo=go)](https://go.dev/) + GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格验证(出口 IP + 位置 + 延迟)后加入智能代理池,对外提供统一的 HTTP 代理服务。系统采用质量分级、智能补充、自动优化等机制,确保代理池始终保持高质量和稳定性。 +**GitHub**:[github.com/isboyjc/GoProxy](https://github.com/isboyjc/GoProxy) + ![](https://cdn.isboyjc.com/img/Xnip2026-03-29_03-35-06.png) +## 📑 目录 + +- [核心特性](#-核心特性) +- [项目结构](#-项目结构) +- [快速开始](#-快速开始) +- [Docker 部署](#-docker-部署) - 推荐使用 docker-compose +- [配置说明](#️-配置说明) - 环境变量、池子参数、认证、地理过滤 +- [WebUI 使用指南](#-webui-使用指南) - 双角色权限、操作功能 +- [核心架构](#️-核心架构) - 智能池子、状态转换、质量分级 +- [数据库 Schema](#-数据库-schema) +- [代理源](#-代理源) +- [测试代理服务](#-测试代理服务) +- [常见问题](#-常见问题) +- [致谢与声明](#-致谢与声明) + +**扩展文档**: +- [Docker 镜像发布配置](.github/DOCKER_SETUP.md) - GitHub Actions CI/CD 设置 +- [地理过滤配置指南](GEO_FILTER.md) - 国家代码、使用场景、测试方法 +- [认证功能测试](test/AUTH_TEST.md) - 代理认证测试和多语言客户端示例 +- [基础功能测试](test/TEST_GUIDE.md) - 测试脚本使用指南 +- [架构设计文档](POOL_DESIGN.md) - 完整的系统设计和实现细节 + ## ✨ 核心特性 ### 🎯 智能池子机制 @@ -39,6 +68,7 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格 - **7776 端口(最低延迟)**:固定使用延迟最低的代理,除非失败才切换,适合长连接和流媒体 - **自动切换**:两个端口都支持失败自动重试,7776 失败后切换到次优代理 - **共享池子**:两个端口使用同一个代理池,统一管理和优化 +- **可选认证**:支持 Basic Auth 认证保护,对外开放时可启用 ### 🎨 黑客风格 WebUI - **Matrix 美学**:荧光绿 + 纯黑背景,CRT 扫描线效果,JetBrains Mono 等宽字体 @@ -58,26 +88,35 @@ GoProxy 从多个公开代理源自动抓取 HTTP/SOCKS5 代理,通过严格 ```text . -├── main.go # 程序入口,协调所有模块 -├── config/ # 配置系统(池子容量、延迟标准、验证参数等) -├── pool/ # 🆕 池子管理器(入池判断、替换逻辑、状态计算) -├── fetcher/ # 🆕 智能抓取器(源分组、断路器、按需抓取) -│ ├── fetcher.go # 多模式抓取逻辑 -│ ├── source_manager.go # 源状态管理和断路器 -│ └── ip_query.go # IP查询限流和多源降级 -├── validator/ # 代理验证(连接测试 + 出口IP检测) -├── checker/ # 🆕 分批健康检查器 -├── optimizer/ # 🆕 优化轮换器(定时优化池子质量) -├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表) -├── proxy/ # 对外 HTTP 代理服务 -├── webui/ # 🆕 黑客风格 WebUI(健康仪表盘、配置界面) -├── logger/ # 内存日志收集 -├── test/ # 🧪 测试脚本(Bash/Go/Python) -│ ├── test_proxy.sh # Bash 测试脚本 -│ ├── test_proxy.go # Go 测试脚本 -│ ├── test_proxy.py # Python 测试脚本 -│ └── TEST_GUIDE.md # 测试指南 -└── POOL_DESIGN.md # 🆕 完整架构设计文档 +├── main.go # 程序入口,协调所有模块 +├── config/ # 配置系统(池子容量、延迟标准、验证参数等) +├── pool/ # 🆕 池子管理器(入池判断、替换逻辑、状态计算) +├── fetcher/ # 🆕 智能抓取器(源分组、断路器、按需抓取) +│ ├── fetcher.go # 多模式抓取逻辑 +│ ├── source_manager.go # 源状态管理和断路器 +│ └── ip_query.go # IP查询限流和多源降级 +├── validator/ # 代理验证(连接测试 + 出口IP检测 + 地理过滤) +├── checker/ # 🆕 分批健康检查器 +├── optimizer/ # 🆕 优化轮换器(定时优化池子质量) +├── storage/ # 🆕 扩展存储层(质量等级、使用统计、源状态表) +├── proxy/ # 🆕 对外 HTTP 代理服务(双端口 + 可选认证) +├── webui/ # 🆕 黑客风格 WebUI(健康仪表盘、配置界面、RBAC) +├── logger/ # 内存日志收集 +├── test/ # 🧪 测试脚本与文档 +│ ├── test_proxy.sh # Bash 测试脚本 +│ ├── test_proxy.go # Go 测试脚本 +│ ├── test_proxy.py # Python 测试脚本 +│ ├── TEST_GUIDE.md # 基础功能测试指南 +│ └── AUTH_TEST.md # 🆕 认证功能测试指南 +├── .github/ # 🆕 GitHub Actions CI/CD +│ ├── workflows/docker-image.yml # 自动构建多架构镜像 +│ └── DOCKER_SETUP.md # Docker 镜像发布配置指南 +├── .env.example # 🆕 环境变量配置模板 +├── docker-compose.yml # 🆕 Docker Compose 配置(自动更新镜像) +├── Dockerfile # Docker 构建文件 +├── GEO_FILTER.md # 🆕 地理过滤配置指南 +├── POOL_DESIGN.md # 完整架构设计文档 +└── README.md # 本文件 ``` ## 🚀 快速开始 @@ -100,15 +139,16 @@ go build -o proxygo . ``` 程序启动后会: -1. 加载配置(优先 `config.json`) +1. 加载配置(环境变量 + `config.json`) 2. 初始化数据库和限流器 -3. 启动 WebUI(`:7778`) -4. 立即执行智能填充(按需抓取 + 严格验证) -5. 启动后台协程: - - 状态监控(每分钟) +3. 清理不符合条件的代理(屏蔽国家出口、无地理信息) +4. 启动 WebUI(`:7778`) +5. 立即执行智能填充(按需抓取 + 严格验证) +6. 启动后台协程: + - 状态监控(每 30 秒) - 健康检查(默认 5 分钟) - 优化轮换(默认 30 分钟) -6. 启动两个代理服务: +7. 启动两个代理服务(支持可选认证): - `:7776` - 最低延迟模式(稳定连接) - `:7777` - 随机轮换模式(IP 多样性) @@ -151,6 +191,7 @@ curl -x http://127.0.0.1:7776 https://httpbin.org/ip #### 环境变量配置 +**本地使用(无认证)**: ```bash # 使用随机模式 export http_proxy=http://127.0.0.1:7777 @@ -161,6 +202,13 @@ export http_proxy=http://127.0.0.1:7776 export https_proxy=http://127.0.0.1:7776 ``` +**远程使用(带认证)**: +```bash +# 格式:http://username:password@host:port +export http_proxy=http://proxy:your_password@192.168.1.100:7777 +export https_proxy=http://proxy:your_password@192.168.1.100:7777 +``` + #### 端口对比 | 特性 | 7777(随机轮换) | 7776(最低延迟) | @@ -190,50 +238,130 @@ export https_proxy=http://127.0.0.1:7776 ## 🐳 Docker 部署 -### 使用 Dockerfile +> 💡 **自动构建**:GitHub Actions 自动构建多架构镜像(amd64/arm64),支持 Docker Hub 和 GHCR 双仓库推送。 +> 详细配置说明请查看 [`.github/DOCKER_SETUP.md`](.github/DOCKER_SETUP.md) + +### 快速启动(推荐) + +使用 docker-compose,支持环境变量配置,自动拉取最新镜像: ```bash -docker build -t proxygo . -docker run -d \ - --name proxygo \ - -p 127.0.0.1:7776:7776 \ - -p 127.0.0.1:7777:7777 \ - -p 7778:7778 \ - -e TZ=Asia/Shanghai \ - -e WEBUI_PASSWORD=your_password \ - -e DATA_DIR=/app/data \ - -v "$(pwd)/data:/app/data" \ - proxygo +# 1. 复制环境变量模板 +cp .env.example .env + +# 2. 根据需要编辑 .env(可选,默认配置即可使用) +# vim .env + +# 3. 启动服务(自动拉取最新镜像) +docker compose up -d + +# 4. 访问 WebUI +# http://localhost:7778(默认密码:goproxy) ``` -### 使用 docker-compose +### 使用 Docker Hub 镜像 ```bash -docker compose up -d --build +docker pull isboyjc/goproxy:latest +docker run -d --name proxygo \ + -p 127.0.0.1:7776:7776 -p 127.0.0.1:7777:7777 -p 7778:7778 \ + -e WEBUI_PASSWORD=your_password -v "$(pwd)/data:/app/data" \ + isboyjc/goproxy:latest ``` -### WebUI 公网访问配置 - -得益于**双角色权限系统**,WebUI 可以安全地对外开放: +### 使用 GitHub Container Registry 镜像 ```bash -docker run -d \ - --name proxygo \ - -p 127.0.0.1:7776:7776 \ - -p 127.0.0.1:7777:7777 \ - -p 0.0.0.0:7778:7778 \ # 公网访问 WebUI - -e TZ=Asia/Shanghai \ - -e WEBUI_PASSWORD=strong_password \ - -e DATA_DIR=/app/data \ - -v "$(pwd)/data:/app/data" \ - proxygo +docker pull ghcr.io/isboyjc/goproxy:latest +docker run -d --name proxygo \ + -p 127.0.0.1:7776:7776 -p 127.0.0.1:7777:7777 -p 7778:7778 \ + -e WEBUI_PASSWORD=your_password -v "$(pwd)/data:/app/data" \ + ghcr.io/isboyjc/goproxy:latest ``` -**安全说明**: -- 访客(未登录)只能查看数据,无法执行任何操作 -- 所有写操作(抓取、删除、配置修改)都需要管理员密码 -- 建议设置强密码(通过 `WEBUI_PASSWORD` 环境变量) -- 代理服务端口(7776、7777)建议仅绑定内网(`127.0.0.1`) +### 环境变量配置 + +**核心配置**(`.env` 文件): + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `PROXY_HOST` | `127.0.0.1` | 代理服务绑定地址(`0.0.0.0` = 对外开放) | +| `BLOCKED_COUNTRIES` | `CN` | 屏蔽的国家代码(逗号分隔,如 `CN,RU`,留空=不屏蔽) | +| `PROXY_AUTH_ENABLED` | `false` | 是否启用代理认证 | +| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 | +| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码 | +| `WEBUI_PASSWORD` | `goproxy` | WebUI 登录密码 | + +完整环境变量列表请查看 `.env.example` 文件。 + +**常用配置示例**: + +```bash +# 场景 1:本地使用(默认,最安全) +# 直接运行 docker compose up -d 即可 + +# 场景 2:对外开放 + 启用认证(推荐) +cat > .env << EOF +PROXY_HOST=0.0.0.0 +PROXY_AUTH_ENABLED=true +PROXY_AUTH_USERNAME=myuser +PROXY_AUTH_PASSWORD=secure_pass_123 +WEBUI_PASSWORD=admin_pass_456 +BLOCKED_COUNTRIES=CN +EOF +docker compose up -d + +# 场景 3:屏蔽多个国家 +cat > .env << EOF +BLOCKED_COUNTRIES=CN,RU,KP,IR +WEBUI_PASSWORD=admin_pass +EOF +docker compose up -d + +# 场景 4:不屏蔽任何国家 +cat > .env << EOF +BLOCKED_COUNTRIES= +WEBUI_PASSWORD=admin_pass +EOF +docker compose up -d +``` + +### 安全部署配置 + +**默认配置**:代理服务仅本地访问(`127.0.0.1`),WebUI 可对外访问。 + +**如需对外开放代理服务**,强烈建议启用认证: + +| 使用场景 | 配置建议 | 安全级别 | +|---------|---------|---------| +| **个人 VPS + 本地使用** | `PROXY_HOST=127.0.0.1`(默认) | ⭐⭐⭐⭐⭐ 最安全 | +| **团队共享 + 认证** | `PROXY_HOST=0.0.0.0` + 启用认证 | ⭐⭐⭐⭐ 安全 | +| **内网 + 防火墙** | `PROXY_HOST=0.0.0.0` + iptables 白名单 | ⭐⭐⭐ 中等 | + +**启用代理认证**(编辑 `.env`): + +```bash +PROXY_HOST=0.0.0.0 +PROXY_AUTH_ENABLED=true +PROXY_AUTH_USERNAME=myuser +PROXY_AUTH_PASSWORD=secure_pass_123 +``` + +**客户端使用(带认证)**: + +```bash +# 环境变量方式 +export http_proxy=http://myuser:secure_pass_123@server-ip:7777 +export https_proxy=http://myuser:secure_pass_123@server-ip:7777 + +# curl 直接指定 +curl -x http://myuser:secure_pass_123@server-ip:7777 https://httpbin.org/ip + +# Python requests +proxies = {'http': 'http://myuser:secure_pass_123@server-ip:7777', 'https': '...'} +``` + +> 💡 **认证测试指南**:详细的认证功能测试和多语言客户端示例,请查看 [`test/AUTH_TEST.md`](test/AUTH_TEST.md) ## ⚙️ 配置说明 @@ -268,6 +396,16 @@ docker run -d \ | `stable_proxy_port` | `:7776` | 最低延迟代理端口 | | `webui_port` | `:7778` | WebUI 端口 | +**代理认证配置** + +| 参数 | 默认值 | 说明 | +| --- | --- | --- | +| `proxy_auth_enabled` | `false` | 是否启用代理认证(对外开放时建议启用) | +| `proxy_auth_username` | `proxy` | 代理认证用户名 | +| `proxy_auth_password_hash` | 空 | 代理认证密码 SHA256 哈希(通过环境变量 `PROXY_AUTH_PASSWORD` 设置原始密码) | + +> 💡 **注意**:代理认证配置通过**环境变量**设置,不在 `config.json` 中。启动时从 `PROXY_AUTH_ENABLED`、`PROXY_AUTH_USERNAME`、`PROXY_AUTH_PASSWORD` 环境变量读取。 + **池子容量配置** | 参数 | 默认值 | 说明 | 推荐范围 | @@ -349,17 +487,28 @@ docker run -d \ ### 固定配置 -以下配置在代码中固定,无需调整: +以下配置在代码中固定或通过环境变量设置,无需在 `config.json` 中调整: | 配置项 | 值 | 说明 | | --- | --- | --- | -| `WebUIPort` | `:7778` | Web 管理后台端口 | -| `ProxyPort` | `:7777` | 对外统一代理端口 | +| `WebUIPort` | `:7778` | WebUI 端口 | +| `ProxyPort` | `:7777` | 随机轮换代理端口 | +| `StableProxyPort` | `:7776` | 最低延迟代理端口 | | `ValidateURL` | `http://www.gstatic.com/generate_204` | 验证目标地址 | -| `IPQueryRateLimit` | `10` | IP 查询限流(次/秒) | -| `SourceFailThreshold` | `3` | 源降级阈值 | -| `SourceDisableThreshold` | `5` | 源禁用阈值 | -| `SourceCooldownMinutes` | `30` | 源禁用冷却时间(分钟) | +| `IPQueryRateLimit` | `10 次/秒` | IP 查询限流 | +| `SourceFailThreshold` | `3` | 源降级阈值(连续失败) | +| `SourceDisableThreshold` | `5` | 源禁用阈值(连续失败) | +| `SourceCooldownMinutes` | `30` | 源禁用冷却时间 | +| `MaxRetry` | `3` | 代理请求失败重试次数 | + +**环境变量配置**(启动时读取): + +| 环境变量 | 默认值 | 说明 | +| --- | --- | --- | +| `PROXY_AUTH_ENABLED` | `false` | 是否启用代理认证 | +| `PROXY_AUTH_USERNAME` | `proxy` | 代理认证用户名 | +| `PROXY_AUTH_PASSWORD` | 空 | 代理认证密码(原始密码,自动哈希) | +| `BLOCKED_COUNTRIES` | `CN` | 屏蔽的国家代码(逗号分隔,如 `CN,RU,KP`,留空=不屏蔽) | ## 🎨 WebUI 使用指南 @@ -415,23 +564,29 @@ GoProxy WebUI 支持**访客模式**和**管理员模式**: ### 代理注册表 **表格字段** -- **Grade**:质量等级(S/A/B/C) +- **Grade**:质量等级(S/A/B/C,基于延迟计算) - **Protocol**:协议类型(HTTP/SOCKS5) -- **Address**:代理地址(host:port) -- **Exit IP**:出口 IP -- **Location**:出口地理位置(国旗 + 国家代码 + 城市) -- **Latency**:延迟(毫秒,颜色编码) -- **Usage**:使用次数 / 成功次数 -- **Action**:删除按钮 +- **Address**:代理地址(host:port),点击可复制 +- **Exit IP**:代理的出口 IP 地址 +- **Location**:出口地理位置(国旗 emoji + 国家代码 + 城市) +- **Latency**:连接延迟(毫秒,动态颜色编码:绿/黄/橙/红) +- **Usage**:使用统计(使用总次数 / 成功次数,成功率指标) +- **Action**:操作按钮(刷新单个代理、删除代理,管理员可见) **操作功能** -- **筛选**:All / HTTP / SOCKS5(所有用户可用) -- **点击复制地址**:点击代理地址单元格复制到剪贴板(所有用户可用) -- **Fetch Proxies**:手动触发智能抓取(⚡ 管理员专属) -- **Refresh Latency**:重新验证所有代理并更新延迟(⚡ 管理员专属) -- **刷新单个代理**:点击行内刷新按钮验证单个代理(⚡ 管理员专属) -- **删除代理**:点击行内删除按钮移除指定代理(⚡ 管理员专属) -- **Configure Pool**:打开配置界面(⚡ 管理员专属) + +**所有用户可用**: +- **协议筛选**:下拉选择协议类型(全部/HTTP/SOCKS5) +- **国家筛选**:下拉选择出口国家(全部/动态国家列表,带国旗 emoji) +- **点击复制地址**:点击代理地址单元格直接复制到剪贴板 +- **查看数据**:池子状态、质量分布、系统日志 + +**管理员专属**(需登录): +- **Fetch Proxies**:手动触发智能抓取 +- **Refresh Latency**:重新验证所有代理并更新延迟 +- **刷新单个代理**:点击行内刷新按钮验证单个代理 +- **删除代理**:点击行内删除按钮移除指定代理 +- **Configure Pool**:打开配置界面修改池子参数 ### 配置界面(⚡ 管理员专属) @@ -488,9 +643,9 @@ GoProxy WebUI 支持**访客模式**和**管理员模式**: ### 状态转换机制 ```text -Healthy (总数≥80% 且 各协议≥80%槽位) +Healthy (总数≥95% 且 各协议≥80%槽位) ↓ 代理失效 -Warning (总数<80% 或 任一协议<80%) +Warning (总数<95% 或 任一协议<80%) ↓ 继续失效 Critical (总数<50% 或 任一协议<20%槽位) ↓ 继续失效 @@ -499,12 +654,14 @@ Emergency (总数<10% 或 单协议缺失) └─ 自动触发紧急抓取 ─┘ ``` +> 💡 **自动补充阈值**:当总数低于 95% 时进入 Warning 状态并触发自动补充,确保池子始终接近满容量运行。 + ### 抓取模式选择 | 池子状态 | 抓取模式 | 使用源 | 触发条件 | | --- | --- | --- | --- | | Emergency | 紧急模式 | 所有可用源 | 单协议缺失或总数<10% | -| Critical/Warning | 补充模式 | 快更新源 | 总数<80%或协议不均 | +| Critical/Warning | 补充模式 | 快更新源 | 总数<95%或协议不均 | | Healthy | 优化模式 | 慢更新源(随机2-3个) | 定时触发(30分钟) | ### 质量分级标准 @@ -648,8 +805,8 @@ A: ### Q: 池子状态如何计算? A: -- **Healthy**:总数 ≥80% 且各协议 ≥80% 槽位 -- **Warning**:总数 <80% 或任一协议 <80% 槽位 +- **Healthy**:总数 ≥95% 且各协议 ≥80% 槽位 +- **Warning**:总数 <95% 或任一协议 <80% 槽位 - **Critical**:总数 <50% 或任一协议 <20% 槽位 - **Emergency**:总数 <10% 或单协议缺失 @@ -665,6 +822,31 @@ A: - IP 查询有限流(10 次/秒) - 部分代理可能不支持 IP 查询 - 系统会在后续健康检查中补全信息 +- 没有出口信息的代理会在启动时被自动清理 + +### Q: 如何配置地理过滤? +A: +通过 `BLOCKED_COUNTRIES` 环境变量配置需要屏蔽的国家: + +```bash +# 默认屏蔽中国大陆(CN) +BLOCKED_COUNTRIES=CN + +# 屏蔽多个国家(逗号分隔) +BLOCKED_COUNTRIES=CN,RU,KP + +# 不屏蔽任何国家(留空) +BLOCKED_COUNTRIES= +``` + +**工作机制**: +- **验证阶段**:检测到屏蔽国家出口直接拒绝入池 +- **启动清理**:自动删除数据库中屏蔽国家的代理 +- **精确匹配**:使用 ISO 3166-1 alpha-2 国家代码(CN、HK、US 等) + +**常用国家代码**:`CN`=中国大陆 | `HK`=香港 | `RU`=俄罗斯 | `US`=美国 | `JP`=日本 | `SG`=新加坡 + +> 📖 **详细配置指南**:更多国家代码、使用场景、测试方法,请查看 [`GEO_FILTER.md`](./GEO_FILTER.md) ### Q: 资源消耗如何? A: @@ -675,11 +857,24 @@ A: - 按需抓取,避免无效流量 - 健康检查批次小(20 个) -## 📚 详细设计文档 +### Q: 代理服务如何启用认证? +A: +1. 编辑 `.env` 文件: + ```bash + PROXY_AUTH_ENABLED=true + PROXY_AUTH_USERNAME=myuser + PROXY_AUTH_PASSWORD=mypass + ``` +2. 重启服务:`docker compose up -d` +3. 客户端使用:`http://myuser:mypass@server-ip:7777` -完整的架构设计、模块说明、配置策略、资源优化方案,请查看: +详细说明和测试方法请查看 [`test/AUTH_TEST.md`](./test/AUTH_TEST.md) -👉 [POOL_DESIGN.md](./POOL_DESIGN.md) +### Q: 代理认证和 WebUI 认证有什么区别? +A: +- **代理认证**:保护 7776/7777 代理服务端口,防止代理被滥用 +- **WebUI 认证**:保护 7778 管理后台,区分访客和管理员权限 +- 两者独立配置,互不影响 ## 🛠️ 开发与调试 @@ -759,7 +954,18 @@ proxy from 🇯🇵 198.51.100.12: seq=5 time=890ms 50 requests transmitted, 47 received, 3 failed, 6.0% packet loss ``` -详细测试指南请查看:👉 [test/TEST_GUIDE.md](./test/TEST_GUIDE.md) +**测试认证功能**: +```bash +# 启用认证后测试 +curl -x http://myuser:mypass@127.0.0.1:7777 https://httpbin.org/ip + +# 无认证请求(应该返回 407 错误) +curl -x http://127.0.0.1:7777 https://httpbin.org/ip +``` + +**更多测试指南**: +- 基础功能测试:[`test/TEST_GUIDE.md`](./test/TEST_GUIDE.md) +- 认证功能测试:[`test/AUTH_TEST.md`](./test/AUTH_TEST.md) ## 🙏 致谢与声明 @@ -778,10 +984,13 @@ proxy from 🇯🇵 198.51.100.12: seq=5 time=890ms - 🆕 **分层健康管理**:批次检查、智能跳过 S 级、定时优化轮换 - 🆕 **智能重试机制**:自动故障切换、失败即删除、防重复尝试 - 🆕 **双端口服务**:7777 随机轮换(IP 多样性)+ 7776 最低延迟(稳定连接) +- 🆕 **代理认证保护**:可选 Basic Auth 认证,对外开放时保护代理服务不被滥用 - 🆕 **黑客风格 WebUI**:Matrix 美学、实时仪表盘、完整配置界面、中英文切换 - 🆕 **双角色权限**:访客模式(只读)+ 管理员模式(完全控制),可安全公网开放 - 🆕 **扩展存储层**:质量等级、使用统计、源状态管理 - 🆕 **测试套件**:Bash/Go/Python 三种测试脚本,持续运行模式,显示国旗 emoji +- 🆕 **CI/CD 自动化**:GitHub Actions 自动构建多架构镜像(amd64/arm64),双仓库发布 +- 🆕 **环境变量配置**:docker-compose + .env 文件,灵活配置各种部署场景 感谢原作者提供的基础实现,让我们能够在此之上构建更强大的代理池系统。 diff --git a/config/config.go b/config/config.go index 88bed2d..642a78a 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "os" + "strings" "sync" ) @@ -33,6 +34,14 @@ type Config struct { // 稳定代理端口(最低延迟模式) StableProxyPort string + // 代理服务认证配置 + ProxyAuthEnabled bool // 是否启用代理认证(默认 false) + ProxyAuthUsername string // 代理认证用户名(默认 "proxy") + ProxyAuthPasswordHash string // 代理认证密码 SHA256 哈希 + + // 地理过滤配置 + BlockedCountries []string // 屏蔽的国家代码列表(如 ["CN", "RU"],默认 ["CN"]) + // SQLite 数据库路径 DBPath string @@ -97,6 +106,33 @@ func DefaultConfig() *Config { if password == "" { password = DefaultPassword } + + // 读取代理认证配置 + proxyAuthEnabled := os.Getenv("PROXY_AUTH_ENABLED") == "true" + proxyAuthUsername := os.Getenv("PROXY_AUTH_USERNAME") + if proxyAuthUsername == "" { + proxyAuthUsername = "proxy" + } + proxyAuthPassword := os.Getenv("PROXY_AUTH_PASSWORD") + proxyAuthHash := "" + if proxyAuthPassword != "" { + proxyAuthHash = passwordHash(proxyAuthPassword) + } + + // 读取地理过滤配置 + blockedCountries := []string{"CN"} // 默认屏蔽中国大陆 + if blockedEnv := os.Getenv("BLOCKED_COUNTRIES"); blockedEnv != "" { + // 支持逗号分隔的国家代码,如 "CN,RU,KP" + countries := strings.Split(blockedEnv, ",") + blockedCountries = make([]string, 0, len(countries)) + for _, c := range countries { + c = strings.TrimSpace(strings.ToUpper(c)) + if c != "" { + blockedCountries = append(blockedCountries, c) + } + } + } + return &Config{ // 基础服务配置 WebUIPort: ":7778", @@ -104,6 +140,14 @@ func DefaultConfig() *Config { ProxyPort: ":7777", StableProxyPort: ":7776", DBPath: dataDir() + "proxy.db", + + // 代理认证配置 + ProxyAuthEnabled: proxyAuthEnabled, + ProxyAuthUsername: proxyAuthUsername, + ProxyAuthPasswordHash: proxyAuthHash, + + // 地理过滤配置 + BlockedCountries: blockedCountries, // 池子容量配置 PoolMaxSize: 100, // 总容量 diff --git a/docker-compose.yml b/docker-compose.yml index b26a215..83e9367 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,26 +1,32 @@ services: proxygo: - build: . - container_name: proxygo + # 使用预构建镜像(每次部署自动拉取最新版本) + image: ghcr.io/isboyjc/goproxy:latest + pull_policy: always + + # 或使用本地构建(取消下面注释,注释上面的 image 和 pull_policy 行) + # build: . + + container_name: ${CONTAINER_NAME:-proxygo} restart: unless-stopped ports: - - "127.0.0.1:7776:7776" # 稳定代理端口(最低延迟) - - "127.0.0.1:7777:7777" # 随机代理端口(轮换模式) - - "7778:7778" # WebUI 端口(外网可访问) + # 代理端口:默认仅本地访问(最安全) + # 如需对外开放,在 .env 中设置 PROXY_HOST=0.0.0.0(⚠️ 无认证,需配合防火墙) + - "${PROXY_HOST:-127.0.0.1}:${STABLE_PORT:-7776}:7776" # 稳定代理(最低延迟) + - "${PROXY_HOST:-127.0.0.1}:${RANDOM_PORT:-7777}:7777" # 随机轮换 + - "${WEBUI_HOST:-0.0.0.0}:${WEBUI_PORT:-7778}:7778" # WebUI(有登录认证) volumes: - - ./data:/app/data + - ${DATA_DIR:-./data}:/app/data environment: - - TZ=Asia/Shanghai + - TZ=${TZ:-Asia/Shanghai} - DATA_DIR=/app/data - # - WEBUI_PASSWORD=your-password # 自定义 WebUI 登录密码,默认: proxygo + - WEBUI_PASSWORD=${WEBUI_PASSWORD:-goproxy} + - PROXY_AUTH_ENABLED=${PROXY_AUTH_ENABLED:-false} + - PROXY_AUTH_USERNAME=${PROXY_AUTH_USERNAME:-proxy} + - PROXY_AUTH_PASSWORD=${PROXY_AUTH_PASSWORD} + - BLOCKED_COUNTRIES=${BLOCKED_COUNTRIES:-CN} healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:7778/"] interval: 30s timeout: 5s retries: 3 - networks: - - cursor2api_default - -networks: - cursor2api_default: - external: true diff --git a/main.go b/main.go index 838c4b5..ff75677 100644 --- a/main.go +++ b/main.go @@ -59,9 +59,11 @@ func main() { // 清理无效代理 totalDeleted := 0 - if deleted, err := store.DeleteChinaMainland(); err == nil && deleted > 0 { - log.Printf("[main] 🧹 已清理 %d 个中国大陆出口代理", deleted) - totalDeleted += int(deleted) + if len(cfg.BlockedCountries) > 0 { + if deleted, err := store.DeleteBlockedCountries(cfg.BlockedCountries); err == nil && deleted > 0 { + log.Printf("[main] 🧹 已清理 %d 个屏蔽国家出口代理 (屏蔽: %v)", deleted, cfg.BlockedCountries) + totalDeleted += int(deleted) + } } if deleted, err := store.DeleteWithoutExitInfo(); err == nil && deleted > 0 { log.Printf("[main] 🧹 已清理 %d 个无出口信息的代理", deleted) diff --git a/proxy/server.go b/proxy/server.go index 8a63e7b..0076927 100644 --- a/proxy/server.go +++ b/proxy/server.go @@ -1,12 +1,16 @@ package proxy import ( + "crypto/sha256" + "crypto/subtle" + "encoding/base64" "fmt" "io" "log" "net" "net/http" "net/url" + "strings" "time" "golang.org/x/net/proxy" @@ -35,11 +39,24 @@ func (s *Server) Start() error { if s.mode == "lowest-latency" { modeDesc = "最低延迟" } - log.Printf("proxy server listening on %s [%s]", s.port, modeDesc) + authStatus := "无认证" + if s.cfg.ProxyAuthEnabled { + authStatus = fmt.Sprintf("需认证 (用户: %s)", s.cfg.ProxyAuthUsername) + } + log.Printf("proxy server listening on %s [%s] [%s]", s.port, modeDesc, authStatus) return http.ListenAndServe(s.port, s) } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // 认证检查(如果启用) + if s.cfg.ProxyAuthEnabled { + if !s.checkAuth(r) { + w.Header().Set("Proxy-Authenticate", `Basic realm="GoProxy"`) + http.Error(w, "Proxy Authentication Required", http.StatusProxyAuthRequired) + return + } + } + if r.Method == http.MethodConnect { s.handleTunnel(w, r) } else { @@ -47,6 +64,40 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +// checkAuth 验证代理 Basic Auth +func (s *Server) checkAuth(r *http.Request) bool { + auth := r.Header.Get("Proxy-Authorization") + if auth == "" { + return false + } + + // 解析 Basic Auth + const prefix = "Basic " + if !strings.HasPrefix(auth, prefix) { + return false + } + + decoded, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return false + } + + credentials := strings.SplitN(string(decoded), ":", 2) + if len(credentials) != 2 { + return false + } + + username := credentials[0] + password := credentials[1] + + // 验证用户名和密码 + usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(s.cfg.ProxyAuthUsername)) == 1 + passwordHash := fmt.Sprintf("%x", sha256.Sum256([]byte(password))) + passwordMatch := subtle.ConstantTimeCompare([]byte(passwordHash), []byte(s.cfg.ProxyAuthPasswordHash)) == 1 + + return usernameMatch && passwordMatch +} + // handleHTTP 处理普通 HTTP 请求(带自动重试) func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { var tried []string diff --git a/storage/storage.go b/storage/storage.go index 156b846..6817ad4 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -556,15 +556,24 @@ func (s *Storage) DeleteInvalid(maxFailCount int) (int64, error) { return res.RowsAffected() } -// DeleteChinaMainland 删除中国大陆出口的代理(保留香港) -func (s *Storage) DeleteChinaMainland() (int64, error) { - // 删除 exit_location 以 "CN " 开头的代理(CN后面有空格表示是城市) - // 香港是 "HK Hong Kong" 或 "HK" 开头,不会被删除 - res, err := s.db.Exec(`DELETE FROM proxies WHERE exit_location LIKE 'CN %'`) - if err != nil { - return 0, err +// DeleteBlockedCountries 删除指定国家代码出口的代理 +func (s *Storage) DeleteBlockedCountries(countryCodes []string) (int64, error) { + if len(countryCodes) == 0 { + return 0, nil } - return res.RowsAffected() + + var totalDeleted int64 + for _, code := range countryCodes { + // exit_location 格式:如 "CN Beijing" 或 "HK Hong Kong" + // 使用 LIKE 'CODE %' 来匹配国家代码(后面有空格表示有城市信息) + res, err := s.db.Exec(`DELETE FROM proxies WHERE exit_location LIKE ?`, code+" %") + if err != nil { + return totalDeleted, err + } + affected, _ := res.RowsAffected() + totalDeleted += affected + } + return totalDeleted, nil } // DeleteWithoutExitInfo 删除没有出口信息的代理 diff --git a/test/AUTH_TEST.md b/test/AUTH_TEST.md new file mode 100644 index 0000000..1fd1a0e --- /dev/null +++ b/test/AUTH_TEST.md @@ -0,0 +1,165 @@ +# 代理认证功能测试指南 + +本文档说明如何测试 GoProxy 的代理认证功能。 + +## 🔒 启用认证 + +### 使用 docker-compose + +编辑 `.env` 文件: + +```bash +PROXY_HOST=0.0.0.0 +PROXY_AUTH_ENABLED=true +PROXY_AUTH_USERNAME=testuser +PROXY_AUTH_PASSWORD=testpass123 +``` + +启动服务: +```bash +docker compose up -d +``` + +### 直接运行 + +设置环境变量后启动: + +```bash +export PROXY_AUTH_ENABLED=true +export PROXY_AUTH_USERNAME=testuser +export PROXY_AUTH_PASSWORD=testpass123 +go run . +``` + +## 🧪 测试认证功能 + +### 测试 1:无认证请求(应该失败) + +```bash +curl -x http://127.0.0.1:7777 https://httpbin.org/ip -v +``` + +**预期结果**: +- HTTP 状态码:`407 Proxy Authentication Required` +- 响应头:`Proxy-Authenticate: Basic realm="GoProxy"` + +### 测试 2:错误的用户名/密码(应该失败) + +```bash +curl -x http://wronguser:wrongpass@127.0.0.1:7777 https://httpbin.org/ip -v +``` + +**预期结果**: +- HTTP 状态码:`407 Proxy Authentication Required` + +### 测试 3:正确的认证(应该成功) + +```bash +curl -x http://testuser:testpass123@127.0.0.1:7777 https://httpbin.org/ip +``` + +**预期结果**: +- HTTP 状态码:`200 OK` +- 返回代理的出口 IP 信息 + +### 测试 4:环境变量方式(应该成功) + +```bash +export http_proxy=http://testuser:testpass123@127.0.0.1:7777 +export https_proxy=http://testuser:testpass123@127.0.0.1:7777 +curl https://httpbin.org/ip +``` + +### 测试 5:HTTPS CONNECT 隧道(应该成功) + +```bash +curl -x http://testuser:testpass123@127.0.0.1:7776 https://www.google.com -I +``` + +## 🧩 多语言客户端示例 + +### Python (requests) + +```python +import requests + +proxies = { + 'http': 'http://testuser:testpass123@127.0.0.1:7777', + 'https': 'http://testuser:testpass123@127.0.0.1:7777', +} + +response = requests.get('https://httpbin.org/ip', proxies=proxies) +print(response.text) +``` + +### Go + +```go +package main + +import ( + "fmt" + "io" + "net/http" + "net/url" +) + +func main() { + proxyURL, _ := url.Parse("http://testuser:testpass123@127.0.0.1:7777") + client := &http.Client{ + Transport: &http.Transport{ + Proxy: http.ProxyURL(proxyURL), + }, + } + + resp, err := client.Get("https://httpbin.org/ip") + if err != nil { + panic(err) + } + defer resp.Body.Close() + + body, _ := io.ReadAll(resp.Body) + fmt.Println(string(body)) +} +``` + +### Node.js + +```javascript +const axios = require('axios'); +const HttpsProxyAgent = require('https-proxy-agent'); + +const proxyUrl = 'http://testuser:testpass123@127.0.0.1:7777'; +const agent = new HttpsProxyAgent(proxyUrl); + +axios.get('https://httpbin.org/ip', { + httpAgent: agent, + httpsAgent: agent +}) +.then(response => console.log(response.data)) +.catch(error => console.error(error)); +``` + +## 🔍 日志验证 + +启动时会输出认证状态: + +``` +proxy server listening on :7777 [随机轮换] [需认证 (用户: testuser)] +proxy server listening on :7776 [最低延迟] [需认证 (用户: testuser)] +``` + +无认证模式: + +``` +proxy server listening on :7777 [随机轮换] [无认证] +proxy server listening on :7776 [最低延迟] [无认证] +``` + +## 📝 注意事项 + +- 认证采用 **HTTP Basic Auth** 标准,兼容绝大多数客户端 +- 密码存储为 **SHA256 哈希**,安全性高 +- 认证失败不会删除代理或影响池子状态 +- 两个端口(7776、7777)共享相同的认证配置 +- 认证仅保护代理服务,WebUI 有独立的登录系统 diff --git a/validator/validator.go b/validator/validator.go index ab9c023..29ba1be 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -20,6 +20,7 @@ type Validator struct { timeout time.Duration validateURL string maxResponseMs int + cfg *config.Config } func concurrencyBuffer(total, concurrency int) int { @@ -40,6 +41,7 @@ func New(concurrency, timeoutSec int, validateURL string) *Validator { timeout: time.Duration(timeoutSec) * time.Second, validateURL: validateURL, maxResponseMs: maxMs, + cfg: cfg, } } @@ -160,12 +162,13 @@ func (v *Validator) ValidateOne(p storage.Proxy) (bool, time.Duration, string, s return false, latency, exitIP, exitLocation } - // 过滤中国大陆出口(香港的countryCode是HK,不是CN) - if len(exitLocation) >= 2 { + // 过滤屏蔽国家出口(根据配置) + if v.cfg != nil && len(v.cfg.BlockedCountries) > 0 && len(exitLocation) >= 2 { countryCode := exitLocation[:2] - if countryCode == "CN" { - // 中国大陆出口,直接拒绝 - return false, latency, exitIP, exitLocation + for _, blocked := range v.cfg.BlockedCountries { + if countryCode == blocked { + return false, latency, exitIP, exitLocation + } } }