feat: 优化codex冷启动, 还有连接池数据库配置信息
This commit is contained in:
@@ -789,8 +789,8 @@ func setDefaults() {
|
|||||||
viper.SetDefault("redis.dial_timeout_seconds", 5)
|
viper.SetDefault("redis.dial_timeout_seconds", 5)
|
||||||
viper.SetDefault("redis.read_timeout_seconds", 3)
|
viper.SetDefault("redis.read_timeout_seconds", 3)
|
||||||
viper.SetDefault("redis.write_timeout_seconds", 3)
|
viper.SetDefault("redis.write_timeout_seconds", 3)
|
||||||
viper.SetDefault("redis.pool_size", 128)
|
viper.SetDefault("redis.pool_size", 1024)
|
||||||
viper.SetDefault("redis.min_idle_conns", 10)
|
viper.SetDefault("redis.min_idle_conns", 128)
|
||||||
viper.SetDefault("redis.enable_tls", false)
|
viper.SetDefault("redis.enable_tls", false)
|
||||||
|
|
||||||
// Ops (vNext)
|
// Ops (vNext)
|
||||||
@@ -888,7 +888,7 @@ func setDefaults() {
|
|||||||
// HTTP 上游连接池配置(针对 5000+ 并发用户优化)
|
// HTTP 上游连接池配置(针对 5000+ 并发用户优化)
|
||||||
viper.SetDefault("gateway.max_idle_conns", 240) // 最大空闲连接总数(HTTP/2 场景默认)
|
viper.SetDefault("gateway.max_idle_conns", 240) // 最大空闲连接总数(HTTP/2 场景默认)
|
||||||
viper.SetDefault("gateway.max_idle_conns_per_host", 120) // 每主机最大空闲连接(HTTP/2 场景默认)
|
viper.SetDefault("gateway.max_idle_conns_per_host", 120) // 每主机最大空闲连接(HTTP/2 场景默认)
|
||||||
viper.SetDefault("gateway.max_conns_per_host", 240) // 每主机最大连接数(含活跃,HTTP/2 场景默认)
|
viper.SetDefault("gateway.max_conns_per_host", 4096) // 每主机最大连接数(含活跃;流式/HTTP1.1 可调大)
|
||||||
viper.SetDefault("gateway.idle_conn_timeout_seconds", 90) // 空闲连接超时(秒)
|
viper.SetDefault("gateway.idle_conn_timeout_seconds", 90) // 空闲连接超时(秒)
|
||||||
viper.SetDefault("gateway.max_upstream_clients", 5000)
|
viper.SetDefault("gateway.max_upstream_clients", 5000)
|
||||||
viper.SetDefault("gateway.client_idle_ttl_seconds", 900)
|
viper.SetDefault("gateway.client_idle_ttl_seconds", 900)
|
||||||
|
|||||||
@@ -9,12 +9,29 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
opencodeCodexHeaderURL = "https://raw.githubusercontent.com/anomalyco/opencode/dev/packages/opencode/src/session/prompt/codex_header.txt"
|
opencodeCodexHeaderURL = "https://raw.githubusercontent.com/anomalyco/opencode/dev/packages/opencode/src/session/prompt/codex_header.txt"
|
||||||
codexCacheTTL = 15 * time.Minute
|
codexCacheTTL = 15 * time.Minute
|
||||||
|
|
||||||
|
// 避免冷启动首请求被外网/ DNS / GitHub 卡死。
|
||||||
|
// http.DefaultClient 默认无超时,网络异常时可能阻塞很久。
|
||||||
|
opencodeFetchTimeout = 3 * time.Second
|
||||||
|
// 本地缓存为空时,最小回源间隔(防止并发下反复打 GitHub)。
|
||||||
|
opencodeEmptyCacheRefreshInterval = 1 * time.Minute
|
||||||
|
// 防抖:防止短时间内重复触发异步回源。
|
||||||
|
opencodeFetchDebounce = 3 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
var opencodeFetchHTTPClient = &http.Client{Timeout: opencodeFetchTimeout}
|
||||||
|
|
||||||
|
var (
|
||||||
|
opencodeFetchMu sync.Mutex
|
||||||
|
opencodeFetchInFlight bool
|
||||||
|
opencodeFetchLastStart time.Time
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed prompts/codex_cli_instructions.md
|
//go:embed prompts/codex_cli_instructions.md
|
||||||
@@ -230,28 +247,72 @@ func getOpenCodeCachedPrompt(url, cacheFileName, metaFileName string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var meta opencodeCacheMetadata
|
var meta opencodeCacheMetadata
|
||||||
if loadJSON(metaFile, &meta) && meta.LastChecked > 0 && cachedContent != "" {
|
_ = loadJSON(metaFile, &meta)
|
||||||
if time.Since(time.UnixMilli(meta.LastChecked)) < codexCacheTTL {
|
if meta.LastChecked > 0 {
|
||||||
|
lastCheckedAt := time.UnixMilli(meta.LastChecked)
|
||||||
|
if cachedContent != "" {
|
||||||
|
if time.Since(lastCheckedAt) < codexCacheTTL {
|
||||||
return cachedContent
|
return cachedContent
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 没有任何缓存内容时,回源失败也不应影响请求链路;这里做节流,避免并发下反复回源。
|
||||||
|
if time.Since(lastCheckedAt) < opencodeEmptyCacheRefreshInterval {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content, etag, status, err := fetchWithETag(url, meta.ETag)
|
// 不在请求链路内同步拉取(GitHub/DNS/网络异常会导致冷启动首请求卡 1 分钟+)。
|
||||||
if err == nil && status == http.StatusNotModified && cachedContent != "" {
|
// 直接返回当前缓存(可为空),并异步刷新缓存。
|
||||||
|
scheduleOpencodeCacheRefresh(url, cacheFile, metaFile, meta.ETag)
|
||||||
return cachedContent
|
return cachedContent
|
||||||
}
|
}
|
||||||
if err == nil && status >= 200 && status < 300 && content != "" {
|
|
||||||
_ = writeFile(cacheFile, content)
|
func scheduleOpencodeCacheRefresh(url, cacheFile, metaFile, etag string) {
|
||||||
meta = opencodeCacheMetadata{
|
opencodeFetchMu.Lock()
|
||||||
ETag: etag,
|
if opencodeFetchInFlight {
|
||||||
LastFetch: time.Now().UTC().Format(time.RFC3339),
|
opencodeFetchMu.Unlock()
|
||||||
LastChecked: time.Now().UnixMilli(),
|
return
|
||||||
|
}
|
||||||
|
if !opencodeFetchLastStart.IsZero() && time.Since(opencodeFetchLastStart) < opencodeFetchDebounce {
|
||||||
|
opencodeFetchMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opencodeFetchInFlight = true
|
||||||
|
opencodeFetchLastStart = time.Now()
|
||||||
|
opencodeFetchMu.Unlock()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
opencodeFetchMu.Lock()
|
||||||
|
opencodeFetchInFlight = false
|
||||||
|
opencodeFetchMu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
content, newETag, status, err := fetchWithETag(url, etag)
|
||||||
|
|
||||||
|
var meta opencodeCacheMetadata
|
||||||
|
_ = loadJSON(metaFile, &meta)
|
||||||
|
meta.LastChecked = now.UnixMilli()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == nil && status == http.StatusNotModified:
|
||||||
|
// 304 表示无需更新缓存文件,只更新检查时间。
|
||||||
|
if newETag != "" {
|
||||||
|
meta.ETag = newETag
|
||||||
}
|
}
|
||||||
_ = writeJSON(metaFile, meta)
|
_ = writeJSON(metaFile, meta)
|
||||||
return content
|
case err == nil && status >= 200 && status < 300 && strings.TrimSpace(content) != "":
|
||||||
|
_ = writeFile(cacheFile, content)
|
||||||
|
meta.ETag = newETag
|
||||||
|
meta.LastFetch = now.UTC().Format(time.RFC3339)
|
||||||
|
_ = writeJSON(metaFile, meta)
|
||||||
|
default:
|
||||||
|
// 拉取失败也记录检查时间,避免高并发下持续回源。
|
||||||
|
_ = writeJSON(metaFile, meta)
|
||||||
}
|
}
|
||||||
|
}()
|
||||||
return cachedContent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOpenCodeCodexHeader() string {
|
func getOpenCodeCodexHeader() string {
|
||||||
@@ -598,7 +659,7 @@ func fetchWithETag(url, etag string) (string, string, int, error) {
|
|||||||
if etag != "" {
|
if etag != "" {
|
||||||
req.Header.Set("If-None-Match", etag)
|
req.Header.Set("If-None-Match", etag)
|
||||||
}
|
}
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := opencodeFetchHTTPClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", 0, err
|
return "", "", 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,59 @@ POSTGRES_USER=sub2api
|
|||||||
POSTGRES_PASSWORD=change_this_secure_password
|
POSTGRES_PASSWORD=change_this_secure_password
|
||||||
POSTGRES_DB=sub2api
|
POSTGRES_DB=sub2api
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# PostgreSQL 服务端参数(可选;主要用于 deploy/docker-compose-aicodex.yml)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# POSTGRES_MAX_CONNECTIONS:PostgreSQL 服务端允许的最大连接数。
|
||||||
|
# 必须 >=(所有 Sub2API 实例的 DATABASE_MAX_OPEN_CONNS 之和)+ 预留余量(例如 20%)。
|
||||||
|
POSTGRES_MAX_CONNECTIONS=1024
|
||||||
|
# POSTGRES_SHARED_BUFFERS:PostgreSQL 用于缓存数据页的共享内存。
|
||||||
|
# 常见建议:物理内存的 10%~25%(容器内存受限时请按实际限制调整)。
|
||||||
|
POSTGRES_SHARED_BUFFERS=256MB
|
||||||
|
# POSTGRES_EFFECTIVE_CACHE_SIZE:查询规划器“假设可用的 OS 缓存大小”(不等于实际分配)。
|
||||||
|
# 常见建议:物理内存的 50%~75%。
|
||||||
|
POSTGRES_EFFECTIVE_CACHE_SIZE=768MB
|
||||||
|
# POSTGRES_MAINTENANCE_WORK_MEM:维护操作内存(VACUUM/CREATE INDEX 等)。
|
||||||
|
# 值越大维护越快,但会占用更多内存。
|
||||||
|
POSTGRES_MAINTENANCE_WORK_MEM=64MB
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# PostgreSQL 连接池参数(可选,默认与程序内置一致)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 说明:
|
||||||
|
# - 这些参数控制 Sub2API 进程到 PostgreSQL 的连接池大小(不是 PostgreSQL 自身的 max_connections)。
|
||||||
|
# - 多实例/多副本部署时,总连接上限约等于:实例数 * DATABASE_MAX_OPEN_CONNS。
|
||||||
|
# - 连接池过大可能导致:数据库连接耗尽、内存占用上升、上下文切换增多,反而变慢。
|
||||||
|
# - 建议结合 PostgreSQL 的 max_connections 与机器规格逐步调优:
|
||||||
|
# 通常把应用总连接上限控制在 max_connections 的 50%~80% 更稳妥。
|
||||||
|
#
|
||||||
|
# DATABASE_MAX_OPEN_CONNS:最大打开连接数(活跃+空闲),达到后新请求会等待可用连接。
|
||||||
|
# 典型范围:50~500(取决于 DB 规格、实例数、SQL 复杂度)。
|
||||||
|
DATABASE_MAX_OPEN_CONNS=50
|
||||||
|
# DATABASE_MAX_IDLE_CONNS:最大空闲连接数(热连接),建议 <= MAX_OPEN。
|
||||||
|
# 太小会频繁建连增加延迟;太大会长期占用数据库资源。
|
||||||
|
DATABASE_MAX_IDLE_CONNS=10
|
||||||
|
# DATABASE_CONN_MAX_LIFETIME_MINUTES:单个连接最大存活时间。
|
||||||
|
# 用于避免连接长期不重建导致的中间件/LB/NAT 异常或服务端重启后的“僵尸连接”。
|
||||||
|
# 设置为 0 表示不限制(一般不建议生产环境)。
|
||||||
|
DATABASE_CONN_MAX_LIFETIME_MINUTES=30
|
||||||
|
# DATABASE_CONN_MAX_IDLE_TIME_MINUTES:空闲连接最大存活时间。
|
||||||
|
# 超过该时间的空闲连接会被回收,防止长时间闲置占用连接数。
|
||||||
|
# 设置为 0 表示不限制(一般不建议生产环境)。
|
||||||
|
DATABASE_CONN_MAX_IDLE_TIME_MINUTES=5
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Leave empty for no password (default for local development)
|
# Leave empty for no password (default for local development)
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
REDIS_DB=0
|
REDIS_DB=0
|
||||||
|
# Redis 服务端最大客户端连接数(可选;主要用于 deploy/docker-compose-aicodex.yml)
|
||||||
|
REDIS_MAXCLIENTS=50000
|
||||||
|
# Redis 连接池大小(默认 1024)
|
||||||
|
REDIS_POOL_SIZE=1024
|
||||||
|
# Redis 最小空闲连接数(默认 10)
|
||||||
|
REDIS_MIN_IDLE_CONNS=10
|
||||||
REDIS_ENABLE_TLS=false
|
REDIS_ENABLE_TLS=false
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
@@ -119,6 +166,8 @@ RATE_LIMIT_OVERLOAD_COOLDOWN_MINUTES=10
|
|||||||
# Gateway Scheduling (Optional)
|
# Gateway Scheduling (Optional)
|
||||||
# 调度缓存与受控回源配置(缓存就绪且命中时不读 DB)
|
# 调度缓存与受控回源配置(缓存就绪且命中时不读 DB)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
# 上游连接池:每主机最大连接数(默认 1024;流式/HTTP1.1 可调大)
|
||||||
|
GATEWAY_MAX_CONNS_PER_HOST=4096
|
||||||
# 粘性会话最大排队长度
|
# 粘性会话最大排队长度
|
||||||
GATEWAY_SCHEDULING_STICKY_SESSION_MAX_WAITING=3
|
GATEWAY_SCHEDULING_STICKY_SESSION_MAX_WAITING=3
|
||||||
# 粘性会话等待超时(时间段,例如 45s)
|
# 粘性会话等待超时(时间段,例如 45s)
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ gateway:
|
|||||||
max_idle_conns_per_host: 120
|
max_idle_conns_per_host: 120
|
||||||
# Max connections per host
|
# Max connections per host
|
||||||
# 每个主机的最大连接数
|
# 每个主机的最大连接数
|
||||||
max_conns_per_host: 240
|
max_conns_per_host: 4096
|
||||||
# Idle connection timeout (seconds)
|
# Idle connection timeout (seconds)
|
||||||
# 空闲连接超时时间(秒)
|
# 空闲连接超时时间(秒)
|
||||||
idle_conn_timeout_seconds: 90
|
idle_conn_timeout_seconds: 90
|
||||||
@@ -384,6 +384,18 @@ database:
|
|||||||
# SSL mode: disable, require, verify-ca, verify-full
|
# SSL mode: disable, require, verify-ca, verify-full
|
||||||
# SSL 模式:disable(禁用), require(要求), verify-ca(验证CA), verify-full(完全验证)
|
# SSL 模式:disable(禁用), require(要求), verify-ca(验证CA), verify-full(完全验证)
|
||||||
sslmode: "disable"
|
sslmode: "disable"
|
||||||
|
# Max open connections
|
||||||
|
# 最大打开连接数
|
||||||
|
max_open_conns: 50
|
||||||
|
# Max idle connections
|
||||||
|
# 最大空闲连接数
|
||||||
|
max_idle_conns: 10
|
||||||
|
# Connection max lifetime (minutes)
|
||||||
|
# 连接最大存活时间(分钟)
|
||||||
|
conn_max_lifetime_minutes: 30
|
||||||
|
# Connection max idle time (minutes)
|
||||||
|
# 空闲连接最大存活时间(分钟)
|
||||||
|
conn_max_idle_time_minutes: 5
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
@@ -402,6 +414,12 @@ redis:
|
|||||||
# Database number (0-15)
|
# Database number (0-15)
|
||||||
# 数据库编号(0-15)
|
# 数据库编号(0-15)
|
||||||
db: 0
|
db: 0
|
||||||
|
# Connection pool size (max concurrent connections)
|
||||||
|
# 连接池大小(最大并发连接数)
|
||||||
|
pool_size: 1024
|
||||||
|
# Minimum number of idle connections
|
||||||
|
# 最小空闲连接数
|
||||||
|
min_idle_conns: 10
|
||||||
# Enable TLS/SSL connection
|
# Enable TLS/SSL connection
|
||||||
# 是否启用 TLS/SSL 连接
|
# 是否启用 TLS/SSL 连接
|
||||||
enable_tls: false
|
enable_tls: false
|
||||||
|
|||||||
226
deploy/docker-compose-aicodex.yml
Normal file
226
deploy/docker-compose-aicodex.yml
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Sub2API Docker Compose Host Configuration (Local Build)
|
||||||
|
# =============================================================================
|
||||||
|
# Quick Start:
|
||||||
|
# 1. Copy .env.example to .env and configure
|
||||||
|
# 2. docker-compose -f docker-compose-host.yml up -d --build
|
||||||
|
# 3. Check logs: docker-compose -f docker-compose-host.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: weishaw/sub2api:latest
|
||||||
|
image: yangjianbo/aicodex2api:latest
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: sub2api
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 800000
|
||||||
|
hard: 800000
|
||||||
|
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)
|
||||||
|
# =======================================================================
|
||||||
|
# Using host network: point to host/external DB by DATABASE_HOST/DATABASE_PORT
|
||||||
|
- DATABASE_HOST=${DATABASE_HOST:-127.0.0.1}
|
||||||
|
- DATABASE_PORT=${DATABASE_PORT:-5432}
|
||||||
|
- DATABASE_USER=${POSTGRES_USER:-sub2api}
|
||||||
|
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
|
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
||||||
|
- DATABASE_SSLMODE=disable
|
||||||
|
- DATABASE_MAX_OPEN_CONNS=${DATABASE_MAX_OPEN_CONNS:-50}
|
||||||
|
- DATABASE_MAX_IDLE_CONNS=${DATABASE_MAX_IDLE_CONNS:-10}
|
||||||
|
- DATABASE_CONN_MAX_LIFETIME_MINUTES=${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}
|
||||||
|
- DATABASE_CONN_MAX_IDLE_TIME_MINUTES=${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# Gateway Configuration
|
||||||
|
# =======================================================================
|
||||||
|
- GATEWAY_MAX_CONNS_PER_HOST=${GATEWAY_MAX_CONNS_PER_HOST:-1024}
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# Redis Configuration
|
||||||
|
# =======================================================================
|
||||||
|
# Using host network: point to host/external Redis by REDIS_HOST/REDIS_PORT
|
||||||
|
- REDIS_HOST=${REDIS_HOST:-127.0.0.1}
|
||||||
|
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||||
|
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||||
|
- REDIS_DB=${REDIS_DB:-0}
|
||||||
|
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||||
|
- REDIS_MIN_IDLE_CONNS=${REDIS_MIN_IDLE_CONNS:-10}
|
||||||
|
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# 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}
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# TOTP (2FA) Configuration
|
||||||
|
# =======================================================================
|
||||||
|
# IMPORTANT: Set a fixed encryption key for TOTP secrets. If left empty,
|
||||||
|
# a random key will be generated on each startup, causing all existing
|
||||||
|
# TOTP configurations to become invalid (users won't be able to login
|
||||||
|
# with 2FA).
|
||||||
|
# Generate a secure key: openssl rand -hex 32
|
||||||
|
- TOTP_ENCRYPTION_KEY=${TOTP_ENCRYPTION_KEY:-}
|
||||||
|
|
||||||
|
# =======================================================================
|
||||||
|
# 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
|
||||||
|
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
|
||||||
|
network_mode: host
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 800000
|
||||||
|
hard: 800000
|
||||||
|
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}
|
||||||
|
command:
|
||||||
|
- "postgres"
|
||||||
|
- "-c"
|
||||||
|
- "listen_addresses=127.0.0.1"
|
||||||
|
# 连接数上限:需要结合应用侧 DATABASE_MAX_OPEN_CONNS 调整。
|
||||||
|
# 注意:max_connections 过大可能导致内存占用与上下文切换开销显著上升。
|
||||||
|
- "-c"
|
||||||
|
- "max_connections=${POSTGRES_MAX_CONNECTIONS:-1024}"
|
||||||
|
# 典型内存参数(建议结合机器内存调优;不确定就保持默认或小步调大)。
|
||||||
|
- "-c"
|
||||||
|
- "shared_buffers=${POSTGRES_SHARED_BUFFERS:-1GB}"
|
||||||
|
- "-c"
|
||||||
|
- "effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4GB}"
|
||||||
|
- "-c"
|
||||||
|
- "maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-128MB}"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sub2api} -d ${POSTGRES_DB:-sub2api}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
# Note: bound to localhost only; not exposed to external network by default.
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Redis Cache
|
||||||
|
# ===========================================================================
|
||||||
|
redis:
|
||||||
|
image: redis:8-alpine
|
||||||
|
container_name: sub2api-redis
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: host
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 100000
|
||||||
|
hard: 100000
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
command: >
|
||||||
|
redis-server
|
||||||
|
--bind 127.0.0.1
|
||||||
|
--maxclients ${REDIS_MAXCLIENTS:-50000}
|
||||||
|
--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:-}
|
||||||
|
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
|
||||||
@@ -57,6 +57,10 @@ services:
|
|||||||
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
||||||
- DATABASE_SSLMODE=disable
|
- DATABASE_SSLMODE=disable
|
||||||
|
- DATABASE_MAX_OPEN_CONNS=${DATABASE_MAX_OPEN_CONNS:-50}
|
||||||
|
- DATABASE_MAX_IDLE_CONNS=${DATABASE_MAX_IDLE_CONNS:-10}
|
||||||
|
- DATABASE_CONN_MAX_LIFETIME_MINUTES=${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}
|
||||||
|
- DATABASE_CONN_MAX_IDLE_TIME_MINUTES=${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
@@ -65,6 +69,8 @@ services:
|
|||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||||
- REDIS_DB=${REDIS_DB:-0}
|
- REDIS_DB=${REDIS_DB:-0}
|
||||||
|
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||||
|
- REDIS_MIN_IDLE_CONNS=${REDIS_MIN_IDLE_CONNS:-10}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Admin Account (auto-created on first run)
|
# Admin Account (auto-created on first run)
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ services:
|
|||||||
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
||||||
- DATABASE_SSLMODE=disable
|
- DATABASE_SSLMODE=disable
|
||||||
|
- DATABASE_MAX_OPEN_CONNS=${DATABASE_MAX_OPEN_CONNS:-50}
|
||||||
|
- DATABASE_MAX_IDLE_CONNS=${DATABASE_MAX_IDLE_CONNS:-10}
|
||||||
|
- DATABASE_CONN_MAX_LIFETIME_MINUTES=${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}
|
||||||
|
- DATABASE_CONN_MAX_IDLE_TIME_MINUTES=${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
@@ -70,6 +74,8 @@ services:
|
|||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||||
- REDIS_DB=${REDIS_DB:-0}
|
- REDIS_DB=${REDIS_DB:-0}
|
||||||
|
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||||
|
- REDIS_MIN_IDLE_CONNS=${REDIS_MIN_IDLE_CONNS:-10}
|
||||||
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ services:
|
|||||||
- DATABASE_PASSWORD=${DATABASE_PASSWORD:?DATABASE_PASSWORD is required}
|
- DATABASE_PASSWORD=${DATABASE_PASSWORD:?DATABASE_PASSWORD is required}
|
||||||
- DATABASE_DBNAME=${DATABASE_DBNAME:-sub2api}
|
- DATABASE_DBNAME=${DATABASE_DBNAME:-sub2api}
|
||||||
- DATABASE_SSLMODE=${DATABASE_SSLMODE:-disable}
|
- DATABASE_SSLMODE=${DATABASE_SSLMODE:-disable}
|
||||||
|
- DATABASE_MAX_OPEN_CONNS=${DATABASE_MAX_OPEN_CONNS:-50}
|
||||||
|
- DATABASE_MAX_IDLE_CONNS=${DATABASE_MAX_IDLE_CONNS:-10}
|
||||||
|
- DATABASE_CONN_MAX_LIFETIME_MINUTES=${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}
|
||||||
|
- DATABASE_CONN_MAX_IDLE_TIME_MINUTES=${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Redis Configuration - Required
|
# Redis Configuration - Required
|
||||||
@@ -56,6 +60,8 @@ services:
|
|||||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||||
- REDIS_DB=${REDIS_DB:-0}
|
- REDIS_DB=${REDIS_DB:-0}
|
||||||
|
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||||
|
- REDIS_MIN_IDLE_CONNS=${REDIS_MIN_IDLE_CONNS:-10}
|
||||||
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ services:
|
|||||||
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
- DATABASE_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||||
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
- DATABASE_DBNAME=${POSTGRES_DB:-sub2api}
|
||||||
- DATABASE_SSLMODE=disable
|
- DATABASE_SSLMODE=disable
|
||||||
|
- DATABASE_MAX_OPEN_CONNS=${DATABASE_MAX_OPEN_CONNS:-50}
|
||||||
|
- DATABASE_MAX_IDLE_CONNS=${DATABASE_MAX_IDLE_CONNS:-10}
|
||||||
|
- DATABASE_CONN_MAX_LIFETIME_MINUTES=${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}
|
||||||
|
- DATABASE_CONN_MAX_IDLE_TIME_MINUTES=${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
# Redis Configuration
|
# Redis Configuration
|
||||||
@@ -62,6 +66,8 @@ services:
|
|||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
|
||||||
- REDIS_DB=${REDIS_DB:-0}
|
- REDIS_DB=${REDIS_DB:-0}
|
||||||
|
- REDIS_POOL_SIZE=${REDIS_POOL_SIZE:-1024}
|
||||||
|
- REDIS_MIN_IDLE_CONNS=${REDIS_MIN_IDLE_CONNS:-10}
|
||||||
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
|
||||||
|
|
||||||
# =======================================================================
|
# =======================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user