Files
yinghuoapi/deploy
IanShaw 7fdc2b2d29 Fix/multiple issues (#24)
* fix(gemini): 修复 google_one OAuth 配置和 scopes 问题

- 修复 google_one 类型在 ExchangeCode 和 RefreshToken 中使用内置客户端
- 添加 DefaultGoogleOneScopes,包含 generative-language 和 drive.readonly 权限
- 在 EffectiveOAuthConfig 中为 google_one 类型使用专门的 scopes
- 将 docker-compose.override.yml 重命名为 .example 并添加到 .gitignore
- 完善 docker-compose.override.yml.example 示例文档

解决问题:
1. google_one OAuth 授权后 API 调用返回 403 权限不足
2. 缺少访问 Gemini API 所需的 generative-language scope
3. 缺少获取 Drive 存储配额所需的 drive.readonly scope

* fix(antigravity): 完全跳过 Claude 模型的所有 thinking 块

问题分析:
- 当前代码尝试保留有 signature 的 thinking 块
- 但 Vertex AI 的 signature 是完整性令牌,无法在本地验证
- 导致 400 错误:Invalid signature in thinking block

根本原因:
1. thinking 功能已对非 Gemini 模型禁用 (isThinkingEnabled=false)
2. Vertex AI 要求原样重放 (thinking, signature) 对或完全不发送
3. 本地无法复制 Vertex 的加密验证逻辑

修复方案:
- 对 Claude 模型完全跳过所有 thinking 块(无论是否有 signature)
- 保持 Gemini 模型使用 dummy signature 的行为不变
- 更新测试用例以反映新的预期行为

影响:
- 消除 thinking 相关的 400 错误
- 与现有的 thinking 禁用策略保持一致
- 不影响 Gemini 模型的 thinking 功能

测试:
-  TestBuildParts_ThinkingBlockWithoutSignature 全部通过
-  TestBuildTools_CustomTypeTools 全部通过

参考:Codex review 建议

* fix(gateway): 修复 count_tokens 端点 400 错误

问题分析:
- count_tokens 请求包含 thinking 块时返回 400 错误
- 原因:thinking 块未被过滤,直接转发到上游 API
- 上游 API 拒绝无效的 thinking signature

根本原因:
1. /v1/messages 请求通过 TransformClaudeToGemini 过滤 thinking 块
2. count_tokens 请求绕过转换,直接转发原始请求体
3. 导致包含无效 signature 的 thinking 块被发送到上游

修复方案:
- 创建 FilterThinkingBlocks 工具函数
- 在 buildCountTokensRequest 中应用过滤(1 行修改)
- 与 /v1/messages 行为保持一致

实现细节:
- FilterThinkingBlocks: 解析 JSON,过滤 thinking 块,重新序列化
- 失败安全:解析/序列化失败时返回原始请求体
- 性能优化:仅在发现 thinking 块时重新序列化

测试:
-  6 个单元测试全部通过
-  覆盖正常过滤、无 thinking 块、无效 JSON 等场景
-  现有测试不受影响

影响:
- 消除 count_tokens 的 400 错误
- 不影响 Antigravity 账号(仍返回模拟响应)
- 适用于所有账号类型(OAuth、API Key)

文件修改:
- backend/internal/service/gateway_request.go: +62 行(新函数)
- backend/internal/service/gateway_service.go: +2 行(应用过滤)
- backend/internal/service/gateway_request_test.go: +62 行(测试)

* fix(gateway): 增强 thinking 块过滤逻辑

基于 Codex 分析和建议的改进:

问题分析:
- 新错误:signature: Field required(signature 字段缺失)
- 旧错误:Invalid signature(signature 存在但无效)
- 两者都说明 thinking 块在请求中是危险的

Codex 建议:
- 保持 Option A:完全跳过所有 thinking 块
- 原因:thinking 块应该是只输出的,除非有服务端来源证明
- 在无状态代理中,无法安全区分上游来源 vs 客户端注入

改进内容:

1. 增强 FilterThinkingBlocks 函数
   - 过滤显式的 thinking 块:{"type":"thinking", ...}
   - 过滤无 type 的 thinking 对象:{"thinking": {...}}
   - 保留 tool_use 等其他类型块中的 thinking 字段
   - 修复:只在实际过滤时更新 content 数组

2. 扩展过滤范围
   - 将 FilterThinkingBlocks 应用到 /v1/messages 主路径
   - 之前只应用于 count_tokens,现在两个端点都过滤
   - 防止所有端点的 thinking 相关 400 错误

3. 改进测试
   - 新增:过滤无 type discriminator 的 thinking 块
   - 新增:不过滤 tool_use 中的 thinking 字段
   - 使用 containsThinkingBlock 辅助函数验证

测试:
-  8 个测试用例全部通过
-  覆盖各种 thinking 块格式
-  确保不误伤其他类型的块

影响:
- 消除 signature required 和 invalid signature 错误
- 统一 /v1/messages 和 count_tokens 的行为
- 更健壮的 thinking 块检测逻辑

参考:Codex review 和代码改进

* refactor: 根据 Codex 审查建议进行代码优化

基于 Codex 代码审查的 P1 和 P2 改进:

P1 改进(重要问题):

1. 优化日志输出
   - 移除 thinking 块跳过时的 log.Printf
   - 避免高频请求下的日志噪音
   - 添加注释说明可通过指标监控

2. 清理遗留代码
   - 删除未使用的 isValidThoughtSignature 函数(27行)
   - 该函数在改为完全跳过 thinking 块后不再需要

P2 改进(性能优化):

3. 添加快速路径检查
   - 在 FilterThinkingBlocks 中添加 bytes.Contains 预检查
   - 如果请求体不包含 "thinking" 字符串,直接返回
   - 避免不必要的 JSON 解析,提升性能

技术细节:
- request_transformer.go: -27行(删除函数),+1行(优化注释)
- gateway_request.go: +5行(快速路径 + bytes 导入)

测试:
-  TestBuildParts_ThinkingBlockWithoutSignature 全部通过
-  TestFilterThinkingBlocks 全部通过(8个测试用例)

影响:
- 减少日志噪音
- 提升性能(快速路径)
- 代码更简洁(删除未使用代码)

参考:Codex 代码审查建议

* fix: 修复 golangci-lint 检查问题

- 格式化 gateway_request_test.go
- 使用 switch 语句替代 if-else 链(staticcheck QF1003)

* fix(antigravity): 修复 thinking signature 处理并实现 Auto 模式降级

问题分析:
1. 原先代码错误地禁用了 Claude via Vertex 的 thinkingConfig
2. 历史 thinking 块的 signature 被完全跳过,导致验证失败
3. 跨模型混用时 dummy signature 会导致 400 错误

修复内容:

**request_transformer.go**:
- 删除第 38-43 行的错误逻辑(禁用 thinkingConfig)
- 引入 thoughtSignatureMode(Preserve/Dummy)策略
- Claude 模式:透传真实 signature,过滤空/dummy
- Gemini 模式:使用 dummy signature
- 支持 signature-only thinking 块
- tool_use 的 signature 也透传

**antigravity_gateway_service.go**:
- 新增 isSignatureRelatedError() 检测 signature 相关错误
- 新增 stripThinkingFromClaudeRequest() 移除 thinking 块
- 实现 Auto 模式:检测 400 + signature 关键词时自动降级重试
- 重试时完全移除 thinking 配置和消息中的 thinking 块
- 最多重试一次,避免循环

**测试**:
- 更新并新增测试覆盖 Claude preserve/Gemini dummy 模式
- 新增 tool_use signature 处理测试
- 所有测试通过(6/6)

影响:
-  Claude via Vertex 可以正常使用 thinking 功能
-  历史 signature 正确透传,避免验证失败
-  跨模型混用时自动过滤无效 signature
-  错误驱动降级,自动修复 signature 问题
-  不影响纯 Claude API 和其他渠道

参考:Codex 深度分析和实现建议

* fix(lint): 修复 gofmt 格式问题

* fix(antigravity): 修复 stripThinkingFromClaudeRequest 遗漏 untyped thinking blocks

问题:
- Codex 审查指出 stripThinkingFromClaudeRequest 只移除了 type="thinking" 的块
- 没有处理没有 type 字段的 thinking 对象(如 {"thinking": "...", "signature": "..."})
- 导致重试时仍包含无效 thinking 块,上游 400 错误持续

修复:
- 添加检查:跳过没有 type 但有 thinking 字段的块
- 现在会移除两种格式:
  1. {"type": "thinking", "thinking": "...", "signature": "..."}
  2. {"thinking": "...", "signature": "..."}(untyped)

测试:所有测试通过

参考:Codex P1 审查意见
2026-01-02 17:47:49 +08:00
..
2026-01-01 08:45:49 +08:00
2025-12-30 10:29:26 +08:00
2025-12-18 13:50:39 +08:00

Sub2API Deployment Files

This directory contains files for deploying Sub2API on Linux servers.

Deployment Methods

Method Best For Setup Wizard
Docker Compose Quick setup, all-in-one Not needed (auto-setup)
Binary Install Production servers, systemd Web-based wizard

Files

File Description
docker-compose.yml Docker Compose configuration
.env.example Docker environment variables template
DOCKER.md Docker Hub documentation
install.sh One-click binary installation script
sub2api.service Systemd service unit file
config.example.yaml Example configuration file

Quick Start

# Clone repository
git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy

# Configure environment
cp .env.example .env
nano .env  # Set POSTGRES_PASSWORD (required)

# Start all services
docker-compose up -d

# View logs (check for auto-generated admin password)
docker-compose logs -f sub2api

# Access Web UI
# http://localhost:8080

How Auto-Setup Works

When using Docker Compose with AUTO_SETUP=true:

  1. On first run, the system automatically:

    • Connects to PostgreSQL and Redis
    • Applies database migrations (SQL files in backend/migrations/*.sql) and records them in schema_migrations
    • Generates JWT secret (if not provided)
    • Creates admin account (password auto-generated if not provided)
    • Writes config.yaml
  2. No manual Setup Wizard needed - just configure .env and start

  3. If ADMIN_PASSWORD is not set, check logs for the generated password:

    docker-compose logs sub2api | grep "admin password"
    

Database Migration Notes (PostgreSQL)

  • Migrations are applied in lexicographic order (e.g. 001_...sql, 002_...sql).
  • schema_migrations tracks applied migrations (filename + checksum).
  • Migrations are forward-only; rollback requires a DB backup restore or a manual compensating SQL script.

Verify users.allowed_groupsuser_allowed_groups backfill

During the incremental GORM→Ent migration, users.allowed_groups (legacy BIGINT[]) is being replaced by a normalized join table user_allowed_groups(user_id, group_id).

Run this query to compare the legacy data vs the join table:

WITH old_pairs AS (
  SELECT DISTINCT u.id AS user_id, x.group_id
  FROM users u
  CROSS JOIN LATERAL unnest(u.allowed_groups) AS x(group_id)
  WHERE u.allowed_groups IS NOT NULL
)
SELECT
  (SELECT COUNT(*) FROM old_pairs)           AS old_pair_count,
  (SELECT COUNT(*) FROM user_allowed_groups) AS new_pair_count;

Commands

# Start services
docker-compose up -d

# Stop services
docker-compose down

# View logs
docker-compose logs -f sub2api

# Restart Sub2API only
docker-compose restart sub2api

# Update to latest version
docker-compose pull
docker-compose up -d

# Remove all data (caution!)
docker-compose down -v

Environment Variables

Variable Required Default Description
POSTGRES_PASSWORD Yes - PostgreSQL password
SERVER_PORT No 8080 Server port
ADMIN_EMAIL No admin@sub2api.local Admin email
ADMIN_PASSWORD No (auto-generated) Admin password
JWT_SECRET No (auto-generated) JWT secret
TZ No Asia/Shanghai Timezone
GEMINI_OAUTH_CLIENT_ID No (builtin) Google OAuth client ID (Gemini OAuth). Leave empty to use the built-in Gemini CLI client.
GEMINI_OAUTH_CLIENT_SECRET No (builtin) Google OAuth client secret (Gemini OAuth). Leave empty to use the built-in Gemini CLI client.
GEMINI_OAUTH_SCOPES No (default) OAuth scopes (Gemini OAuth)
GEMINI_QUOTA_POLICY No (empty) JSON overrides for Gemini local quota simulation (Code Assist only).

See .env.example for all available options.


Gemini OAuth Configuration

Sub2API supports three methods to connect to Gemini:

No configuration needed - always uses the built-in Gemini CLI OAuth client (public).

  1. Leave GEMINI_OAUTH_CLIENT_ID and GEMINI_OAUTH_CLIENT_SECRET empty
  2. In the Admin UI, create a Gemini OAuth account and select "Code Assist" type
  3. Complete the OAuth flow in your browser

Note: Even if you configure GEMINI_OAUTH_CLIENT_ID / GEMINI_OAUTH_CLIENT_SECRET for AI Studio OAuth, Code Assist OAuth will still use the built-in Gemini CLI client.

Requirements:

  • Google account with access to Google Cloud Platform
  • A GCP project (auto-detected or manually specified)

How to get Project ID (if auto-detection fails):

  1. Go to Google Cloud Console
  2. Click the project dropdown at the top of the page
  3. Copy the Project ID (not the project name) from the list
  4. Common formats: my-project-123456 or cloud-ai-companion-xxxxx

Method 2: AI Studio OAuth (For Regular Google Accounts)

Requires your own OAuth client credentials.

Step 1: Create OAuth Client in Google Cloud Console

  1. Go to Google Cloud Console - Credentials
  2. Create a new project or select an existing one
  3. Enable the Generative Language API:
    • Go to "APIs & Services" → "Library"
    • Search for "Generative Language API"
    • Click "Enable"
  4. Configure OAuth Consent Screen (if not done):
    • Go to "APIs & Services" → "OAuth consent screen"
    • Choose "External" user type
    • Fill in app name, user support email, developer contact
    • Add scopes: https://www.googleapis.com/auth/generative-language.retriever (and optionally https://www.googleapis.com/auth/cloud-platform)
    • Add test users (your Google account email)
  5. Create OAuth 2.0 credentials:
    • Go to "APIs & Services" → "Credentials"
    • Click "Create Credentials" → "OAuth client ID"
    • Application type: Web application (or Desktop app)
    • Name: e.g., "Sub2API Gemini"
    • Authorized redirect URIs: Add http://localhost:1455/auth/callback
  6. Copy the Client ID and Client Secret
  7. ⚠️ Publish to Production (IMPORTANT):
    • Go to "APIs & Services" → "OAuth consent screen"
    • Click "PUBLISH APP" to move from Testing to Production
    • Testing mode limitations:
      • Only manually added test users can authenticate (max 100 users)
      • Refresh tokens expire after 7 days
      • Users must be re-added periodically
    • Production mode: Any Google user can authenticate, tokens don't expire
    • Note: For sensitive scopes, Google may require verification (demo video, privacy policy)

Step 2: Configure Environment Variables

GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret

Step 3: Create Account in Admin UI

  1. Create a Gemini OAuth account and select "AI Studio" type
  2. Complete the OAuth flow
    • After consent, your browser will be redirected to http://localhost:1455/auth/callback?code=...&state=...
    • Copy the full callback URL (recommended) or just the code and paste it back into the Admin UI

Method 3: API Key (Simplest)

  1. Go to Google AI Studio
  2. Click "Create API key"
  3. In Admin UI, create a Gemini API Key account
  4. Paste your API key (starts with AIza...)

Comparison Table

Feature Code Assist OAuth AI Studio OAuth API Key
Setup Complexity Easy (no config) Medium (OAuth client) Easy
GCP Project Required Yes No No
Custom OAuth Client No (built-in) Yes (required) N/A
Rate Limits GCP quota Standard Standard
Best For GCP developers Regular users needing OAuth Quick testing

Binary Installation

For production servers using systemd.

One-Line Installation

curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash

Manual Installation

  1. Download the latest release from GitHub Releases
  2. Extract and copy the binary to /opt/sub2api/
  3. Copy sub2api.service to /etc/systemd/system/
  4. Run:
    sudo systemctl daemon-reload
    sudo systemctl enable sub2api
    sudo systemctl start sub2api
    
  5. Open the Setup Wizard in your browser to complete configuration

Commands

# Install
sudo ./install.sh

# Upgrade
sudo ./install.sh upgrade

# Uninstall
sudo ./install.sh uninstall

Service Management

# Start the service
sudo systemctl start sub2api

# Stop the service
sudo systemctl stop sub2api

# Restart the service
sudo systemctl restart sub2api

# Check status
sudo systemctl status sub2api

# View logs
sudo journalctl -u sub2api -f

# Enable auto-start on boot
sudo systemctl enable sub2api

Configuration

Server Address and Port

During installation, you will be prompted to configure the server listen address and port. These settings are stored in the systemd service file as environment variables.

To change after installation:

  1. Edit the systemd service:

    sudo systemctl edit sub2api
    
  2. Add or modify:

    [Service]
    Environment=SERVER_HOST=0.0.0.0
    Environment=SERVER_PORT=3000
    
  3. Reload and restart:

    sudo systemctl daemon-reload
    sudo systemctl restart sub2api
    

Gemini OAuth Configuration

If you need to use AI Studio OAuth for Gemini accounts, add the OAuth client credentials to the systemd service file:

  1. Edit the service file:

    sudo nano /etc/systemd/system/sub2api.service
    
  2. Add your OAuth credentials in the [Service] section (after the existing Environment= lines):

    Environment=GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
    Environment=GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret
    
  3. Reload and restart:

    sudo systemctl daemon-reload
    sudo systemctl restart sub2api
    

Note: Code Assist OAuth does not require any configuration - it uses the built-in Gemini CLI client. See the Gemini OAuth Configuration section above for detailed setup instructions.

Application Configuration

The main config file is at /etc/sub2api/config.yaml (created by Setup Wizard).

Prerequisites

  • Linux server (Ubuntu 20.04+, Debian 11+, CentOS 8+, etc.)
  • PostgreSQL 14+
  • Redis 6+
  • systemd

Directory Structure

/opt/sub2api/
├── sub2api              # Main binary
├── sub2api.backup       # Backup (after upgrade)
└── data/                # Runtime data

/etc/sub2api/
└── config.yaml          # Configuration file

Troubleshooting

Docker

# Check container status
docker-compose ps

# View detailed logs
docker-compose logs --tail=100 sub2api

# Check database connection
docker-compose exec postgres pg_isready

# Check Redis connection
docker-compose exec redis redis-cli ping

# Restart all services
docker-compose restart

Binary Install

# Check service status
sudo systemctl status sub2api

# View recent logs
sudo journalctl -u sub2api -n 50

# Check config file
sudo cat /etc/sub2api/config.yaml

# Check PostgreSQL
sudo systemctl status postgresql

# Check Redis
sudo systemctl status redis

Common Issues

  1. Port already in use: Change SERVER_PORT in .env or systemd config
  2. Database connection failed: Check PostgreSQL is running and credentials are correct
  3. Redis connection failed: Check Redis is running and password is correct
  4. Permission denied: Ensure proper file ownership for binary install