first commit
This commit is contained in:
218
deploy/.env.example
Normal file
218
deploy/.env.example
Normal file
@@ -0,0 +1,218 @@
|
||||
# =============================================================================
|
||||
# Sub2API Docker Environment Configuration
|
||||
# =============================================================================
|
||||
# Copy this file to .env and modify as needed:
|
||||
# cp .env.example .env
|
||||
# nano .env
|
||||
#
|
||||
# Then start with: docker-compose up -d
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Server Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# Bind address for host port mapping
|
||||
BIND_HOST=0.0.0.0
|
||||
|
||||
# Server port (exposed on host)
|
||||
SERVER_PORT=8080
|
||||
|
||||
# Server mode: release or debug
|
||||
SERVER_MODE=release
|
||||
|
||||
# 运行模式: standard (默认) 或 simple (内部自用)
|
||||
# standard: 完整 SaaS 功能,包含计费/余额校验;simple: 隐藏 SaaS 功能并跳过计费/余额校验
|
||||
RUN_MODE=standard
|
||||
|
||||
# Timezone
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PostgreSQL Configuration (REQUIRED)
|
||||
# -----------------------------------------------------------------------------
|
||||
POSTGRES_USER=sub2api
|
||||
POSTGRES_PASSWORD=change_this_secure_password
|
||||
POSTGRES_DB=sub2api
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Redis Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# Leave empty for no password (default for local development)
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Admin Account
|
||||
# -----------------------------------------------------------------------------
|
||||
# Email for the admin account
|
||||
ADMIN_EMAIL=admin@sub2api.local
|
||||
|
||||
# Password for admin account
|
||||
# Leave empty to auto-generate (will be shown in logs on first run)
|
||||
ADMIN_PASSWORD=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# JWT Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# IMPORTANT: Set a fixed JWT_SECRET to prevent login sessions from being
|
||||
# invalidated after container restarts. If left empty, a random secret will
|
||||
# be generated on each startup, causing all users to be logged out.
|
||||
# Generate a secure secret: openssl rand -hex 32
|
||||
JWT_SECRET=
|
||||
JWT_EXPIRE_HOUR=24
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Configuration File (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Path to custom config file (relative to docker-compose.yml directory)
|
||||
# Copy config.example.yaml to config.yaml and modify as needed
|
||||
# Leave unset to use default ./config.yaml
|
||||
#CONFIG_FILE=./config.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Rate Limiting (Optional)
|
||||
# 速率限制(可选)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Cooldown time (in minutes) when upstream returns 529 (overloaded)
|
||||
# 上游返回 529(过载)时的冷却时间(分钟)
|
||||
RATE_LIMIT_OVERLOAD_COOLDOWN_MINUTES=10
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Gateway Scheduling (Optional)
|
||||
# 调度缓存与受控回源配置(缓存就绪且命中时不读 DB)
|
||||
# -----------------------------------------------------------------------------
|
||||
# 粘性会话最大排队长度
|
||||
GATEWAY_SCHEDULING_STICKY_SESSION_MAX_WAITING=3
|
||||
# 粘性会话等待超时(时间段,例如 45s)
|
||||
GATEWAY_SCHEDULING_STICKY_SESSION_WAIT_TIMEOUT=120s
|
||||
# 兜底排队等待超时(时间段,例如 30s)
|
||||
GATEWAY_SCHEDULING_FALLBACK_WAIT_TIMEOUT=30s
|
||||
# 兜底最大排队长度
|
||||
GATEWAY_SCHEDULING_FALLBACK_MAX_WAITING=100
|
||||
# 启用调度批量负载计算
|
||||
GATEWAY_SCHEDULING_LOAD_BATCH_ENABLED=true
|
||||
# 并发槽位清理周期(时间段,例如 30s)
|
||||
GATEWAY_SCHEDULING_SLOT_CLEANUP_INTERVAL=30s
|
||||
# 是否允许受控回源到 DB(默认 true,保持现有行为)
|
||||
GATEWAY_SCHEDULING_DB_FALLBACK_ENABLED=true
|
||||
# 受控回源超时(秒),0 表示不额外收紧超时
|
||||
GATEWAY_SCHEDULING_DB_FALLBACK_TIMEOUT_SECONDS=0
|
||||
# 受控回源限流(实例级 QPS),0 表示不限制
|
||||
GATEWAY_SCHEDULING_DB_FALLBACK_MAX_QPS=0
|
||||
# outbox 轮询周期(秒)
|
||||
GATEWAY_SCHEDULING_OUTBOX_POLL_INTERVAL_SECONDS=1
|
||||
# outbox 滞后告警阈值(秒)
|
||||
GATEWAY_SCHEDULING_OUTBOX_LAG_WARN_SECONDS=5
|
||||
# outbox 触发强制重建阈值(秒)
|
||||
GATEWAY_SCHEDULING_OUTBOX_LAG_REBUILD_SECONDS=10
|
||||
# outbox 连续滞后触发次数
|
||||
GATEWAY_SCHEDULING_OUTBOX_LAG_REBUILD_FAILURES=3
|
||||
# outbox 积压触发重建阈值(行数)
|
||||
GATEWAY_SCHEDULING_OUTBOX_BACKLOG_REBUILD_ROWS=10000
|
||||
# 全量重建周期(秒)
|
||||
GATEWAY_SCHEDULING_FULL_REBUILD_INTERVAL_SECONDS=300
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Dashboard Aggregation (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Enable aggregation job
|
||||
# 启用仪表盘预聚合
|
||||
DASHBOARD_AGGREGATION_ENABLED=true
|
||||
# Refresh interval (seconds)
|
||||
# 刷新间隔(秒)
|
||||
DASHBOARD_AGGREGATION_INTERVAL_SECONDS=60
|
||||
# Lookback window (seconds)
|
||||
# 回看窗口(秒)
|
||||
DASHBOARD_AGGREGATION_LOOKBACK_SECONDS=120
|
||||
# Allow manual backfill
|
||||
# 允许手动回填
|
||||
DASHBOARD_AGGREGATION_BACKFILL_ENABLED=false
|
||||
# Backfill max range (days)
|
||||
# 回填最大跨度(天)
|
||||
DASHBOARD_AGGREGATION_BACKFILL_MAX_DAYS=31
|
||||
# Recompute recent N days on startup
|
||||
# 启动时重算最近 N 天
|
||||
DASHBOARD_AGGREGATION_RECOMPUTE_DAYS=2
|
||||
# Retention windows (days)
|
||||
# 保留窗口(天)
|
||||
DASHBOARD_AGGREGATION_RETENTION_USAGE_LOGS_DAYS=90
|
||||
DASHBOARD_AGGREGATION_RETENTION_HOURLY_DAYS=180
|
||||
DASHBOARD_AGGREGATION_RETENTION_DAILY_DAYS=730
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Security Configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
# URL Allowlist Configuration
|
||||
# 启用 URL 白名单验证(false 则跳过白名单检查,仅做基本格式校验)
|
||||
SECURITY_URL_ALLOWLIST_ENABLED=false
|
||||
|
||||
# 关闭白名单时,是否允许 http:// URL(默认 false,只允许 https://)
|
||||
# ⚠️ 警告:允许 HTTP 存在安全风险(明文传输),仅建议在开发/测试环境或可信内网中使用
|
||||
# Allow insecure HTTP URLs when allowlist is disabled (default: false, requires https)
|
||||
# ⚠️ WARNING: Allowing HTTP has security risks (plaintext transmission)
|
||||
# Only recommended for dev/test environments or trusted networks
|
||||
SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=true
|
||||
|
||||
# 是否允许本地/私有 IP 地址用于上游/定价/CRS(仅在可信网络中使用)
|
||||
# Allow localhost/private IPs for upstream/pricing/CRS (use only in trusted networks)
|
||||
SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Gemini OAuth (OPTIONAL, required only for Gemini OAuth accounts)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Sub2API supports TWO Gemini OAuth modes:
|
||||
#
|
||||
# 1. Code Assist OAuth (需要 GCP project_id)
|
||||
# - Uses: cloudcode-pa.googleapis.com (Code Assist API)
|
||||
# - Auto scopes: cloud-platform + userinfo.email + userinfo.profile
|
||||
# - OAuth Client: Can use built-in Gemini CLI client (留空即可)
|
||||
# - Requires: Google Cloud Platform project with Code Assist enabled
|
||||
#
|
||||
# 2. AI Studio OAuth (不需要 project_id)
|
||||
# - Uses: generativelanguage.googleapis.com (AI Studio API)
|
||||
# - Default scopes: generative-language
|
||||
# - OAuth Client: Requires your own OAuth 2.0 Client (内置 Gemini CLI client 不能申请 generative-language scope)
|
||||
# - Requires: Create OAuth 2.0 Client in GCP Console + OAuth consent screen
|
||||
# - Setup Guide: https://ai.google.dev/gemini-api/docs/oauth
|
||||
# - ⚠️ IMPORTANT: OAuth Client 必须发布为正式版本 (Production)
|
||||
# Testing 模式限制: 只能添加 100 个测试用户, refresh token 7 天后过期
|
||||
# 发布步骤: GCP Console → OAuth consent screen → PUBLISH APP
|
||||
#
|
||||
# Configuration:
|
||||
# Leave empty to use the built-in Gemini CLI OAuth client (Code Assist OAuth only).
|
||||
# To enable AI Studio OAuth, set your own OAuth client ID/secret here.
|
||||
GEMINI_OAUTH_CLIENT_ID=
|
||||
GEMINI_OAUTH_CLIENT_SECRET=
|
||||
# Optional; leave empty to auto-select scopes based on oauth_type
|
||||
GEMINI_OAUTH_SCOPES=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Gemini Quota Policy (OPTIONAL, local simulation)
|
||||
# -----------------------------------------------------------------------------
|
||||
# JSON overrides for local quota simulation (Code Assist only).
|
||||
# Example:
|
||||
# GEMINI_QUOTA_POLICY={"tiers":{"LEGACY":{"pro_rpd":50,"flash_rpd":1500,"cooldown_minutes":30},"PRO":{"pro_rpd":1500,"flash_rpd":4000,"cooldown_minutes":5},"ULTRA":{"pro_rpd":2000,"flash_rpd":0,"cooldown_minutes":5}}}
|
||||
GEMINI_QUOTA_POLICY=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Ops Monitoring Configuration (运维监控配置)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Enable ops monitoring features (background jobs and APIs)
|
||||
# 是否启用运维监控功能(后台任务和接口)
|
||||
# Set to false to hide ops menu in sidebar and disable all ops features
|
||||
# 设置为 false 可在左侧栏隐藏运维监控菜单并禁用所有运维监控功能
|
||||
OPS_ENABLED=true
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Update Configuration (在线更新配置)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Proxy URL for accessing GitHub (used for online updates and pricing data)
|
||||
# 用于访问 GitHub 的代理地址(用于在线更新和定价数据获取)
|
||||
# Supports: http, https, socks5, socks5h
|
||||
# Examples:
|
||||
# HTTP proxy: http://127.0.0.1:7890
|
||||
# SOCKS5 proxy: socks5://127.0.0.1:1080
|
||||
# With authentication: http://user:pass@proxy.example.com:8080
|
||||
# Leave empty for direct connection (recommended for overseas servers)
|
||||
# 留空表示直连(适用于海外服务器)
|
||||
UPDATE_PROXY_URL=
|
||||
188
deploy/Caddyfile
Normal file
188
deploy/Caddyfile
Normal file
@@ -0,0 +1,188 @@
|
||||
# =============================================================================
|
||||
# Sub2API Caddy Reverse Proxy Configuration (宿主机部署)
|
||||
# =============================================================================
|
||||
# 使用方法:
|
||||
# 1. 安装 Caddy: https://caddyserver.com/docs/install
|
||||
# 2. 修改下方 example.com 为你的域名
|
||||
# 3. 确保域名 DNS 已指向服务器
|
||||
# 4. 复制配置: sudo cp Caddyfile /etc/caddy/Caddyfile
|
||||
# 5. 重载配置: sudo systemctl reload caddy
|
||||
#
|
||||
# Caddy 会自动申请和续期 Let's Encrypt SSL 证书
|
||||
# =============================================================================
|
||||
|
||||
# 全局配置
|
||||
{
|
||||
# Let's Encrypt 邮箱通知
|
||||
email admin@example.com
|
||||
|
||||
# 服务器配置
|
||||
servers {
|
||||
# 启用 HTTP/2 和 HTTP/3
|
||||
protocols h1 h2 h3
|
||||
|
||||
# 超时配置
|
||||
timeouts {
|
||||
read_body 30s
|
||||
read_header 10s
|
||||
write 300s
|
||||
idle 300s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 修改为你的域名
|
||||
example.com {
|
||||
# =========================================================================
|
||||
# TLS 安全配置
|
||||
# =========================================================================
|
||||
tls {
|
||||
# 仅使用 TLS 1.2 和 1.3
|
||||
protocols tls1.2 tls1.3
|
||||
|
||||
# 优先使用的加密套件
|
||||
ciphers TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 反向代理配置
|
||||
# =========================================================================
|
||||
reverse_proxy localhost:8080 {
|
||||
# 健康检查
|
||||
health_uri /health
|
||||
health_interval 30s
|
||||
health_timeout 10s
|
||||
health_status 200
|
||||
|
||||
# 负载均衡策略(单节点可忽略,多节点时有用)
|
||||
lb_policy round_robin
|
||||
lb_try_duration 5s
|
||||
lb_try_interval 250ms
|
||||
|
||||
# 传递真实客户端信息
|
||||
# 兼容 Cloudflare 和直连:后端应优先读取 CF-Connecting-IP,其次 X-Real-IP
|
||||
header_up X-Real-IP {remote_host}
|
||||
header_up X-Forwarded-For {remote_host}
|
||||
header_up X-Forwarded-Proto {scheme}
|
||||
header_up X-Forwarded-Host {host}
|
||||
# 保留 Cloudflare 原始头(如果存在)
|
||||
# 后端获取 IP 的优先级建议: CF-Connecting-IP → X-Real-IP → X-Forwarded-For
|
||||
header_up CF-Connecting-IP {http.request.header.CF-Connecting-IP}
|
||||
|
||||
# 连接池优化
|
||||
transport http {
|
||||
keepalive 120s
|
||||
keepalive_idle_conns 256
|
||||
read_buffer 16KB
|
||||
write_buffer 16KB
|
||||
compression off
|
||||
}
|
||||
|
||||
# SSE/流式传输优化:禁用响应缓冲,立即刷新数据给客户端
|
||||
flush_interval -1
|
||||
|
||||
# 故障转移
|
||||
fail_duration 30s
|
||||
max_fails 3
|
||||
unhealthy_status 500 502 503 504
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 压缩配置
|
||||
# =========================================================================
|
||||
encode {
|
||||
zstd
|
||||
gzip 6
|
||||
minimum_length 256
|
||||
match {
|
||||
# SSE 请求通常会带 Accept: text/event-stream,需排除压缩
|
||||
not header Accept text/event-stream*
|
||||
# 排除已知 SSE 路径(即便 Accept 缺失)
|
||||
not path /v1/messages /v1/responses /responses /antigravity/v1/messages /v1beta/models/* /antigravity/v1beta/models/*
|
||||
header Content-Type text/*
|
||||
header Content-Type application/json*
|
||||
header Content-Type application/javascript*
|
||||
header Content-Type application/xml*
|
||||
header Content-Type application/rss+xml*
|
||||
header Content-Type image/svg+xml*
|
||||
}
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 速率限制 (需要 caddy-ratelimit 插件)
|
||||
# 如未安装插件,请注释掉此段
|
||||
# =========================================================================
|
||||
# rate_limit {
|
||||
# zone api {
|
||||
# key {remote_host}
|
||||
# events 100
|
||||
# window 1m
|
||||
# }
|
||||
# }
|
||||
|
||||
# =========================================================================
|
||||
# 安全响应头
|
||||
# =========================================================================
|
||||
header {
|
||||
# 防止点击劫持
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
|
||||
# XSS 保护
|
||||
X-XSS-Protection "1; mode=block"
|
||||
|
||||
# 防止 MIME 类型嗅探
|
||||
X-Content-Type-Options "nosniff"
|
||||
|
||||
# 引用策略
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
|
||||
# HSTS - 强制 HTTPS (max-age=1年)
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
|
||||
# 内容安全策略 (根据需要调整)
|
||||
# Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:;"
|
||||
|
||||
# 权限策略
|
||||
Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()"
|
||||
|
||||
# 跨域资源策略
|
||||
Cross-Origin-Opener-Policy "same-origin"
|
||||
Cross-Origin-Embedder-Policy "require-corp"
|
||||
Cross-Origin-Resource-Policy "same-origin"
|
||||
|
||||
# 移除敏感头
|
||||
-Server
|
||||
-X-Powered-By
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 请求大小限制 (防止大文件攻击)
|
||||
# =========================================================================
|
||||
request_body {
|
||||
max_size 100MB
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 日志配置
|
||||
# =========================================================================
|
||||
log {
|
||||
output file /var/log/caddy/sub2api.log {
|
||||
roll_size 50mb
|
||||
roll_keep 10
|
||||
roll_keep_for 720h
|
||||
}
|
||||
format json
|
||||
level INFO
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 错误处理
|
||||
# =========================================================================
|
||||
handle_errors {
|
||||
respond "{err.status_code} {err.status_text}"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# HTTP 重定向到 HTTPS (Caddy 默认自动处理,此处显式声明)
|
||||
# =============================================================================
|
||||
76
deploy/DOCKER.md
Normal file
76
deploy/DOCKER.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Sub2API Docker Image
|
||||
|
||||
Sub2API is an AI API Gateway Platform for distributing and managing AI product subscription API quotas.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name sub2api \
|
||||
-p 8080:8080 \
|
||||
-e DATABASE_URL="postgres://user:pass@host:5432/sub2api" \
|
||||
-e REDIS_URL="redis://host:6379" \
|
||||
weishaw/sub2api:latest
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
sub2api:
|
||||
image: weishaw/sub2api:latest
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:postgres@db:5432/sub2api?sslmode=disable
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=sub2api
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description | Required | Default |
|
||||
|----------|-------------|----------|---------|
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Yes | - |
|
||||
| `REDIS_URL` | Redis connection string | Yes | - |
|
||||
| `PORT` | Server port | No | `8080` |
|
||||
| `GIN_MODE` | Gin framework mode (`debug`/`release`) | No | `release` |
|
||||
|
||||
## Supported Architectures
|
||||
|
||||
- `linux/amd64`
|
||||
- `linux/arm64`
|
||||
|
||||
## Tags
|
||||
|
||||
- `latest` - Latest stable release
|
||||
- `x.y.z` - Specific version
|
||||
- `x.y` - Latest patch of minor version
|
||||
- `x` - Latest minor of major version
|
||||
|
||||
## Links
|
||||
|
||||
- [GitHub Repository](https://github.com/weishaw/sub2api)
|
||||
- [Documentation](https://github.com/weishaw/sub2api#readme)
|
||||
41
deploy/Makefile
Normal file
41
deploy/Makefile
Normal file
@@ -0,0 +1,41 @@
|
||||
.PHONY: wire build build-embed test-unit test-integration test-e2e test-cover-integration
|
||||
|
||||
wire:
|
||||
@echo "生成 Wire 代码..."
|
||||
@cd cmd/server && go generate
|
||||
@echo "Wire 代码生成完成"
|
||||
|
||||
build:
|
||||
@echo "构建后端(不嵌入前端)..."
|
||||
@go build -o bin/server ./cmd/server
|
||||
@echo "构建完成: bin/server"
|
||||
|
||||
build-embed:
|
||||
@echo "构建后端(嵌入前端)..."
|
||||
@go build -tags embed -o bin/server ./cmd/server
|
||||
@echo "构建完成: bin/server (with embedded frontend)"
|
||||
|
||||
test-unit:
|
||||
@go test -tags unit ./... -count=1
|
||||
|
||||
test-integration:
|
||||
@go test -tags integration ./... -count=1 -race -parallel=8
|
||||
|
||||
test-e2e:
|
||||
@echo "运行 E2E 测试(需要本地服务器运行)..."
|
||||
@go test -tags e2e ./internal/integration/... -count=1 -v
|
||||
|
||||
test-cover-integration:
|
||||
@echo "运行集成测试并生成覆盖率报告..."
|
||||
@go test -tags=integration -cover -coverprofile=coverage.out -count=1 -race -parallel=8 ./...
|
||||
@go tool cover -func=coverage.out | tail -1
|
||||
@go tool cover -html=coverage.out -o coverage.html
|
||||
@echo "覆盖率报告已生成: coverage.html"
|
||||
|
||||
clean-coverage:
|
||||
@rm -f coverage.out coverage.html
|
||||
@echo "覆盖率文件已清理"
|
||||
|
||||
clean: clean-coverage
|
||||
@rm -rf bin/
|
||||
@echo "构建产物已清理"
|
||||
403
deploy/README.md
Normal file
403
deploy/README.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# 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 |
|
||||
|
||||
---
|
||||
|
||||
## Docker Deployment (Recommended)
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
```bash
|
||||
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_groups` → `user_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:
|
||||
|
||||
```sql
|
||||
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
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
### Method 1: Code Assist OAuth (Recommended for GCP Users)
|
||||
|
||||
**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](https://console.cloud.google.com/)
|
||||
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](https://console.cloud.google.com/apis/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**
|
||||
|
||||
```bash
|
||||
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](https://aistudio.google.com/app/apikey)
|
||||
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
|
||||
|
||||
```bash
|
||||
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](https://github.com/Wei-Shaw/sub2api/releases)
|
||||
2. Extract and copy the binary to `/opt/sub2api/`
|
||||
3. Copy `sub2api.service` to `/etc/systemd/system/`
|
||||
4. Run:
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable sub2api
|
||||
sudo systemctl start sub2api
|
||||
```
|
||||
5. Open the Setup Wizard in your browser to complete configuration
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
# Install
|
||||
sudo ./install.sh
|
||||
|
||||
# Upgrade
|
||||
sudo ./install.sh upgrade
|
||||
|
||||
# Uninstall
|
||||
sudo ./install.sh uninstall
|
||||
```
|
||||
|
||||
### Service Management
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
```bash
|
||||
sudo systemctl edit sub2api
|
||||
```
|
||||
|
||||
2. Add or modify:
|
||||
```ini
|
||||
[Service]
|
||||
Environment=SERVER_HOST=0.0.0.0
|
||||
Environment=SERVER_PORT=3000
|
||||
```
|
||||
|
||||
3. Reload and restart:
|
||||
```bash
|
||||
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:
|
||||
```bash
|
||||
sudo nano /etc/systemd/system/sub2api.service
|
||||
```
|
||||
|
||||
2. Add your OAuth credentials in the `[Service]` section (after the existing `Environment=` lines):
|
||||
```ini
|
||||
Environment=GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
Environment=GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret
|
||||
```
|
||||
|
||||
3. Reload and restart:
|
||||
```bash
|
||||
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](#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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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
|
||||
563
deploy/config.example.yaml
Normal file
563
deploy/config.example.yaml
Normal file
@@ -0,0 +1,563 @@
|
||||
# Sub2API Configuration File
|
||||
# Sub2API 配置文件
|
||||
#
|
||||
# Copy this file to /etc/sub2api/config.yaml and modify as needed
|
||||
# 复制此文件到 /etc/sub2api/config.yaml 并根据需要修改
|
||||
#
|
||||
# Documentation / 文档: https://github.com/Wei-Shaw/sub2api
|
||||
|
||||
# =============================================================================
|
||||
# Server Configuration
|
||||
# 服务器配置
|
||||
# =============================================================================
|
||||
server:
|
||||
# Bind address (0.0.0.0 for all interfaces)
|
||||
# 绑定地址(0.0.0.0 表示监听所有网络接口)
|
||||
host: "0.0.0.0"
|
||||
# Port to listen on
|
||||
# 监听端口
|
||||
port: 8080
|
||||
# Mode: "debug" for development, "release" for production
|
||||
# 运行模式:"debug" 用于开发,"release" 用于生产环境
|
||||
mode: "release"
|
||||
# Trusted proxies for X-Forwarded-For parsing (CIDR/IP). Empty disables trusted proxies.
|
||||
# 信任的代理地址(CIDR/IP 格式),用于解析 X-Forwarded-For 头。留空则禁用代理信任。
|
||||
trusted_proxies: []
|
||||
|
||||
# =============================================================================
|
||||
# Run Mode Configuration
|
||||
# 运行模式配置
|
||||
# =============================================================================
|
||||
# Run mode: "standard" (default) or "simple" (for internal use)
|
||||
# 运行模式:"standard"(默认)或 "simple"(内部使用)
|
||||
# - standard: Full SaaS features with billing/balance checks
|
||||
# - standard: 完整 SaaS 功能,包含计费和余额校验
|
||||
# - simple: Hides SaaS features and skips billing/balance checks
|
||||
# - simple: 隐藏 SaaS 功能,跳过计费和余额校验
|
||||
run_mode: "standard"
|
||||
|
||||
# =============================================================================
|
||||
# CORS Configuration
|
||||
# 跨域资源共享 (CORS) 配置
|
||||
# =============================================================================
|
||||
cors:
|
||||
# Allowed origins list. Leave empty to disable cross-origin requests.
|
||||
# 允许的来源列表。留空则禁用跨域请求。
|
||||
allowed_origins: []
|
||||
# Allow credentials (cookies/authorization headers). Cannot be used with "*".
|
||||
# 允许携带凭证(cookies/授权头)。不能与 "*" 通配符同时使用。
|
||||
allow_credentials: true
|
||||
|
||||
# =============================================================================
|
||||
# Security Configuration
|
||||
# 安全配置
|
||||
# =============================================================================
|
||||
security:
|
||||
url_allowlist:
|
||||
# Enable URL allowlist validation (disable to skip all URL checks)
|
||||
# 启用 URL 白名单验证(禁用则跳过所有 URL 检查)
|
||||
enabled: false
|
||||
# Allowed upstream hosts for API proxying
|
||||
# 允许代理的上游 API 主机列表
|
||||
upstream_hosts:
|
||||
- "api.openai.com"
|
||||
- "api.anthropic.com"
|
||||
- "api.kimi.com"
|
||||
- "open.bigmodel.cn"
|
||||
- "api.minimaxi.com"
|
||||
- "generativelanguage.googleapis.com"
|
||||
- "cloudcode-pa.googleapis.com"
|
||||
- "*.openai.azure.com"
|
||||
# Allowed hosts for pricing data download
|
||||
# 允许下载定价数据的主机列表
|
||||
pricing_hosts:
|
||||
- "raw.githubusercontent.com"
|
||||
# Allowed hosts for CRS sync (required when using CRS sync)
|
||||
# 允许 CRS 同步的主机列表(使用 CRS 同步功能时必须配置)
|
||||
crs_hosts: []
|
||||
# Allow localhost/private IPs for upstream/pricing/CRS (use only in trusted networks)
|
||||
# 允许本地/私有 IP 地址用于上游/定价/CRS(仅在可信网络中使用)
|
||||
allow_private_hosts: true
|
||||
# Allow http:// URLs when allowlist is disabled (default: false, require https)
|
||||
# 白名单禁用时是否允许 http:// URL(默认: false,要求 https)
|
||||
allow_insecure_http: true
|
||||
response_headers:
|
||||
# Enable configurable response header filtering (disable to use default allowlist)
|
||||
# 启用可配置的响应头过滤(禁用则使用默认白名单)
|
||||
enabled: false
|
||||
# Extra allowed response headers from upstream
|
||||
# 额外允许的上游响应头
|
||||
additional_allowed: []
|
||||
# Force-remove response headers from upstream
|
||||
# 强制移除的上游响应头
|
||||
force_remove: []
|
||||
csp:
|
||||
# Enable Content-Security-Policy header
|
||||
# 启用内容安全策略 (CSP) 响应头
|
||||
enabled: true
|
||||
# Default CSP policy (override if you host assets on other domains)
|
||||
# 默认 CSP 策略(如果静态资源托管在其他域名,请自行覆盖)
|
||||
policy: "default-src 'self'; script-src 'self' https://challenges.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' data: https://fonts.gstatic.com; connect-src 'self' https:; frame-src https://challenges.cloudflare.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
||||
proxy_probe:
|
||||
# Allow skipping TLS verification for proxy probe (debug only)
|
||||
# 允许代理探测时跳过 TLS 证书验证(仅用于调试)
|
||||
insecure_skip_verify: false
|
||||
|
||||
# =============================================================================
|
||||
# Gateway Configuration
|
||||
# 网关配置
|
||||
# =============================================================================
|
||||
gateway:
|
||||
# Timeout for waiting upstream response headers (seconds)
|
||||
# 等待上游响应头超时时间(秒)
|
||||
response_header_timeout: 600
|
||||
# Max request body size in bytes (default: 100MB)
|
||||
# 请求体最大字节数(默认 100MB)
|
||||
max_body_size: 104857600
|
||||
# Connection pool isolation strategy:
|
||||
# 连接池隔离策略:
|
||||
# - proxy: Isolate by proxy, same proxy shares connection pool (suitable for few proxies, many accounts)
|
||||
# - proxy: 按代理隔离,同一代理共享连接池(适合代理少、账户多)
|
||||
# - account: Isolate by account, same account shares connection pool (suitable for few accounts, strict isolation)
|
||||
# - account: 按账户隔离,同一账户共享连接池(适合账户少、需严格隔离)
|
||||
# - account_proxy: Isolate by account+proxy combination (default, finest granularity)
|
||||
# - account_proxy: 按账户+代理组合隔离(默认,最细粒度)
|
||||
connection_pool_isolation: "account_proxy"
|
||||
# HTTP upstream connection pool settings (HTTP/2 + multi-proxy scenario defaults)
|
||||
# HTTP 上游连接池配置(HTTP/2 + 多代理场景默认值)
|
||||
# Max idle connections across all hosts
|
||||
# 所有主机的最大空闲连接数
|
||||
max_idle_conns: 240
|
||||
# Max idle connections per host
|
||||
# 每个主机的最大空闲连接数
|
||||
max_idle_conns_per_host: 120
|
||||
# Max connections per host
|
||||
# 每个主机的最大连接数
|
||||
max_conns_per_host: 240
|
||||
# Idle connection timeout (seconds)
|
||||
# 空闲连接超时时间(秒)
|
||||
idle_conn_timeout_seconds: 90
|
||||
# Upstream client cache settings
|
||||
# 上游连接池客户端缓存配置
|
||||
# max_upstream_clients: Max cached clients, evicts least recently used when exceeded
|
||||
# max_upstream_clients: 最大缓存客户端数量,超出后淘汰最久未使用的
|
||||
max_upstream_clients: 5000
|
||||
# client_idle_ttl_seconds: Client idle reclaim threshold (seconds), reclaimed when idle and no active requests
|
||||
# client_idle_ttl_seconds: 客户端空闲回收阈值(秒),超时且无活跃请求时回收
|
||||
client_idle_ttl_seconds: 900
|
||||
# Concurrency slot expiration time (minutes)
|
||||
# 并发槽位过期时间(分钟)
|
||||
concurrency_slot_ttl_minutes: 30
|
||||
# Stream data interval timeout (seconds), 0=disable
|
||||
# 流数据间隔超时(秒),0=禁用
|
||||
stream_data_interval_timeout: 180
|
||||
# Stream keepalive interval (seconds), 0=disable
|
||||
# 流式 keepalive 间隔(秒),0=禁用
|
||||
stream_keepalive_interval: 10
|
||||
# SSE max line size in bytes (default: 40MB)
|
||||
# SSE 单行最大字节数(默认 40MB)
|
||||
max_line_size: 41943040
|
||||
# Log upstream error response body summary (safe/truncated; does not log request content)
|
||||
# 记录上游错误响应体摘要(安全/截断;不记录请求内容)
|
||||
log_upstream_error_body: true
|
||||
# Max bytes to log from upstream error body
|
||||
# 记录上游错误响应体的最大字节数
|
||||
log_upstream_error_body_max_bytes: 2048
|
||||
# Auto inject anthropic-beta header for API-key accounts when needed (default: off)
|
||||
# 需要时自动为 API-key 账户注入 anthropic-beta 头(默认:关闭)
|
||||
inject_beta_for_apikey: false
|
||||
# Allow failover on selected 400 errors (default: off)
|
||||
# 允许在特定 400 错误时进行故障转移(默认:关闭)
|
||||
failover_on_400: false
|
||||
# Scheduling configuration
|
||||
# 调度配置
|
||||
scheduling:
|
||||
# Sticky session max waiting queue size
|
||||
# 粘性会话最大排队长度
|
||||
sticky_session_max_waiting: 3
|
||||
# Sticky session wait timeout (duration)
|
||||
# 粘性会话等待超时(时间段)
|
||||
sticky_session_wait_timeout: 120s
|
||||
# Fallback wait timeout (duration)
|
||||
# 兜底排队等待超时(时间段)
|
||||
fallback_wait_timeout: 30s
|
||||
# Fallback max waiting queue size
|
||||
# 兜底最大排队长度
|
||||
fallback_max_waiting: 100
|
||||
# Enable batch load calculation for scheduling
|
||||
# 启用调度批量负载计算
|
||||
load_batch_enabled: true
|
||||
# Slot cleanup interval (duration)
|
||||
# 并发槽位清理周期(时间段)
|
||||
slot_cleanup_interval: 30s
|
||||
# 是否允许受控回源到 DB(默认 true,保持现有行为)
|
||||
db_fallback_enabled: true
|
||||
# 受控回源超时(秒),0 表示不额外收紧超时
|
||||
db_fallback_timeout_seconds: 0
|
||||
# 受控回源限流(实例级 QPS),0 表示不限制
|
||||
db_fallback_max_qps: 0
|
||||
# outbox 轮询周期(秒)
|
||||
outbox_poll_interval_seconds: 1
|
||||
# outbox 滞后告警阈值(秒)
|
||||
outbox_lag_warn_seconds: 5
|
||||
# outbox 触发强制重建阈值(秒)
|
||||
outbox_lag_rebuild_seconds: 10
|
||||
# outbox 连续滞后触发次数
|
||||
outbox_lag_rebuild_failures: 3
|
||||
# outbox 积压触发重建阈值(行数)
|
||||
outbox_backlog_rebuild_rows: 10000
|
||||
# 全量重建周期(秒),0 表示禁用
|
||||
full_rebuild_interval_seconds: 300
|
||||
|
||||
# =============================================================================
|
||||
# API Key Auth Cache Configuration
|
||||
# API Key 认证缓存配置
|
||||
# =============================================================================
|
||||
api_key_auth_cache:
|
||||
# L1 cache size (entries), in-process LRU/TTL cache
|
||||
# L1 缓存容量(条目数),进程内 LRU/TTL 缓存
|
||||
l1_size: 65535
|
||||
# L1 cache TTL (seconds)
|
||||
# L1 缓存 TTL(秒)
|
||||
l1_ttl_seconds: 15
|
||||
# L2 cache TTL (seconds), stored in Redis
|
||||
# L2 缓存 TTL(秒),Redis 中存储
|
||||
l2_ttl_seconds: 300
|
||||
# Negative cache TTL (seconds)
|
||||
# 负缓存 TTL(秒)
|
||||
negative_ttl_seconds: 30
|
||||
# TTL jitter percent (0-100)
|
||||
# TTL 抖动百分比(0-100)
|
||||
jitter_percent: 10
|
||||
# Enable singleflight for cache misses
|
||||
# 缓存未命中时启用 singleflight 合并回源
|
||||
singleflight: true
|
||||
|
||||
# =============================================================================
|
||||
# Dashboard Cache Configuration
|
||||
# 仪表盘缓存配置
|
||||
# =============================================================================
|
||||
dashboard_cache:
|
||||
# Enable dashboard cache
|
||||
# 启用仪表盘缓存
|
||||
enabled: true
|
||||
# Redis key prefix for multi-environment isolation
|
||||
# Redis key 前缀,用于多环境隔离
|
||||
key_prefix: "sub2api:"
|
||||
# Fresh TTL (seconds); within this window cached stats are considered fresh
|
||||
# 新鲜阈值(秒);命中后处于该窗口视为新鲜数据
|
||||
stats_fresh_ttl_seconds: 15
|
||||
# Cache TTL (seconds) stored in Redis
|
||||
# Redis 缓存 TTL(秒)
|
||||
stats_ttl_seconds: 30
|
||||
# Async refresh timeout (seconds)
|
||||
# 异步刷新超时(秒)
|
||||
stats_refresh_timeout_seconds: 30
|
||||
|
||||
# =============================================================================
|
||||
# Dashboard Aggregation Configuration
|
||||
# 仪表盘预聚合配置(重启生效)
|
||||
# =============================================================================
|
||||
dashboard_aggregation:
|
||||
# Enable aggregation job
|
||||
# 启用聚合作业
|
||||
enabled: true
|
||||
# Refresh interval (seconds)
|
||||
# 刷新间隔(秒)
|
||||
interval_seconds: 60
|
||||
# Lookback window (seconds) for late-arriving data
|
||||
# 回看窗口(秒),处理迟到数据
|
||||
lookback_seconds: 120
|
||||
# Allow manual backfill
|
||||
# 允许手动回填
|
||||
backfill_enabled: false
|
||||
# Backfill max range (days)
|
||||
# 回填最大跨度(天)
|
||||
backfill_max_days: 31
|
||||
# Recompute recent N days on startup
|
||||
# 启动时重算最近 N 天
|
||||
recompute_days: 2
|
||||
# Retention windows (days)
|
||||
# 保留窗口(天)
|
||||
retention:
|
||||
# Raw usage_logs retention
|
||||
# 原始 usage_logs 保留天数
|
||||
usage_logs_days: 90
|
||||
# Hourly aggregation retention
|
||||
# 小时聚合保留天数
|
||||
hourly_days: 180
|
||||
# Daily aggregation retention
|
||||
# 日聚合保留天数
|
||||
daily_days: 730
|
||||
|
||||
# =============================================================================
|
||||
# Concurrency Wait Configuration
|
||||
# 并发等待配置
|
||||
# =============================================================================
|
||||
concurrency:
|
||||
# SSE ping interval during concurrency wait (seconds)
|
||||
# 并发等待期间的 SSE ping 间隔(秒)
|
||||
ping_interval: 10
|
||||
|
||||
# =============================================================================
|
||||
# Database Configuration (PostgreSQL)
|
||||
# 数据库配置 (PostgreSQL)
|
||||
# =============================================================================
|
||||
database:
|
||||
# Database host address
|
||||
# 数据库主机地址
|
||||
host: "localhost"
|
||||
# Database port
|
||||
# 数据库端口
|
||||
port: 5432
|
||||
# Database username
|
||||
# 数据库用户名
|
||||
user: "postgres"
|
||||
# Database password
|
||||
# 数据库密码
|
||||
password: "your_secure_password_here"
|
||||
# Database name
|
||||
# 数据库名称
|
||||
dbname: "sub2api"
|
||||
# SSL mode: disable, require, verify-ca, verify-full
|
||||
# SSL 模式:disable(禁用), require(要求), verify-ca(验证CA), verify-full(完全验证)
|
||||
sslmode: "disable"
|
||||
|
||||
# =============================================================================
|
||||
# Redis Configuration
|
||||
# Redis 配置
|
||||
# =============================================================================
|
||||
redis:
|
||||
# Redis host address
|
||||
# Redis 主机地址
|
||||
host: "localhost"
|
||||
# Redis port
|
||||
# Redis 端口
|
||||
port: 6379
|
||||
# Redis password (leave empty if no password is set)
|
||||
# Redis 密码(如果未设置密码则留空)
|
||||
password: ""
|
||||
# Database number (0-15)
|
||||
# 数据库编号(0-15)
|
||||
db: 0
|
||||
|
||||
# =============================================================================
|
||||
# Ops Monitoring (Optional)
|
||||
# 运维监控 (可选)
|
||||
# =============================================================================
|
||||
ops:
|
||||
# Enable ops monitoring features (background jobs and APIs)
|
||||
# 是否启用运维监控功能(后台任务和接口)
|
||||
# Set to false to hide ops menu in sidebar and disable all ops features
|
||||
# 设置为 false 可在左侧栏隐藏运维监控菜单并禁用所有运维监控功能
|
||||
# Other detailed settings (cleanup, aggregation, etc.) are configured in ops settings dialog
|
||||
# 其他详细设置(数据清理、预聚合等)在运维监控设置对话框中配置
|
||||
enabled: true
|
||||
|
||||
# =============================================================================
|
||||
# JWT Configuration
|
||||
# JWT 配置
|
||||
# =============================================================================
|
||||
jwt:
|
||||
# IMPORTANT: Change this to a random string in production!
|
||||
# 重要:生产环境中请更改为随机字符串!
|
||||
# Generate with / 生成命令: openssl rand -hex 32
|
||||
secret: "change-this-to-a-secure-random-string"
|
||||
# Token expiration time in hours (max 24)
|
||||
# 令牌过期时间(小时,最大 24)
|
||||
expire_hour: 24
|
||||
|
||||
# =============================================================================
|
||||
# LinuxDo Connect OAuth Login (SSO)
|
||||
# LinuxDo Connect OAuth 登录(用于 Sub2API 用户登录)
|
||||
# =============================================================================
|
||||
linuxdo_connect:
|
||||
enabled: false
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
authorize_url: "https://connect.linux.do/oauth2/authorize"
|
||||
token_url: "https://connect.linux.do/oauth2/token"
|
||||
userinfo_url: "https://connect.linux.do/api/user"
|
||||
scopes: "user"
|
||||
# 示例: "https://your-domain.com/api/v1/auth/oauth/linuxdo/callback"
|
||||
redirect_url: ""
|
||||
# 安全提示:
|
||||
# - 建议使用同源相对路径(以 / 开头),避免把 token 重定向到意外的第三方域名
|
||||
# - 该地址不应包含 #fragment(本实现使用 URL fragment 传递 access_token)
|
||||
frontend_redirect_url: "/auth/linuxdo/callback"
|
||||
token_auth_method: "client_secret_post" # client_secret_post | client_secret_basic | none
|
||||
# 注意:当 token_auth_method=none(public client)时,必须启用 PKCE
|
||||
use_pkce: false
|
||||
userinfo_email_path: ""
|
||||
userinfo_id_path: ""
|
||||
userinfo_username_path: ""
|
||||
|
||||
# =============================================================================
|
||||
# Default Settings
|
||||
# 默认设置
|
||||
# =============================================================================
|
||||
default:
|
||||
# Initial admin account (created on first run)
|
||||
# 初始管理员账户(首次运行时创建)
|
||||
admin_email: "admin@example.com"
|
||||
admin_password: "admin123"
|
||||
|
||||
# Default settings for new users
|
||||
# 新用户默认设置
|
||||
# Max concurrent requests per user
|
||||
# 每用户最大并发请求数
|
||||
user_concurrency: 5
|
||||
# Initial balance for new users
|
||||
# 新用户初始余额
|
||||
user_balance: 0
|
||||
|
||||
# API key settings
|
||||
# API 密钥设置
|
||||
# Prefix for generated API keys
|
||||
# 生成的 API 密钥前缀
|
||||
api_key_prefix: "sk-"
|
||||
|
||||
# Rate multiplier (affects billing calculation)
|
||||
# 费率倍数(影响计费计算)
|
||||
rate_multiplier: 1.0
|
||||
|
||||
# =============================================================================
|
||||
# Rate Limiting
|
||||
# 速率限制
|
||||
# =============================================================================
|
||||
rate_limit:
|
||||
# Cooldown time (in minutes) when upstream returns 529 (overloaded)
|
||||
# 上游返回 529(过载)时的冷却时间(分钟)
|
||||
overload_cooldown_minutes: 10
|
||||
|
||||
# =============================================================================
|
||||
# Pricing Data Source (Optional)
|
||||
# 定价数据源(可选)
|
||||
# =============================================================================
|
||||
pricing:
|
||||
# URL to fetch model pricing data (default: LiteLLM)
|
||||
# 获取模型定价数据的 URL(默认:LiteLLM)
|
||||
remote_url: "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
|
||||
# Hash verification URL (optional)
|
||||
# 哈希校验 URL(可选)
|
||||
hash_url: ""
|
||||
# Local data directory for caching
|
||||
# 本地数据缓存目录
|
||||
data_dir: "./data"
|
||||
# Fallback pricing file
|
||||
# 备用定价文件
|
||||
fallback_file: "./resources/model-pricing/model_prices_and_context_window.json"
|
||||
# Update interval in hours
|
||||
# 更新间隔(小时)
|
||||
update_interval_hours: 24
|
||||
# Hash check interval in minutes
|
||||
# 哈希检查间隔(分钟)
|
||||
hash_check_interval_minutes: 10
|
||||
|
||||
# =============================================================================
|
||||
# Billing Configuration
|
||||
# 计费配置
|
||||
# =============================================================================
|
||||
billing:
|
||||
circuit_breaker:
|
||||
# Enable circuit breaker for billing service
|
||||
# 启用计费服务熔断器
|
||||
enabled: true
|
||||
# Number of failures before opening circuit
|
||||
# 触发熔断的失败次数阈值
|
||||
failure_threshold: 5
|
||||
# Time to wait before attempting reset (seconds)
|
||||
# 熔断后重试等待时间(秒)
|
||||
reset_timeout_seconds: 30
|
||||
# Number of requests to allow in half-open state
|
||||
# 半开状态允许通过的请求数
|
||||
half_open_requests: 3
|
||||
|
||||
# =============================================================================
|
||||
# Turnstile Configuration
|
||||
# Turnstile 人机验证配置
|
||||
# =============================================================================
|
||||
turnstile:
|
||||
# Require Turnstile in release mode (when enabled, login/register will fail if not configured)
|
||||
# 在 release 模式下要求 Turnstile 验证(启用后,若未配置则登录/注册会失败)
|
||||
required: false
|
||||
|
||||
# =============================================================================
|
||||
# Gemini OAuth (Required for Gemini accounts)
|
||||
# Gemini OAuth 配置(Gemini 账户必需)
|
||||
# =============================================================================
|
||||
# Sub2API supports TWO Gemini OAuth modes:
|
||||
# Sub2API 支持两种 Gemini OAuth 模式:
|
||||
#
|
||||
# 1. Code Assist OAuth (requires GCP project_id)
|
||||
# 1. Code Assist OAuth(需要 GCP project_id)
|
||||
# - Uses: cloudcode-pa.googleapis.com (Code Assist API)
|
||||
# - 使用:cloudcode-pa.googleapis.com(Code Assist API)
|
||||
#
|
||||
# 2. AI Studio OAuth (no project_id needed)
|
||||
# 2. AI Studio OAuth(不需要 project_id)
|
||||
# - Uses: generativelanguage.googleapis.com (AI Studio API)
|
||||
# - 使用:generativelanguage.googleapis.com(AI Studio API)
|
||||
#
|
||||
# Default: Uses Gemini CLI's public OAuth credentials (same as Google's official CLI tool)
|
||||
# 默认:使用 Gemini CLI 的公开 OAuth 凭证(与 Google 官方 CLI 工具相同)
|
||||
gemini:
|
||||
oauth:
|
||||
# Gemini CLI public OAuth credentials (works for both Code Assist and AI Studio)
|
||||
# Gemini CLI 公开 OAuth 凭证(适用于 Code Assist 和 AI Studio)
|
||||
client_id: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com"
|
||||
client_secret: "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"
|
||||
# Optional scopes (space-separated). Leave empty to auto-select based on oauth_type.
|
||||
# 可选的权限范围(空格分隔)。留空则根据 oauth_type 自动选择。
|
||||
scopes: ""
|
||||
quota:
|
||||
# Optional: local quota simulation for Gemini Code Assist (local billing).
|
||||
# 可选:Gemini Code Assist 本地配额模拟(本地计费)。
|
||||
# These values are used for UI progress + precheck scheduling, not official Google quotas.
|
||||
# 这些值用于 UI 进度显示和预检调度,并非 Google 官方配额。
|
||||
tiers:
|
||||
LEGACY:
|
||||
# Pro model requests per day
|
||||
# Pro 模型每日请求数
|
||||
pro_rpd: 50
|
||||
# Flash model requests per day
|
||||
# Flash 模型每日请求数
|
||||
flash_rpd: 1500
|
||||
# Cooldown time (minutes) after hitting quota
|
||||
# 达到配额后的冷却时间(分钟)
|
||||
cooldown_minutes: 30
|
||||
PRO:
|
||||
# Pro model requests per day
|
||||
# Pro 模型每日请求数
|
||||
pro_rpd: 1500
|
||||
# Flash model requests per day
|
||||
# Flash 模型每日请求数
|
||||
flash_rpd: 4000
|
||||
# Cooldown time (minutes) after hitting quota
|
||||
# 达到配额后的冷却时间(分钟)
|
||||
cooldown_minutes: 5
|
||||
ULTRA:
|
||||
# Pro model requests per day
|
||||
# Pro 模型每日请求数
|
||||
pro_rpd: 2000
|
||||
# Flash model requests per day (0 = unlimited)
|
||||
# Flash 模型每日请求数(0 = 无限制)
|
||||
flash_rpd: 0
|
||||
# Cooldown time (minutes) after hitting quota
|
||||
# 达到配额后的冷却时间(分钟)
|
||||
cooldown_minutes: 5
|
||||
|
||||
# =============================================================================
|
||||
# Update Configuration (在线更新配置)
|
||||
# =============================================================================
|
||||
update:
|
||||
# Proxy URL for accessing GitHub (used for online updates and pricing data)
|
||||
# 用于访问 GitHub 的代理地址(用于在线更新和定价数据获取)
|
||||
# Supports: http, https, socks5, socks5h
|
||||
# Examples:
|
||||
# - HTTP proxy: "http://127.0.0.1:7890"
|
||||
# - SOCKS5 proxy: "socks5://127.0.0.1:1080"
|
||||
# - With authentication: "http://user:pass@proxy.example.com:8080"
|
||||
# Leave empty for direct connection (recommended for overseas servers)
|
||||
# 留空表示直连(适用于海外服务器)
|
||||
proxy_url: ""
|
||||
197
deploy/docker-compose-test.yml
Normal file
197
deploy/docker-compose-test.yml
Normal file
@@ -0,0 +1,197 @@
|
||||
# =============================================================================
|
||||
# Sub2API Docker Compose Test Configuration (Local Build)
|
||||
# =============================================================================
|
||||
# Quick Start:
|
||||
# 1. Copy .env.example to .env and configure
|
||||
# 2. docker-compose -f docker-compose-test.yml up -d --build
|
||||
# 3. Check logs: docker-compose -f docker-compose-test.yml logs -f sub2api
|
||||
# 4. Access: http://localhost:8080
|
||||
#
|
||||
# This configuration builds the image from source (Dockerfile in project root).
|
||||
# All configuration is done via environment variables.
|
||||
# No Setup Wizard needed - the system auto-initializes on first run.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ===========================================================================
|
||||
# Sub2API Application
|
||||
# ===========================================================================
|
||||
sub2api:
|
||||
image: sub2api:latest
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: Dockerfile
|
||||
container_name: sub2api
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
ports:
|
||||
- "${BIND_HOST:-0.0.0.0}:${SERVER_PORT:-8080}:8080"
|
||||
volumes:
|
||||
# Data persistence (config.yaml will be auto-generated here)
|
||||
- sub2api_data:/app/data
|
||||
# Mount custom config.yaml (optional, overrides auto-generated config)
|
||||
- ./config.yaml:/app/data/config.yaml:ro
|
||||
environment:
|
||||
# =======================================================================
|
||||
# Auto Setup (REQUIRED for Docker deployment)
|
||||
# =======================================================================
|
||||
- AUTO_SETUP=true
|
||||
|
||||
# =======================================================================
|
||||
# Server Configuration
|
||||
# =======================================================================
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- SERVER_PORT=8080
|
||||
- SERVER_MODE=${SERVER_MODE:-release}
|
||||
- RUN_MODE=${RUN_MODE:-standard}
|
||||
|
||||
# =======================================================================
|
||||
# Database Configuration (PostgreSQL)
|
||||
# =======================================================================
|
||||
- DATABASE_HOST=postgres
|
||||
- DATABASE_PORT=5432
|
||||
- DATABASE_USER=${POSTGRES_USER:-sub2api}
|
||||
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
||||
- DATABASE_SSLMODE=disable
|
||||
|
||||
# =======================================================================
|
||||
# Redis Configuration
|
||||
# =======================================================================
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
|
||||
# =======================================================================
|
||||
# Admin Account (auto-created on first run)
|
||||
# =======================================================================
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@sub2api.local}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-}
|
||||
|
||||
# =======================================================================
|
||||
# JWT Configuration
|
||||
# =======================================================================
|
||||
# Leave empty to auto-generate (recommended)
|
||||
- JWT_SECRET=${JWT_SECRET:-}
|
||||
- JWT_EXPIRE_HOUR=${JWT_EXPIRE_HOUR:-24}
|
||||
|
||||
# =======================================================================
|
||||
# Timezone Configuration
|
||||
# This affects ALL time operations in the application:
|
||||
# - Database timestamps
|
||||
# - Usage statistics "today" boundary
|
||||
# - Subscription expiry times
|
||||
# - Log timestamps
|
||||
# Common values: Asia/Shanghai, America/New_York, Europe/London, UTC
|
||||
# =======================================================================
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
|
||||
# =======================================================================
|
||||
# Gemini OAuth Configuration (for Gemini accounts)
|
||||
# =======================================================================
|
||||
- GEMINI_OAUTH_CLIENT_ID=${GEMINI_OAUTH_CLIENT_ID:-}
|
||||
- GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-}
|
||||
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
|
||||
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
|
||||
|
||||
# =======================================================================
|
||||
# Security Configuration (URL Allowlist)
|
||||
# =======================================================================
|
||||
# Allow private IP addresses for CRS sync (for internal deployments)
|
||||
- SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=${SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS:-true}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- sub2api-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# ===========================================================================
|
||||
# PostgreSQL Database
|
||||
# ===========================================================================
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
container_name: sub2api-postgres
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER:-sub2api}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-sub2api}
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
networks:
|
||||
- sub2api-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
# 注意:不暴露端口到宿主机,应用通过内部网络连接
|
||||
# 如需调试,可临时添加:ports: ["127.0.0.1:5433:5432"]
|
||||
|
||||
# ===========================================================================
|
||||
# Redis Cache
|
||||
# ===========================================================================
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: sub2api-redis
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: >
|
||||
redis-server
|
||||
--save 60 1
|
||||
--appendonly yes
|
||||
--appendfsync everysec
|
||||
${REDIS_PASSWORD:+--requirepass ${REDIS_PASSWORD}}
|
||||
environment:
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
# REDISCLI_AUTH is used by redis-cli for authentication (safer than -a flag)
|
||||
- REDISCLI_AUTH=${REDIS_PASSWORD:-}
|
||||
networks:
|
||||
- sub2api-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
|
||||
# =============================================================================
|
||||
# Volumes
|
||||
# =============================================================================
|
||||
volumes:
|
||||
sub2api_data:
|
||||
driver: local
|
||||
postgres_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
# =============================================================================
|
||||
# Networks
|
||||
# =============================================================================
|
||||
networks:
|
||||
sub2api-network:
|
||||
driver: bridge
|
||||
137
deploy/docker-compose.override.yml.example
Normal file
137
deploy/docker-compose.override.yml.example
Normal file
@@ -0,0 +1,137 @@
|
||||
# =============================================================================
|
||||
# Docker Compose Override Configuration Example
|
||||
# =============================================================================
|
||||
# This file provides examples for customizing the Docker Compose setup.
|
||||
# Copy this file to docker-compose.override.yml and modify as needed.
|
||||
#
|
||||
# Usage:
|
||||
# cp docker-compose.override.yml.example docker-compose.override.yml
|
||||
# # Edit docker-compose.override.yml with your settings
|
||||
# docker-compose up -d
|
||||
#
|
||||
# IMPORTANT: docker-compose.override.yml is gitignored and will not be committed.
|
||||
# =============================================================================
|
||||
|
||||
# =============================================================================
|
||||
# Scenario 1: Use External Database and Redis (Recommended for Production)
|
||||
# =============================================================================
|
||||
# Use this when you have PostgreSQL and Redis running on the host machine
|
||||
# or on separate servers.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - PostgreSQL running on host (accessible via host.docker.internal)
|
||||
# - Redis running on host (accessible via host.docker.internal)
|
||||
# - Update DATABASE_PORT and REDIS_PORT in .env file if using non-standard ports
|
||||
#
|
||||
# Security Notes:
|
||||
# - Ensure PostgreSQL pg_hba.conf allows connections from Docker network
|
||||
# - Use strong passwords for database and Redis
|
||||
# - Consider using SSL/TLS for database connections in production
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
sub2api:
|
||||
# Remove dependencies on containerized postgres/redis
|
||||
depends_on: []
|
||||
|
||||
# Enable access to host machine services
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
# Override database and Redis connection settings
|
||||
environment:
|
||||
# PostgreSQL Configuration
|
||||
DATABASE_HOST: host.docker.internal
|
||||
DATABASE_PORT: "5678" # Change to your PostgreSQL port
|
||||
# DATABASE_USER: postgres # Uncomment to override
|
||||
# DATABASE_PASSWORD: your_password # Uncomment to override
|
||||
# DATABASE_DBNAME: sub2api # Uncomment to override
|
||||
|
||||
# Redis Configuration
|
||||
REDIS_HOST: host.docker.internal
|
||||
REDIS_PORT: "6379" # Change to your Redis port
|
||||
# REDIS_PASSWORD: your_redis_password # Uncomment if Redis requires auth
|
||||
# REDIS_DB: 0 # Uncomment to override
|
||||
|
||||
# Disable containerized PostgreSQL
|
||||
postgres:
|
||||
deploy:
|
||||
replicas: 0
|
||||
scale: 0
|
||||
|
||||
# Disable containerized Redis
|
||||
redis:
|
||||
deploy:
|
||||
replicas: 0
|
||||
scale: 0
|
||||
|
||||
# =============================================================================
|
||||
# Scenario 2: Development with Local Services (Alternative)
|
||||
# =============================================================================
|
||||
# Uncomment this section if you want to use the containerized postgres/redis
|
||||
# but expose their ports for local development tools.
|
||||
#
|
||||
# Usage: Comment out Scenario 1 above and uncomment this section.
|
||||
# =============================================================================
|
||||
|
||||
# services:
|
||||
# sub2api:
|
||||
# # Keep default dependencies
|
||||
# pass
|
||||
#
|
||||
# postgres:
|
||||
# ports:
|
||||
# - "127.0.0.1:5432:5432" # Expose PostgreSQL on localhost
|
||||
#
|
||||
# redis:
|
||||
# ports:
|
||||
# - "127.0.0.1:6379:6379" # Expose Redis on localhost
|
||||
|
||||
# =============================================================================
|
||||
# Scenario 3: Custom Network Configuration
|
||||
# =============================================================================
|
||||
# Uncomment if you need to connect to an existing Docker network
|
||||
# =============================================================================
|
||||
|
||||
# networks:
|
||||
# default:
|
||||
# external: true
|
||||
# name: your-existing-network
|
||||
|
||||
# =============================================================================
|
||||
# Scenario 4: Resource Limits (Production)
|
||||
# =============================================================================
|
||||
# Uncomment to set resource limits for the sub2api container
|
||||
# =============================================================================
|
||||
|
||||
# services:
|
||||
# sub2api:
|
||||
# deploy:
|
||||
# resources:
|
||||
# limits:
|
||||
# cpus: '2.0'
|
||||
# memory: 2G
|
||||
# reservations:
|
||||
# cpus: '1.0'
|
||||
# memory: 1G
|
||||
|
||||
# =============================================================================
|
||||
# Scenario 5: Custom Volumes
|
||||
# =============================================================================
|
||||
# Uncomment to mount additional volumes (e.g., for logs, backups)
|
||||
# =============================================================================
|
||||
|
||||
# services:
|
||||
# sub2api:
|
||||
# volumes:
|
||||
# - ./logs:/app/logs
|
||||
# - ./backups:/app/backups
|
||||
|
||||
# =============================================================================
|
||||
# Additional Notes
|
||||
# =============================================================================
|
||||
# - This file overrides settings in docker-compose.yml
|
||||
# - Environment variables in .env file take precedence
|
||||
# - For more information, see: https://docs.docker.com/compose/extends/
|
||||
# - Check the main README.md for detailed configuration instructions
|
||||
# =============================================================================
|
||||
93
deploy/docker-compose.standalone.yml
Normal file
93
deploy/docker-compose.standalone.yml
Normal file
@@ -0,0 +1,93 @@
|
||||
# =============================================================================
|
||||
# Sub2API Docker Compose - Standalone Configuration
|
||||
# =============================================================================
|
||||
# This configuration runs only the Sub2API application.
|
||||
# PostgreSQL and Redis must be provided externally.
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy .env.example to .env and configure database/redis connection
|
||||
# 2. docker-compose -f docker-compose.standalone.yml up -d
|
||||
# 3. Access: http://localhost:8080
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
sub2api:
|
||||
image: weishaw/sub2api:latest
|
||||
container_name: sub2api
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
ports:
|
||||
- "${BIND_HOST:-0.0.0.0}:${SERVER_PORT:-8080}:8080"
|
||||
volumes:
|
||||
- sub2api_data:/app/data
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
environment:
|
||||
# =======================================================================
|
||||
# Auto Setup
|
||||
# =======================================================================
|
||||
- AUTO_SETUP=true
|
||||
|
||||
# =======================================================================
|
||||
# Server Configuration
|
||||
# =======================================================================
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- SERVER_PORT=8080
|
||||
- SERVER_MODE=${SERVER_MODE:-release}
|
||||
- RUN_MODE=${RUN_MODE:-standard}
|
||||
|
||||
# =======================================================================
|
||||
# Database Configuration (PostgreSQL) - Required
|
||||
# =======================================================================
|
||||
- DATABASE_HOST=${DATABASE_HOST:?DATABASE_HOST is required}
|
||||
- DATABASE_PORT=${DATABASE_PORT:-5432}
|
||||
- DATABASE_USER=${DATABASE_USER:-sub2api}
|
||||
- DATABASE_PASSWORD=${DATABASE_PASSWORD:?DATABASE_PASSWORD is required}
|
||||
- DATABASE_DBNAME=${DATABASE_DBNAME:-sub2api}
|
||||
- DATABASE_SSLMODE=${DATABASE_SSLMODE:-disable}
|
||||
|
||||
# =======================================================================
|
||||
# Redis Configuration - Required
|
||||
# =======================================================================
|
||||
- REDIS_HOST=${REDIS_HOST:?REDIS_HOST is required}
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
|
||||
# =======================================================================
|
||||
# Admin Account (auto-created on first run)
|
||||
# =======================================================================
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@sub2api.local}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-}
|
||||
|
||||
# =======================================================================
|
||||
# JWT Configuration
|
||||
# =======================================================================
|
||||
- JWT_SECRET=${JWT_SECRET:-}
|
||||
- JWT_EXPIRE_HOUR=${JWT_EXPIRE_HOUR:-24}
|
||||
|
||||
# =======================================================================
|
||||
# Timezone Configuration
|
||||
# =======================================================================
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
|
||||
# =======================================================================
|
||||
# Gemini OAuth Configuration (optional)
|
||||
# =======================================================================
|
||||
- GEMINI_OAUTH_CLIENT_ID=${GEMINI_OAUTH_CLIENT_ID:-}
|
||||
- GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-}
|
||||
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
|
||||
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
volumes:
|
||||
sub2api_data:
|
||||
driver: local
|
||||
211
deploy/docker-compose.yml
Normal file
211
deploy/docker-compose.yml
Normal file
@@ -0,0 +1,211 @@
|
||||
# =============================================================================
|
||||
# Sub2API Docker Compose Configuration
|
||||
# =============================================================================
|
||||
# Quick Start:
|
||||
# 1. Copy .env.example to .env and configure
|
||||
# 2. docker-compose up -d
|
||||
# 3. Check logs: docker-compose logs -f sub2api
|
||||
# 4. Access: http://localhost:8080
|
||||
#
|
||||
# All configuration is done via environment variables.
|
||||
# No Setup Wizard needed - the system auto-initializes on first run.
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
# ===========================================================================
|
||||
# Sub2API Application
|
||||
# ===========================================================================
|
||||
sub2api:
|
||||
image: weishaw/sub2api:latest
|
||||
container_name: sub2api
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
ports:
|
||||
- "${BIND_HOST:-0.0.0.0}:${SERVER_PORT:-8080}:8080"
|
||||
volumes:
|
||||
# Data persistence (config.yaml will be auto-generated here)
|
||||
- sub2api_data:/app/data
|
||||
# Optional: Mount custom config.yaml (uncomment and create the file first)
|
||||
# Copy config.example.yaml to config.yaml, modify it, then uncomment:
|
||||
# - ./config.yaml:/app/data/config.yaml:ro
|
||||
environment:
|
||||
# =======================================================================
|
||||
# Auto Setup (REQUIRED for Docker deployment)
|
||||
# =======================================================================
|
||||
- AUTO_SETUP=true
|
||||
|
||||
# =======================================================================
|
||||
# Server Configuration
|
||||
# =======================================================================
|
||||
- SERVER_HOST=0.0.0.0
|
||||
- SERVER_PORT=8080
|
||||
- SERVER_MODE=${SERVER_MODE:-release}
|
||||
- RUN_MODE=${RUN_MODE:-standard}
|
||||
|
||||
# =======================================================================
|
||||
# Database Configuration (PostgreSQL)
|
||||
# =======================================================================
|
||||
- DATABASE_HOST=postgres
|
||||
- DATABASE_PORT=5432
|
||||
- DATABASE_USER=${POSTGRES_USER:-sub2api}
|
||||
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
||||
- DATABASE_SSLMODE=disable
|
||||
|
||||
# =======================================================================
|
||||
# Redis Configuration
|
||||
# =======================================================================
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||
- REDIS_DB=${REDIS_DB:-0}
|
||||
|
||||
# =======================================================================
|
||||
# Admin Account (auto-created on first run)
|
||||
# =======================================================================
|
||||
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@sub2api.local}
|
||||
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-}
|
||||
|
||||
# =======================================================================
|
||||
# JWT Configuration
|
||||
# =======================================================================
|
||||
# IMPORTANT: Set a fixed JWT_SECRET to prevent login sessions from being
|
||||
# invalidated after container restarts. If left empty, a random secret
|
||||
# will be generated on each startup.
|
||||
# Generate a secure secret: openssl rand -hex 32
|
||||
- JWT_SECRET=${JWT_SECRET:-}
|
||||
- JWT_EXPIRE_HOUR=${JWT_EXPIRE_HOUR:-24}
|
||||
|
||||
# =======================================================================
|
||||
# Timezone Configuration
|
||||
# This affects ALL time operations in the application:
|
||||
# - Database timestamps
|
||||
# - Usage statistics "today" boundary
|
||||
# - Subscription expiry times
|
||||
# - Log timestamps
|
||||
# Common values: Asia/Shanghai, America/New_York, Europe/London, UTC
|
||||
# =======================================================================
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
|
||||
# =======================================================================
|
||||
# Gemini OAuth Configuration (for Gemini accounts)
|
||||
# =======================================================================
|
||||
- GEMINI_OAUTH_CLIENT_ID=${GEMINI_OAUTH_CLIENT_ID:-}
|
||||
- GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-}
|
||||
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
|
||||
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
|
||||
|
||||
# =======================================================================
|
||||
# Security Configuration (URL Allowlist)
|
||||
# =======================================================================
|
||||
# Enable URL allowlist validation (false to skip allowlist checks)
|
||||
- SECURITY_URL_ALLOWLIST_ENABLED=${SECURITY_URL_ALLOWLIST_ENABLED:-false}
|
||||
# Allow insecure HTTP URLs when allowlist is disabled (default: false, requires https)
|
||||
- SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP=${SECURITY_URL_ALLOWLIST_ALLOW_INSECURE_HTTP:-false}
|
||||
# Allow private IP addresses for upstream/pricing/CRS (for internal deployments)
|
||||
- SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS=${SECURITY_URL_ALLOWLIST_ALLOW_PRIVATE_HOSTS:-false}
|
||||
# Upstream hosts whitelist (comma-separated, only used when enabled=true)
|
||||
- SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS=${SECURITY_URL_ALLOWLIST_UPSTREAM_HOSTS:-}
|
||||
|
||||
# =======================================================================
|
||||
# Update Configuration (在线更新配置)
|
||||
# =======================================================================
|
||||
# Proxy for accessing GitHub (online updates + pricing data)
|
||||
# Examples: http://host:port, socks5://host:port
|
||||
- UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- sub2api-network
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# ===========================================================================
|
||||
# PostgreSQL Database
|
||||
# ===========================================================================
|
||||
postgres:
|
||||
image: postgres:18-alpine
|
||||
container_name: sub2api-postgres
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER:-sub2api}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-sub2api}
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
networks:
|
||||
- sub2api-network
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
# 注意:不暴露端口到宿主机,应用通过内部网络连接
|
||||
# 如需调试,可临时添加:ports: ["127.0.0.1:5433:5432"]
|
||||
|
||||
# ===========================================================================
|
||||
# Redis Cache
|
||||
# ===========================================================================
|
||||
redis:
|
||||
image: redis:8-alpine
|
||||
container_name: sub2api-redis
|
||||
restart: unless-stopped
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 100000
|
||||
hard: 100000
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: >
|
||||
sh -c '
|
||||
redis-server
|
||||
--save 60 1
|
||||
--appendonly yes
|
||||
--appendfsync everysec
|
||||
${REDIS_PASSWORD:+--requirepass "$REDIS_PASSWORD"}'
|
||||
environment:
|
||||
- TZ=${TZ:-Asia/Shanghai}
|
||||
# REDISCLI_AUTH is used by redis-cli for authentication (safer than -a flag)
|
||||
- REDISCLI_AUTH=${REDIS_PASSWORD:-}
|
||||
networks:
|
||||
- sub2api-network
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 5s
|
||||
|
||||
# =============================================================================
|
||||
# Volumes
|
||||
# =============================================================================
|
||||
volumes:
|
||||
sub2api_data:
|
||||
driver: local
|
||||
postgres_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
# =============================================================================
|
||||
# Networks
|
||||
# =============================================================================
|
||||
networks:
|
||||
sub2api-network:
|
||||
driver: bridge
|
||||
222
deploy/flow.md
Normal file
222
deploy/flow.md
Normal file
@@ -0,0 +1,222 @@
|
||||
```mermaid
|
||||
flowchart TD
|
||||
%% Master dispatch
|
||||
A[HTTP Request] --> B{Route}
|
||||
B -->|v1 messages| GA0
|
||||
B -->|openai v1 responses| OA0
|
||||
B -->|v1beta models model action| GM0
|
||||
B -->|v1 messages count tokens| GT0
|
||||
B -->|v1beta models list or get| GL0
|
||||
|
||||
%% =========================
|
||||
%% FLOW A: Claude Gateway
|
||||
%% =========================
|
||||
subgraph FLOW_A["v1 messages Claude Gateway"]
|
||||
GA0[Auth middleware] --> GA1[Read body]
|
||||
GA1 -->|empty| GA1E[400 invalid_request_error]
|
||||
GA1 --> GA2[ParseGatewayRequest]
|
||||
GA2 -->|parse error| GA2E[400 invalid_request_error]
|
||||
GA2 --> GA3{model present}
|
||||
GA3 -->|no| GA3E[400 invalid_request_error]
|
||||
GA3 --> GA4[streamStarted false]
|
||||
GA4 --> GA5[IncrementWaitCount user]
|
||||
GA5 -->|queue full| GA5E[429 rate_limit_error]
|
||||
GA5 --> GA6[AcquireUserSlotWithWait]
|
||||
GA6 -->|timeout or fail| GA6E[429 rate_limit_error]
|
||||
GA6 --> GA7[BillingEligibility check post wait]
|
||||
GA7 -->|fail| GA7E[403 billing_error]
|
||||
GA7 --> GA8[Generate sessionHash]
|
||||
GA8 --> GA9[Resolve platform]
|
||||
GA9 --> GA10{platform gemini}
|
||||
GA10 -->|yes| GA10Y[sessionKey gemini hash]
|
||||
GA10 -->|no| GA10N[sessionKey hash]
|
||||
GA10Y --> GA11
|
||||
GA10N --> GA11
|
||||
|
||||
GA11[SelectAccountWithLoadAwareness] -->|err and no failed| GA11E1[503 no available accounts]
|
||||
GA11 -->|err and failed| GA11E2[map failover error]
|
||||
GA11 --> GA12[Warmup intercept]
|
||||
GA12 -->|yes| GA12Y[return mock and release if held]
|
||||
GA12 -->|no| GA13[Acquire account slot or wait]
|
||||
GA13 -->|wait queue full| GA13E1[429 rate_limit_error]
|
||||
GA13 -->|wait timeout| GA13E2[429 concurrency limit]
|
||||
GA13 --> GA14[BindStickySession if waited]
|
||||
GA14 --> GA15{account platform antigravity}
|
||||
GA15 -->|yes| GA15Y[ForwardGemini antigravity]
|
||||
GA15 -->|no| GA15N[Forward Claude]
|
||||
GA15Y --> GA16[Release account slot and dec account wait]
|
||||
GA15N --> GA16
|
||||
GA16 --> GA17{UpstreamFailoverError}
|
||||
GA17 -->|yes| GA18[mark failedAccountIDs and map error if exceed]
|
||||
GA18 -->|loop| GA11
|
||||
GA17 -->|no| GA19[success async RecordUsage and return]
|
||||
GA19 --> GA20[defer release user slot and dec wait count]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% FLOW B: OpenAI
|
||||
%% =========================
|
||||
subgraph FLOW_B["openai v1 responses"]
|
||||
OA0[Auth middleware] --> OA1[Read body]
|
||||
OA1 -->|empty| OA1E[400 invalid_request_error]
|
||||
OA1 --> OA2[json Unmarshal body]
|
||||
OA2 -->|parse error| OA2E[400 invalid_request_error]
|
||||
OA2 --> OA3{model present}
|
||||
OA3 -->|no| OA3E[400 invalid_request_error]
|
||||
OA3 --> OA4{User Agent Codex CLI}
|
||||
OA4 -->|no| OA4N[set default instructions]
|
||||
OA4 -->|yes| OA4Y[no change]
|
||||
OA4N --> OA5
|
||||
OA4Y --> OA5
|
||||
OA5[streamStarted false] --> OA6[IncrementWaitCount user]
|
||||
OA6 -->|queue full| OA6E[429 rate_limit_error]
|
||||
OA6 --> OA7[AcquireUserSlotWithWait]
|
||||
OA7 -->|timeout or fail| OA7E[429 rate_limit_error]
|
||||
OA7 --> OA8[BillingEligibility check post wait]
|
||||
OA8 -->|fail| OA8E[403 billing_error]
|
||||
OA8 --> OA9[sessionHash sha256 session_id]
|
||||
OA9 --> OA10[SelectAccountWithLoadAwareness]
|
||||
OA10 -->|err and no failed| OA10E1[503 no available accounts]
|
||||
OA10 -->|err and failed| OA10E2[map failover error]
|
||||
OA10 --> OA11[Acquire account slot or wait]
|
||||
OA11 -->|wait queue full| OA11E1[429 rate_limit_error]
|
||||
OA11 -->|wait timeout| OA11E2[429 concurrency limit]
|
||||
OA11 --> OA12[BindStickySession openai hash if waited]
|
||||
OA12 --> OA13[Forward OpenAI upstream]
|
||||
OA13 --> OA14[Release account slot and dec account wait]
|
||||
OA14 --> OA15{UpstreamFailoverError}
|
||||
OA15 -->|yes| OA16[mark failedAccountIDs and map error if exceed]
|
||||
OA16 -->|loop| OA10
|
||||
OA15 -->|no| OA17[success async RecordUsage and return]
|
||||
OA17 --> OA18[defer release user slot and dec wait count]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% FLOW C: Gemini Native
|
||||
%% =========================
|
||||
subgraph FLOW_C["v1beta models model action Gemini Native"]
|
||||
GM0[Auth middleware] --> GM1[Validate platform]
|
||||
GM1 -->|invalid| GM1E[400 googleError]
|
||||
GM1 --> GM2[Parse path modelName action]
|
||||
GM2 -->|invalid| GM2E[400 googleError]
|
||||
GM2 --> GM3{action supported}
|
||||
GM3 -->|no| GM3E[404 googleError]
|
||||
GM3 --> GM4[Read body]
|
||||
GM4 -->|empty| GM4E[400 googleError]
|
||||
GM4 --> GM5[streamStarted false]
|
||||
GM5 --> GM6[IncrementWaitCount user]
|
||||
GM6 -->|queue full| GM6E[429 googleError]
|
||||
GM6 --> GM7[AcquireUserSlotWithWait]
|
||||
GM7 -->|timeout or fail| GM7E[429 googleError]
|
||||
GM7 --> GM8[BillingEligibility check post wait]
|
||||
GM8 -->|fail| GM8E[403 googleError]
|
||||
GM8 --> GM9[Generate sessionHash]
|
||||
GM9 --> GM10[sessionKey gemini hash]
|
||||
GM10 --> GM11[SelectAccountWithLoadAwareness]
|
||||
GM11 -->|err and no failed| GM11E1[503 googleError]
|
||||
GM11 -->|err and failed| GM11E2[mapGeminiUpstreamError]
|
||||
GM11 --> GM12[Acquire account slot or wait]
|
||||
GM12 -->|wait queue full| GM12E1[429 googleError]
|
||||
GM12 -->|wait timeout| GM12E2[429 googleError]
|
||||
GM12 --> GM13[BindStickySession if waited]
|
||||
GM13 --> GM14{account platform antigravity}
|
||||
GM14 -->|yes| GM14Y[ForwardGemini antigravity]
|
||||
GM14 -->|no| GM14N[ForwardNative]
|
||||
GM14Y --> GM15[Release account slot and dec account wait]
|
||||
GM14N --> GM15
|
||||
GM15 --> GM16{UpstreamFailoverError}
|
||||
GM16 -->|yes| GM17[mark failedAccountIDs and map error if exceed]
|
||||
GM17 -->|loop| GM11
|
||||
GM16 -->|no| GM18[success async RecordUsage and return]
|
||||
GM18 --> GM19[defer release user slot and dec wait count]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% FLOW D: CountTokens
|
||||
%% =========================
|
||||
subgraph FLOW_D["v1 messages count tokens"]
|
||||
GT0[Auth middleware] --> GT1[Read body]
|
||||
GT1 -->|empty| GT1E[400 invalid_request_error]
|
||||
GT1 --> GT2[ParseGatewayRequest]
|
||||
GT2 -->|parse error| GT2E[400 invalid_request_error]
|
||||
GT2 --> GT3{model present}
|
||||
GT3 -->|no| GT3E[400 invalid_request_error]
|
||||
GT3 --> GT4[BillingEligibility check]
|
||||
GT4 -->|fail| GT4E[403 billing_error]
|
||||
GT4 --> GT5[ForwardCountTokens]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% FLOW E: Gemini Models List Get
|
||||
%% =========================
|
||||
subgraph FLOW_E["v1beta models list or get"]
|
||||
GL0[Auth middleware] --> GL1[Validate platform]
|
||||
GL1 -->|invalid| GL1E[400 googleError]
|
||||
GL1 --> GL2{force platform antigravity}
|
||||
GL2 -->|yes| GL2Y[return static fallback models]
|
||||
GL2 -->|no| GL3[SelectAccountForAIStudioEndpoints]
|
||||
GL3 -->|no gemini and has antigravity| GL3Y[return fallback models]
|
||||
GL3 -->|no accounts| GL3E[503 googleError]
|
||||
GL3 --> GL4[ForwardAIStudioGET]
|
||||
GL4 -->|error| GL4E[502 googleError]
|
||||
GL4 --> GL5[Passthrough response or fallback]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% SHARED: Account Selection
|
||||
%% =========================
|
||||
subgraph SELECT["SelectAccountWithLoadAwareness detail"]
|
||||
S0[Start] --> S1{concurrencyService nil OR load batch disabled}
|
||||
S1 -->|yes| S2[SelectAccountForModelWithExclusions legacy]
|
||||
S2 --> S3[tryAcquireAccountSlot]
|
||||
S3 -->|acquired| S3Y[SelectionResult Acquired true ReleaseFunc]
|
||||
S3 -->|not acquired| S3N[WaitPlan FallbackTimeout MaxWaiting]
|
||||
S1 -->|no| S4[Resolve platform]
|
||||
S4 --> S5[List schedulable accounts]
|
||||
S5 --> S6[Layer1 Sticky session]
|
||||
S6 -->|hit and valid| S6A[tryAcquireAccountSlot]
|
||||
S6A -->|acquired| S6AY[SelectionResult Acquired true]
|
||||
S6A -->|not acquired and waitingCount < StickyMax| S6AN[WaitPlan StickyTimeout Max]
|
||||
S6 --> S7[Layer2 Load aware]
|
||||
S7 --> S7A[Load batch concurrency plus wait to loadRate]
|
||||
S7A --> S7B[Sort priority load LRU OAuth prefer for Gemini]
|
||||
S7B --> S7C[tryAcquireAccountSlot in order]
|
||||
S7C -->|first success| S7CY[SelectionResult Acquired true]
|
||||
S7C -->|none| S8[Layer3 Fallback wait]
|
||||
S8 --> S8A[Sort priority LRU]
|
||||
S8A --> S8B[WaitPlan FallbackTimeout Max]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% SHARED: Wait Acquire
|
||||
%% =========================
|
||||
subgraph WAIT["AcquireXSlotWithWait detail"]
|
||||
W0[Try AcquireXSlot immediately] -->|acquired| W1[return ReleaseFunc]
|
||||
W0 -->|not acquired| W2[Wait loop with timeout]
|
||||
W2 --> W3[Backoff 100ms x1.5 jitter max2s]
|
||||
W2 --> W4[If streaming and ping format send SSE ping]
|
||||
W2 --> W5[Retry AcquireXSlot on timer]
|
||||
W5 -->|acquired| W1
|
||||
W2 -->|timeout| W6[ConcurrencyError IsTimeout true]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% SHARED: Account Wait Queue
|
||||
%% =========================
|
||||
subgraph AQ["Account Wait Queue Redis Lua"]
|
||||
Q1[IncrementAccountWaitCount] --> Q2{current >= max}
|
||||
Q2 -->|yes| Q2Y[return false]
|
||||
Q2 -->|no| Q3[INCR and if first set TTL]
|
||||
Q3 --> Q4[return true]
|
||||
Q5[DecrementAccountWaitCount] --> Q6[if current > 0 then DECR]
|
||||
end
|
||||
|
||||
%% =========================
|
||||
%% SHARED: Background cleanup
|
||||
%% =========================
|
||||
subgraph CLEANUP["Slot Cleanup Worker"]
|
||||
C0[StartSlotCleanupWorker interval] --> C1[List schedulable accounts]
|
||||
C1 --> C2[CleanupExpiredAccountSlots per account]
|
||||
C2 --> C3[Repeat every interval]
|
||||
end
|
||||
```
|
||||
1169
deploy/install.sh
Normal file
1169
deploy/install.sh
Normal file
File diff suppressed because it is too large
Load Diff
33
deploy/sub2api.service
Normal file
33
deploy/sub2api.service
Normal file
@@ -0,0 +1,33 @@
|
||||
[Unit]
|
||||
Description=Sub2API - AI API Gateway Platform
|
||||
Documentation=https://github.com/Wei-Shaw/sub2api
|
||||
After=network.target postgresql.service redis.service
|
||||
Wants=postgresql.service redis.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=sub2api
|
||||
Group=sub2api
|
||||
WorkingDirectory=/opt/sub2api
|
||||
ExecStart=/opt/sub2api/sub2api
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=sub2api
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
PrivateTmp=true
|
||||
ReadWritePaths=/opt/sub2api
|
||||
|
||||
# Environment - Server configuration
|
||||
# Modify these values to change listen address and port
|
||||
Environment=GIN_MODE=release
|
||||
Environment=SERVER_HOST=0.0.0.0
|
||||
Environment=SERVER_PORT=8080
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user