Merge remote-tracking branch 'upstream/main'
# Conflicts: # frontend/src/components/layout/AuthLayout.vue
This commit is contained in:
@@ -20,11 +20,61 @@ SERVER_PORT=8080
|
||||
# Server mode: release or debug
|
||||
SERVER_MODE=release
|
||||
|
||||
# Global max request body size in bytes (default: 100MB)
|
||||
# 全局最大请求体大小(字节,默认 100MB)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging Configuration
|
||||
# 日志配置
|
||||
# -----------------------------------------------------------------------------
|
||||
# 日志级别:debug/info/warn/error
|
||||
LOG_LEVEL=info
|
||||
# 日志格式:json/console
|
||||
LOG_FORMAT=json
|
||||
# 每条日志附带的 service 字段
|
||||
LOG_SERVICE_NAME=sub2api
|
||||
# 每条日志附带的 env 字段
|
||||
LOG_ENV=production
|
||||
# 是否输出调用方位置信息
|
||||
LOG_CALLER=true
|
||||
# 堆栈输出阈值:none/error/fatal
|
||||
LOG_STACKTRACE_LEVEL=error
|
||||
|
||||
# 输出开关(建议容器内保持双输出)
|
||||
# 是否输出到 stdout/stderr
|
||||
LOG_OUTPUT_TO_STDOUT=true
|
||||
# 是否输出到文件
|
||||
LOG_OUTPUT_TO_FILE=true
|
||||
# 日志文件路径(留空自动推导):
|
||||
# - 设置 DATA_DIR:${DATA_DIR}/logs/sub2api.log
|
||||
# - 未设置 DATA_DIR:/app/data/logs/sub2api.log
|
||||
LOG_OUTPUT_FILE_PATH=
|
||||
|
||||
# 滚动配置
|
||||
# 单文件最大体积(MB)
|
||||
LOG_ROTATION_MAX_SIZE_MB=100
|
||||
# 保留历史文件数量(0 表示不限制)
|
||||
LOG_ROTATION_MAX_BACKUPS=10
|
||||
# 历史日志保留天数(0 表示不限制)
|
||||
LOG_ROTATION_MAX_AGE_DAYS=7
|
||||
# 是否压缩历史日志
|
||||
LOG_ROTATION_COMPRESS=true
|
||||
# 滚动文件时间戳是否使用本地时间
|
||||
LOG_ROTATION_LOCAL_TIME=true
|
||||
|
||||
# 采样配置(高频重复日志降噪)
|
||||
LOG_SAMPLING_ENABLED=false
|
||||
# 每秒前 N 条日志不采样
|
||||
LOG_SAMPLING_INITIAL=100
|
||||
# 之后每 N 条保留 1 条
|
||||
LOG_SAMPLING_THEREAFTER=100
|
||||
|
||||
# Global max request body size in bytes (default: 256MB)
|
||||
# 全局最大请求体大小(字节,默认 256MB)
|
||||
# Applies to all requests, especially important for h2c first request memory protection
|
||||
# 适用于所有请求,对 h2c 第一请求的内存保护尤为重要
|
||||
SERVER_MAX_REQUEST_BODY_SIZE=104857600
|
||||
SERVER_MAX_REQUEST_BODY_SIZE=268435456
|
||||
|
||||
# Gateway max request body size in bytes (default: 256MB)
|
||||
# 网关请求体最大字节数(默认 256MB)
|
||||
GATEWAY_MAX_BODY_SIZE=268435456
|
||||
|
||||
# Enable HTTP/2 Cleartext (h2c) for client connections
|
||||
# 启用 HTTP/2 Cleartext (h2c) 客户端连接
|
||||
@@ -58,13 +108,67 @@ TZ=Asia/Shanghai
|
||||
POSTGRES_USER=sub2api
|
||||
POSTGRES_PASSWORD=change_this_secure_password
|
||||
POSTGRES_DB=sub2api
|
||||
# PostgreSQL 监听端口(同时用于 PG 服务端和应用连接,默认 5432)
|
||||
DATABASE_PORT=5432
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# PostgreSQL 服务端参数(可选)
|
||||
# -----------------------------------------------------------------------------
|
||||
# POSTGRES_MAX_CONNECTIONS:PostgreSQL 服务端允许的最大连接数。
|
||||
# 必须 >=(所有 Sub2API 实例的 DATABASE_MAX_OPEN_CONNS 之和)+ 预留余量(例如 20%)。
|
||||
POSTGRES_MAX_CONNECTIONS=1024
|
||||
# POSTGRES_SHARED_BUFFERS:PostgreSQL 用于缓存数据页的共享内存。
|
||||
# 常见建议:物理内存的 10%~25%(容器内存受限时请按实际限制调整)。
|
||||
# 8GB 内存容器参考:1GB。
|
||||
POSTGRES_SHARED_BUFFERS=1GB
|
||||
# POSTGRES_EFFECTIVE_CACHE_SIZE:查询规划器“假设可用的 OS 缓存大小”(不等于实际分配)。
|
||||
# 常见建议:物理内存的 50%~75%。
|
||||
# 8GB 内存容器参考:6GB。
|
||||
POSTGRES_EFFECTIVE_CACHE_SIZE=4GB
|
||||
# POSTGRES_MAINTENANCE_WORK_MEM:维护操作内存(VACUUM/CREATE INDEX 等)。
|
||||
# 值越大维护越快,但会占用更多内存。
|
||||
# 8GB 内存容器参考:128MB。
|
||||
POSTGRES_MAINTENANCE_WORK_MEM=128MB
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 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=256
|
||||
# DATABASE_MAX_IDLE_CONNS:最大空闲连接数(热连接),建议 <= MAX_OPEN。
|
||||
# 太小会频繁建连增加延迟;太大会长期占用数据库资源。
|
||||
DATABASE_MAX_IDLE_CONNS=128
|
||||
# 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 监听端口(同时用于应用连接和 Redis 服务端,默认 6379)
|
||||
REDIS_PORT=6379
|
||||
# Leave empty for no password (default for local development)
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
# Redis 服务端最大客户端连接数(可选)
|
||||
REDIS_MAXCLIENTS=50000
|
||||
# Redis 连接池大小(默认 1024)
|
||||
REDIS_POOL_SIZE=4096
|
||||
# Redis 最小空闲连接数(默认 10)
|
||||
REDIS_MIN_IDLE_CONNS=256
|
||||
REDIS_ENABLE_TLS=false
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -86,6 +190,11 @@ ADMIN_PASSWORD=
|
||||
# Generate a secure secret: openssl rand -hex 32
|
||||
JWT_SECRET=
|
||||
JWT_EXPIRE_HOUR=24
|
||||
# Access Token 有效期(分钟)
|
||||
# 优先级说明:
|
||||
# - >0: 按分钟生效(优先于 JWT_EXPIRE_HOUR)
|
||||
# - =0: 回退使用 JWT_EXPIRE_HOUR
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=0
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# TOTP (2FA) Configuration
|
||||
@@ -107,6 +216,19 @@ TOTP_ENCRYPTION_KEY=
|
||||
# Leave unset to use default ./config.yaml
|
||||
#CONFIG_FILE=./config.yaml
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Built-in OAuth Client Secrets (Optional)
|
||||
# -----------------------------------------------------------------------------
|
||||
# SECURITY NOTE:
|
||||
# - 本项目不会在代码仓库中内置第三方 OAuth client_secret。
|
||||
# - 如需使用“内置客户端”(而不是自建 OAuth Client),请在运行环境通过 env 注入。
|
||||
#
|
||||
# Gemini CLI built-in OAuth client_secret(用于 Gemini code_assist/google_one 内置登录流)
|
||||
# GEMINI_CLI_OAUTH_CLIENT_SECRET=
|
||||
#
|
||||
# Antigravity OAuth client_secret(用于 Antigravity OAuth 登录流)
|
||||
# ANTIGRAVITY_OAUTH_CLIENT_SECRET=
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Rate Limiting (Optional)
|
||||
# 速率限制(可选)
|
||||
@@ -119,6 +241,19 @@ RATE_LIMIT_OVERLOAD_COOLDOWN_MINUTES=10
|
||||
# Gateway Scheduling (Optional)
|
||||
# 调度缓存与受控回源配置(缓存就绪且命中时不读 DB)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Force Codex CLI mode: treat all /openai/v1/responses requests as Codex CLI.
|
||||
# 强制按 Codex CLI 处理 /openai/v1/responses 请求(用于网关未透传/改写 User-Agent 的兜底)。
|
||||
#
|
||||
# 注意:开启后会影响所有客户端的行为(不仅限于 VS Code / Codex CLI),请谨慎开启。
|
||||
#
|
||||
# 默认:false
|
||||
GATEWAY_FORCE_CODEX_CLI=false
|
||||
# 上游连接池:每主机最大连接数(默认 1024;流式/HTTP1.1 场景可调大,如 2400/4096)
|
||||
GATEWAY_MAX_CONNS_PER_HOST=2048
|
||||
# 上游连接池:最大空闲连接总数(默认 2560;账号/代理隔离 + 高并发场景可调大)
|
||||
GATEWAY_MAX_IDLE_CONNS=8192
|
||||
# 上游连接池:每主机最大空闲连接(默认 120)
|
||||
GATEWAY_MAX_IDLE_CONNS_PER_HOST=4096
|
||||
# 粘性会话最大排队长度
|
||||
GATEWAY_SCHEDULING_STICKY_SESSION_MAX_WAITING=3
|
||||
# 粘性会话等待超时(时间段,例如 45s)
|
||||
|
||||
@@ -1,39 +1,6 @@
|
||||
# =============================================================================
|
||||
# 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 {
|
||||
# =========================================================================
|
||||
api.sub2api.com {
|
||||
# =========================================================================
|
||||
# 静态资源长期缓存(高优先级,放在最前面)
|
||||
# 带 hash 的文件可以永久缓存,浏览器和 CDN 都会缓存
|
||||
# =========================================================================
|
||||
@@ -93,10 +60,7 @@ example.com {
|
||||
write_buffer 16KB
|
||||
compression off
|
||||
}
|
||||
|
||||
# SSE/流式传输优化:禁用响应缓冲,立即刷新数据给客户端
|
||||
flush_interval -1
|
||||
|
||||
|
||||
# 故障转移
|
||||
fail_duration 30s
|
||||
max_fails 3
|
||||
@@ -111,10 +75,6 @@ example.com {
|
||||
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*
|
||||
@@ -124,53 +84,6 @@ example.com {
|
||||
}
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 速率限制 (需要 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
|
||||
}
|
||||
|
||||
# =========================================================================
|
||||
# 请求大小限制 (防止大文件攻击)
|
||||
# =========================================================================
|
||||
@@ -198,7 +111,3 @@ example.com {
|
||||
respond "{err.status_code} {err.status_text}"
|
||||
}
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# HTTP 重定向到 HTTPS (Caddy 默认自动处理,此处显式声明)
|
||||
# =============================================================================
|
||||
|
||||
78
deploy/DATAMANAGEMENTD_CN.md
Normal file
78
deploy/DATAMANAGEMENTD_CN.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# datamanagementd 部署说明(数据管理)
|
||||
|
||||
本文说明如何在宿主机部署 `datamanagementd`,并与主进程联动开启“数据管理”功能。
|
||||
|
||||
## 1. 关键约束
|
||||
|
||||
- 主进程固定探测路径:`/tmp/sub2api-datamanagement.sock`
|
||||
- 仅当该 Unix Socket 可连通且 `Health` 成功时,后台“数据管理”才会启用
|
||||
- `datamanagementd` 使用 SQLite 持久化元数据,不依赖主库
|
||||
|
||||
## 2. 宿主机构建与运行
|
||||
|
||||
```bash
|
||||
cd /opt/sub2api-src/datamanagement
|
||||
go build -o /opt/sub2api/datamanagementd ./cmd/datamanagementd
|
||||
|
||||
mkdir -p /var/lib/sub2api/datamanagement
|
||||
chown -R sub2api:sub2api /var/lib/sub2api/datamanagement
|
||||
```
|
||||
|
||||
手动启动示例:
|
||||
|
||||
```bash
|
||||
/opt/sub2api/datamanagementd \
|
||||
-socket-path /tmp/sub2api-datamanagement.sock \
|
||||
-sqlite-path /var/lib/sub2api/datamanagement/datamanagementd.db \
|
||||
-version 1.0.0
|
||||
```
|
||||
|
||||
## 3. systemd 托管(推荐)
|
||||
|
||||
仓库已提供示例服务文件:`deploy/sub2api-datamanagementd.service`
|
||||
|
||||
```bash
|
||||
sudo cp deploy/sub2api-datamanagementd.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now sub2api-datamanagementd
|
||||
sudo systemctl status sub2api-datamanagementd
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
```bash
|
||||
sudo journalctl -u sub2api-datamanagementd -f
|
||||
```
|
||||
|
||||
也可以使用一键安装脚本(自动安装二进制 + 注册 systemd):
|
||||
|
||||
```bash
|
||||
# 方式一:使用现成二进制
|
||||
sudo ./deploy/install-datamanagementd.sh --binary /path/to/datamanagementd
|
||||
|
||||
# 方式二:从源码构建后安装
|
||||
sudo ./deploy/install-datamanagementd.sh --source /path/to/sub2api
|
||||
```
|
||||
|
||||
## 4. Docker 部署联动
|
||||
|
||||
若 `sub2api` 运行在 Docker 容器中,需要将宿主机 Socket 挂载到容器同路径:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
sub2api:
|
||||
volumes:
|
||||
- /tmp/sub2api-datamanagement.sock:/tmp/sub2api-datamanagement.sock
|
||||
```
|
||||
|
||||
建议在 `docker-compose.override.yml` 中维护该挂载,避免覆盖主 compose 文件。
|
||||
|
||||
## 5. 依赖检查
|
||||
|
||||
`datamanagementd` 执行备份时依赖以下工具:
|
||||
|
||||
- `pg_dump`
|
||||
- `redis-cli`
|
||||
- `docker`(仅 `source_mode=docker_exec` 时)
|
||||
|
||||
缺失依赖会导致对应任务失败,并在任务详情中体现错误信息。
|
||||
111
deploy/Dockerfile
Normal file
111
deploy/Dockerfile
Normal file
@@ -0,0 +1,111 @@
|
||||
# =============================================================================
|
||||
# Sub2API Multi-Stage Dockerfile
|
||||
# =============================================================================
|
||||
# Stage 1: Build frontend
|
||||
# Stage 2: Build Go backend with embedded frontend
|
||||
# Stage 3: Final minimal image
|
||||
# =============================================================================
|
||||
|
||||
ARG NODE_IMAGE=node:24-alpine
|
||||
ARG GOLANG_IMAGE=golang:1.26.1-alpine
|
||||
ARG ALPINE_IMAGE=alpine:3.20
|
||||
ARG GOPROXY=https://goproxy.cn,direct
|
||||
ARG GOSUMDB=sum.golang.google.cn
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 1: Frontend Builder
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM ${NODE_IMAGE} AS frontend-builder
|
||||
|
||||
WORKDIR /app/frontend
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
# Install dependencies first (better caching)
|
||||
COPY frontend/package.json frontend/pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy frontend source and build
|
||||
COPY frontend/ ./
|
||||
RUN pnpm run build
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 2: Backend Builder
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM ${GOLANG_IMAGE} AS backend-builder
|
||||
|
||||
# Build arguments for version info (set by CI)
|
||||
ARG VERSION=docker
|
||||
ARG COMMIT=docker
|
||||
ARG DATE
|
||||
ARG GOPROXY
|
||||
ARG GOSUMDB
|
||||
|
||||
ENV GOPROXY=${GOPROXY}
|
||||
ENV GOSUMDB=${GOSUMDB}
|
||||
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache git ca-certificates tzdata
|
||||
|
||||
WORKDIR /app/backend
|
||||
|
||||
# Copy go mod files first (better caching)
|
||||
COPY backend/go.mod backend/go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy backend source first
|
||||
COPY backend/ ./
|
||||
|
||||
# Copy frontend dist from previous stage (must be after backend copy to avoid being overwritten)
|
||||
COPY --from=frontend-builder /app/backend/internal/web/dist ./internal/web/dist
|
||||
|
||||
# Build the binary (BuildType=release for CI builds, embed frontend)
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build \
|
||||
-tags embed \
|
||||
-ldflags="-s -w -X main.Commit=${COMMIT} -X main.Date=${DATE:-$(date -u +%Y-%m-%dT%H:%M:%SZ)} -X main.BuildType=release" \
|
||||
-o /app/sub2api \
|
||||
./cmd/server
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Stage 3: Final Runtime Image
|
||||
# -----------------------------------------------------------------------------
|
||||
FROM ${ALPINE_IMAGE}
|
||||
|
||||
# Labels
|
||||
LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
|
||||
LABEL description="Sub2API - AI API Gateway Platform"
|
||||
LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
curl \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1000 sub2api && \
|
||||
adduser -u 1000 -G sub2api -s /bin/sh -D sub2api
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy binary from builder
|
||||
COPY --from=backend-builder /app/sub2api /app/sub2api
|
||||
|
||||
# Create data directory
|
||||
RUN mkdir -p /app/data && chown -R sub2api:sub2api /app
|
||||
|
||||
# Switch to non-root user
|
||||
USER sub2api
|
||||
|
||||
# Expose port (can be overridden by SERVER_PORT env var)
|
||||
EXPOSE 8080
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
|
||||
CMD curl -f http://localhost:${SERVER_PORT:-8080}/health || exit 1
|
||||
|
||||
# Run the application
|
||||
ENTRYPOINT ["/app/sub2api"]
|
||||
@@ -19,7 +19,10 @@ This directory contains files for deploying Sub2API on Linux servers.
|
||||
| `.env.example` | Docker environment variables template |
|
||||
| `DOCKER.md` | Docker Hub documentation |
|
||||
| `install.sh` | One-click binary installation script |
|
||||
| `install-datamanagementd.sh` | datamanagementd 一键安装脚本 |
|
||||
| `sub2api.service` | Systemd service unit file |
|
||||
| `sub2api-datamanagementd.service` | datamanagementd systemd service unit file |
|
||||
| `DATAMANAGEMENTD_CN.md` | datamanagementd 部署与联动说明(中文) |
|
||||
| `config.example.yaml` | Example configuration file |
|
||||
|
||||
---
|
||||
@@ -145,6 +148,14 @@ SELECT
|
||||
(SELECT COUNT(*) FROM user_allowed_groups) AS new_pair_count;
|
||||
```
|
||||
|
||||
### datamanagementd(数据管理)联动
|
||||
|
||||
如需启用管理后台“数据管理”功能,请额外部署宿主机 `datamanagementd`:
|
||||
|
||||
- 主进程固定探测 `/tmp/sub2api-datamanagement.sock`
|
||||
- Docker 场景下需把宿主机 Socket 挂载到容器内同路径
|
||||
- 详细步骤见:`deploy/DATAMANAGEMENTD_CN.md`
|
||||
|
||||
### Commands
|
||||
|
||||
For **local directory version** (docker-compose.local.yml):
|
||||
@@ -303,6 +314,10 @@ Requires your own OAuth client credentials.
|
||||
```bash
|
||||
GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com
|
||||
GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret
|
||||
|
||||
# 可选:如需使用 Gemini CLI 内置 OAuth Client(Code Assist / Google One)
|
||||
# 安全说明:本仓库不会内置该 client_secret,请在运行环境通过环境变量注入。
|
||||
# GEMINI_CLI_OAUTH_CLIENT_SECRET=GOCSPX-your-built-in-secret
|
||||
```
|
||||
|
||||
**Step 3: Create Account in Admin UI**
|
||||
@@ -430,6 +445,11 @@ If you need to use AI Studio OAuth for Gemini accounts, add the OAuth client cre
|
||||
Environment=GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-client-secret
|
||||
```
|
||||
|
||||
如需使用“内置 Gemini CLI OAuth Client”(Code Assist / Google One),还需要注入:
|
||||
```ini
|
||||
Environment=GEMINI_CLI_OAUTH_CLIENT_SECRET=GOCSPX-your-built-in-secret
|
||||
```
|
||||
|
||||
3. Reload and restart:
|
||||
```bash
|
||||
sudo systemctl daemon-reload
|
||||
@@ -566,7 +586,7 @@ gateway:
|
||||
name: "Profile 2"
|
||||
cipher_suites: [4866, 4867, 4865, 49199, 49195, 49200, 49196]
|
||||
curves: [29, 23, 24]
|
||||
point_formats: [0]
|
||||
point_formats: 0
|
||||
|
||||
# Another custom profile
|
||||
profile_3:
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
# 本地构建镜像的快速脚本,避免在命令行反复输入构建参数。
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
docker build -t sub2api:latest \
|
||||
--build-arg GOPROXY=https://goproxy.cn,direct \
|
||||
--build-arg GOSUMDB=sum.golang.google.cn \
|
||||
-f Dockerfile \
|
||||
.
|
||||
-f "${REPO_ROOT}/Dockerfile" \
|
||||
"${REPO_ROOT}"
|
||||
|
||||
@@ -20,14 +20,18 @@ server:
|
||||
# Mode: "debug" for development, "release" for production
|
||||
# 运行模式:"debug" 用于开发,"release" 用于生产环境
|
||||
mode: "release"
|
||||
# Frontend base URL used to generate external links in emails (e.g. password reset)
|
||||
# 用于生成邮件中的外部链接(例如:重置密码链接)的前端基础地址
|
||||
# Example: "https://example.com"
|
||||
frontend_url: ""
|
||||
# Trusted proxies for X-Forwarded-For parsing (CIDR/IP). Empty disables trusted proxies.
|
||||
# 信任的代理地址(CIDR/IP 格式),用于解析 X-Forwarded-For 头。留空则禁用代理信任。
|
||||
trusted_proxies: []
|
||||
# Global max request body size in bytes (default: 100MB)
|
||||
# 全局最大请求体大小(字节,默认 100MB)
|
||||
# Global max request body size in bytes (default: 256MB)
|
||||
# 全局最大请求体大小(字节,默认 256MB)
|
||||
# Applies to all requests, especially important for h2c first request memory protection
|
||||
# 适用于所有请求,对 h2c 第一请求的内存保护尤为重要
|
||||
max_request_body_size: 104857600
|
||||
max_request_body_size: 268435456
|
||||
# HTTP/2 Cleartext (h2c) configuration
|
||||
# HTTP/2 Cleartext (h2c) 配置
|
||||
h2c:
|
||||
@@ -108,9 +112,9 @@ security:
|
||||
# 白名单禁用时是否允许 http:// URL(默认: false,要求 https)
|
||||
allow_insecure_http: true
|
||||
response_headers:
|
||||
# Enable configurable response header filtering (disable to use default allowlist)
|
||||
# 启用可配置的响应头过滤(禁用则使用默认白名单)
|
||||
enabled: false
|
||||
# Enable configurable response header filtering (default: true)
|
||||
# 启用可配置的响应头过滤(默认启用,过滤上游敏感响应头)
|
||||
enabled: true
|
||||
# Extra allowed response headers from upstream
|
||||
# 额外允许的上游响应头
|
||||
additional_allowed: []
|
||||
@@ -130,6 +134,12 @@ security:
|
||||
# Allow skipping TLS verification for proxy probe (debug only)
|
||||
# 允许代理探测时跳过 TLS 证书验证(仅用于调试)
|
||||
insecure_skip_verify: false
|
||||
proxy_fallback:
|
||||
# Allow auxiliary services (update check, pricing data) to fallback to direct
|
||||
# connection when proxy initialization fails. Does NOT affect AI gateway connections.
|
||||
# 辅助服务(更新检查、定价数据拉取)代理初始化失败时是否允许回退直连。
|
||||
# 不影响 AI 账号网关连接。默认 false:fail-fast 防止 IP 泄露。
|
||||
allow_direct_on_error: false
|
||||
|
||||
# =============================================================================
|
||||
# Gateway Configuration
|
||||
@@ -139,9 +149,45 @@ 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
|
||||
# Max request body size in bytes (default: 256MB)
|
||||
# 请求体最大字节数(默认 256MB)
|
||||
max_body_size: 268435456
|
||||
# Max bytes to read for non-stream upstream responses (default: 8MB)
|
||||
# 非流式上游响应体读取上限(默认 8MB)
|
||||
upstream_response_read_max_bytes: 8388608
|
||||
# Max bytes to read for proxy probe responses (default: 1MB)
|
||||
# 代理探测响应体读取上限(默认 1MB)
|
||||
proxy_probe_response_read_max_bytes: 1048576
|
||||
# Enable Gemini upstream response header debug logs (default: false)
|
||||
# 是否开启 Gemini 上游响应头调试日志(默认 false)
|
||||
gemini_debug_response_headers: false
|
||||
# Sora max request body size in bytes (0=use max_body_size)
|
||||
# Sora 请求体最大字节数(0=使用 max_body_size)
|
||||
sora_max_body_size: 268435456
|
||||
# Sora stream timeout (seconds, 0=disable)
|
||||
# Sora 流式请求总超时(秒,0=禁用)
|
||||
sora_stream_timeout_seconds: 900
|
||||
# Sora non-stream timeout (seconds, 0=disable)
|
||||
# Sora 非流式请求超时(秒,0=禁用)
|
||||
sora_request_timeout_seconds: 180
|
||||
# Sora stream enforcement mode: force/error
|
||||
# Sora stream 强制策略:force/error
|
||||
sora_stream_mode: "force"
|
||||
# Sora model filters
|
||||
# Sora 模型过滤配置
|
||||
sora_model_filters:
|
||||
# Hide prompt-enhance models by default
|
||||
# 默认隐藏 prompt-enhance 模型
|
||||
hide_prompt_enhance: true
|
||||
# Require API key for /sora/media proxy (default: false)
|
||||
# /sora/media 是否强制要求 API Key(默认 true)
|
||||
sora_media_require_api_key: true
|
||||
# Sora media temporary signing key (empty disables signed URL)
|
||||
# Sora 媒体临时签名密钥(为空则禁用签名)
|
||||
sora_media_signing_key: ""
|
||||
# Signed URL TTL seconds (<=0 disables)
|
||||
# 临时签名 URL 有效期(秒,<=0 表示禁用)
|
||||
sora_media_signed_url_ttl_seconds: 900
|
||||
# Connection pool isolation strategy:
|
||||
# 连接池隔离策略:
|
||||
# - proxy: Isolate by proxy, same proxy shares connection pool (suitable for few proxies, many accounts)
|
||||
@@ -151,17 +197,103 @@ gateway:
|
||||
# - account_proxy: Isolate by account+proxy combination (default, finest granularity)
|
||||
# - account_proxy: 按账户+代理组合隔离(默认,最细粒度)
|
||||
connection_pool_isolation: "account_proxy"
|
||||
# Force Codex CLI mode: treat all /openai/v1/responses requests as Codex CLI.
|
||||
# 强制按 Codex CLI 处理 /openai/v1/responses 请求(用于网关未透传/改写 User-Agent 的兜底)。
|
||||
#
|
||||
# 注意:开启后会影响所有客户端的行为(不仅限于 VS Code / Codex CLI),请谨慎开启。
|
||||
force_codex_cli: false
|
||||
# OpenAI 透传模式是否放行客户端超时头(如 x-stainless-timeout)
|
||||
# 默认 false:过滤超时头,降低上游提前断流风险。
|
||||
openai_passthrough_allow_timeout_headers: false
|
||||
# OpenAI Responses WebSocket 配置(默认开启,可按需回滚到 HTTP)
|
||||
openai_ws:
|
||||
# 新版 WS mode 路由(默认关闭)。关闭时保持当前 legacy 实现行为。
|
||||
mode_router_v2_enabled: false
|
||||
# ingress 默认模式:off|ctx_pool|passthrough(仅 mode_router_v2_enabled=true 生效)
|
||||
# 兼容旧值:shared/dedicated 会按 ctx_pool 处理。
|
||||
ingress_mode_default: ctx_pool
|
||||
# 全局总开关,默认 true;关闭时所有请求保持原有 HTTP/SSE 路由
|
||||
enabled: true
|
||||
# 按账号类型细分开关
|
||||
oauth_enabled: true
|
||||
apikey_enabled: true
|
||||
# 全局强制 HTTP(紧急回滚开关)
|
||||
force_http: false
|
||||
# 允许在 WSv2 下按策略恢复 store=true(默认 false)
|
||||
allow_store_recovery: false
|
||||
# ingress 模式收到 previous_response_not_found 时,自动去掉 previous_response_id 重试一次(默认 true)
|
||||
ingress_previous_response_recovery_enabled: true
|
||||
# store=false 且无可复用会话连接时的策略:
|
||||
# strict=强制新建连接(隔离优先),adaptive=仅在高风险失败后强制新建,off=尽量复用(性能优先)
|
||||
store_disabled_conn_mode: strict
|
||||
# store=false 且无可复用会话连接时,是否强制新建连接(默认 true,优先会话隔离)
|
||||
# 兼容旧配置:仅在 store_disabled_conn_mode 未配置时生效
|
||||
store_disabled_force_new_conn: true
|
||||
# 是否启用 WSv2 generate=false 预热(默认 false)
|
||||
prewarm_generate_enabled: false
|
||||
# 协议 feature 开关,v2 优先于 v1
|
||||
responses_websockets: false
|
||||
responses_websockets_v2: true
|
||||
# 连接池参数(按账号池化复用)
|
||||
max_conns_per_account: 128
|
||||
min_idle_per_account: 4
|
||||
max_idle_per_account: 12
|
||||
# 是否按账号并发动态计算连接池上限:
|
||||
# effective_max_conns = min(max_conns_per_account, ceil(account.concurrency * factor))
|
||||
dynamic_max_conns_by_account_concurrency_enabled: true
|
||||
# 按账号类型分别设置系数(OAuth / API Key)
|
||||
oauth_max_conns_factor: 1.0
|
||||
apikey_max_conns_factor: 1.0
|
||||
dial_timeout_seconds: 10
|
||||
read_timeout_seconds: 900
|
||||
write_timeout_seconds: 120
|
||||
pool_target_utilization: 0.7
|
||||
queue_limit_per_conn: 64
|
||||
# 流式写出批量 flush 参数
|
||||
event_flush_batch_size: 1
|
||||
event_flush_interval_ms: 10
|
||||
# 预热触发冷却(毫秒)
|
||||
prewarm_cooldown_ms: 300
|
||||
# WS 回退到 HTTP 后的冷却时间(秒),用于避免 WS/HTTP 来回抖动;0 表示关闭冷却
|
||||
fallback_cooldown_seconds: 30
|
||||
# WS 重试退避参数(毫秒)
|
||||
retry_backoff_initial_ms: 120
|
||||
retry_backoff_max_ms: 2000
|
||||
# 抖动比例(0-1)
|
||||
retry_jitter_ratio: 0.2
|
||||
# 单次请求 WS 重试总预算(毫秒);建议设置为有限值,避免重试拉高 TTFT 长尾
|
||||
retry_total_budget_ms: 5000
|
||||
# payload_schema 日志采样率(0-1);降低热路径日志放大
|
||||
payload_log_sample_rate: 0.2
|
||||
# 调度与粘连参数
|
||||
lb_top_k: 7
|
||||
sticky_session_ttl_seconds: 3600
|
||||
# 会话哈希迁移兼容开关:新 key 未命中时回退读取旧 SHA-256 key
|
||||
session_hash_read_old_fallback: true
|
||||
# 会话哈希迁移兼容开关:写入时双写旧 SHA-256 key(短 TTL)
|
||||
session_hash_dual_write_old: true
|
||||
# context 元数据迁移兼容开关:保留旧 ctxkey.* 读取/注入桥接
|
||||
metadata_bridge_enabled: true
|
||||
sticky_response_id_ttl_seconds: 3600
|
||||
# 兼容旧键:当 sticky_response_id_ttl_seconds 缺失时回退该值
|
||||
sticky_previous_response_ttl_seconds: 3600
|
||||
scheduler_score_weights:
|
||||
priority: 1.0
|
||||
load: 1.0
|
||||
queue: 0.7
|
||||
error_rate: 0.8
|
||||
ttft: 0.5
|
||||
# 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_conns: 2560
|
||||
# Max idle connections per host
|
||||
# 每个主机的最大空闲连接数
|
||||
max_idle_conns_per_host: 120
|
||||
# Max connections per host
|
||||
# 每个主机的最大连接数
|
||||
max_conns_per_host: 240
|
||||
max_conns_per_host: 1024
|
||||
# Idle connection timeout (seconds)
|
||||
# 空闲连接超时时间(秒)
|
||||
idle_conn_timeout_seconds: 90
|
||||
@@ -246,9 +378,177 @@ gateway:
|
||||
# name: "Custom Profile 1"
|
||||
# profile_2:
|
||||
# name: "Custom Profile 2"
|
||||
# cipher_suites: [4866, 4867, 4865, 49199, 49195, 49200, 49196]
|
||||
# curves: [29, 23, 24]
|
||||
# point_formats: [0]
|
||||
|
||||
# =============================================================================
|
||||
# Logging Configuration
|
||||
# 日志配置
|
||||
# =============================================================================
|
||||
log:
|
||||
# Log level: debug/info/warn/error
|
||||
# 日志级别:debug/info/warn/error
|
||||
level: "info"
|
||||
# Log format: json/console
|
||||
# 日志格式:json/console
|
||||
format: "console"
|
||||
# Service name field written into each log line
|
||||
# 每条日志都会附带 service 字段
|
||||
service_name: "sub2api"
|
||||
# Environment field written into each log line
|
||||
# 每条日志都会附带 env 字段
|
||||
env: "production"
|
||||
# Include caller information
|
||||
# 是否输出调用方位置信息
|
||||
caller: true
|
||||
# Stacktrace threshold: none/error/fatal
|
||||
# 堆栈输出阈值:none/error/fatal
|
||||
stacktrace_level: "error"
|
||||
output:
|
||||
# Keep stdout/stderr output for container log collection
|
||||
# 保持标准输出用于容器日志采集
|
||||
to_stdout: true
|
||||
# Enable file output (default path auto-derived)
|
||||
# 启用文件输出(默认路径自动推导)
|
||||
to_file: true
|
||||
# Empty means:
|
||||
# - DATA_DIR set: {{DATA_DIR}}/logs/sub2api.log
|
||||
# - otherwise: /app/data/logs/sub2api.log
|
||||
# 留空时:
|
||||
# - 设置 DATA_DIR:{{DATA_DIR}}/logs/sub2api.log
|
||||
# - 否则:/app/data/logs/sub2api.log
|
||||
file_path: ""
|
||||
rotation:
|
||||
# Max file size before rotation (MB)
|
||||
# 单文件滚动阈值(MB)
|
||||
max_size_mb: 100
|
||||
# Number of rotated files to keep (0 means unlimited)
|
||||
# 保留历史文件数量(0 表示不限制)
|
||||
max_backups: 10
|
||||
# Number of days to keep old log files (0 means unlimited)
|
||||
# 历史日志保留天数(0 表示不限制)
|
||||
max_age_days: 7
|
||||
# Compress rotated files
|
||||
# 是否压缩历史日志
|
||||
compress: true
|
||||
# Use local time for timestamp in rotated filename
|
||||
# 滚动文件名时间戳使用本地时区
|
||||
local_time: true
|
||||
sampling:
|
||||
# Enable zap sampler (reduce high-frequency repetitive logs)
|
||||
# 启用 zap 采样(减少高频重复日志)
|
||||
enabled: false
|
||||
# Number of first entries per second to always log
|
||||
# 每秒无采样保留的前 N 条日志
|
||||
initial: 100
|
||||
# Thereafter keep 1 out of N entries per second
|
||||
# 之后每 N 条保留 1 条
|
||||
thereafter: 100
|
||||
|
||||
# =============================================================================
|
||||
# Sora Direct Client Configuration
|
||||
# Sora 直连配置
|
||||
# =============================================================================
|
||||
sora:
|
||||
client:
|
||||
# Sora backend base URL
|
||||
# Sora 上游 Base URL
|
||||
base_url: "https://sora.chatgpt.com/backend"
|
||||
# Request timeout (seconds)
|
||||
# 请求超时(秒)
|
||||
timeout_seconds: 120
|
||||
# Max retries for upstream requests
|
||||
# 上游请求最大重试次数
|
||||
max_retries: 3
|
||||
# Account+proxy cooldown window after Cloudflare challenge (seconds, 0 to disable)
|
||||
# Cloudflare challenge 后按账号+代理冷却窗口(秒,0 表示关闭)
|
||||
cloudflare_challenge_cooldown_seconds: 900
|
||||
# Poll interval (seconds)
|
||||
# 轮询间隔(秒)
|
||||
poll_interval_seconds: 2
|
||||
# Max poll attempts
|
||||
# 最大轮询次数
|
||||
max_poll_attempts: 600
|
||||
# Recent task query limit (image)
|
||||
# 最近任务查询数量(图片轮询)
|
||||
recent_task_limit: 50
|
||||
# Recent task query max limit (fallback)
|
||||
# 最近任务查询最大数量(回退)
|
||||
recent_task_limit_max: 200
|
||||
# Enable debug logs for Sora upstream requests
|
||||
# 启用 Sora 直连调试日志
|
||||
# 调试日志会输出上游请求尝试、重试、响应摘要;Authorization/openai-sentinel-token 等敏感头会自动脱敏
|
||||
debug: false
|
||||
# Allow Sora client to fetch token via OpenAI token provider
|
||||
# 是否允许 Sora 客户端通过 OpenAI token provider 取 token(默认 false,避免误走 OpenAI 刷新链路)
|
||||
use_openai_token_provider: false
|
||||
# Optional custom headers (key-value)
|
||||
# 额外请求头(键值对)
|
||||
headers: {}
|
||||
# Default User-Agent for Sora requests
|
||||
# Sora 默认 User-Agent
|
||||
user_agent: "Sora/1.2026.007 (Android 15; 24122RKC7C; build 2600700)"
|
||||
# Disable TLS fingerprint for Sora upstream
|
||||
# 关闭 Sora 上游 TLS 指纹伪装
|
||||
disable_tls_fingerprint: false
|
||||
# curl_cffi sidecar for Sora only (required)
|
||||
# 仅 Sora 链路使用的 curl_cffi sidecar(必需)
|
||||
curl_cffi_sidecar:
|
||||
# Sora 强制通过 sidecar 请求,必须启用
|
||||
# Sora is forced to use sidecar only; keep enabled=true
|
||||
enabled: true
|
||||
# Sidecar base URL (default endpoint: /request)
|
||||
# sidecar 基础地址(默认请求端点:/request)
|
||||
base_url: "http://sora-curl-cffi-sidecar:8080"
|
||||
# curl_cffi impersonate profile, e.g. chrome131/chrome124/safari18_0
|
||||
# curl_cffi 指纹伪装 profile,例如 chrome131/chrome124/safari18_0
|
||||
impersonate: "chrome131"
|
||||
# Sidecar request timeout (seconds)
|
||||
# sidecar 请求超时(秒)
|
||||
timeout_seconds: 60
|
||||
# Reuse session key per account+proxy to let sidecar persist cookies/session
|
||||
# 按账号+代理复用 session key,让 sidecar 持久化 cookies/session
|
||||
session_reuse_enabled: true
|
||||
# Session TTL in sidecar (seconds)
|
||||
# sidecar 会话 TTL(秒)
|
||||
session_ttl_seconds: 3600
|
||||
storage:
|
||||
# Storage type (local only for now)
|
||||
# 存储类型(首发仅支持 local)
|
||||
type: "local"
|
||||
# Local base path; empty uses /app/data/sora
|
||||
# 本地存储基础路径;为空使用 /app/data/sora
|
||||
local_path: ""
|
||||
# Fallback to upstream URL when download fails
|
||||
# 下载失败时回退到上游 URL
|
||||
fallback_to_upstream: true
|
||||
# Max concurrent downloads
|
||||
# 并发下载上限
|
||||
max_concurrent_downloads: 4
|
||||
# Download timeout (seconds)
|
||||
# 下载超时(秒)
|
||||
download_timeout_seconds: 120
|
||||
# Max download bytes
|
||||
# 最大下载字节数
|
||||
max_download_bytes: 209715200
|
||||
# Enable debug logs for media storage
|
||||
# 启用媒体存储调试日志
|
||||
debug: false
|
||||
cleanup:
|
||||
# Enable cleanup task
|
||||
# 启用清理任务
|
||||
enabled: true
|
||||
# Retention days
|
||||
# 保留天数
|
||||
retention_days: 7
|
||||
# Cron schedule
|
||||
# Cron 调度表达式
|
||||
schedule: "0 3 * * *"
|
||||
|
||||
# Token refresh behavior
|
||||
# token 刷新行为控制
|
||||
token_refresh:
|
||||
# Whether OpenAI refresh flow is allowed to sync linked Sora accounts
|
||||
# 是否允许 OpenAI 刷新流程同步覆盖 linked_openai_account_id 关联的 Sora 账号 token
|
||||
sync_linked_sora_accounts: false
|
||||
|
||||
# =============================================================================
|
||||
# API Key Auth Cache Configuration
|
||||
@@ -352,6 +652,30 @@ usage_cleanup:
|
||||
# 单次任务最大执行时长(秒)
|
||||
task_timeout_seconds: 1800
|
||||
|
||||
# =============================================================================
|
||||
# HTTP 写接口幂等配置
|
||||
# Idempotency Configuration
|
||||
# =============================================================================
|
||||
idempotency:
|
||||
# Observe-only 模式:
|
||||
# true: 观察期,不带 Idempotency-Key 仍放行(但会记录)
|
||||
# false: 强制期,不带 Idempotency-Key 直接拒绝(仅对接入幂等保护的接口生效)
|
||||
observe_only: true
|
||||
# 关键写接口幂等记录 TTL(秒)
|
||||
default_ttl_seconds: 86400
|
||||
# 系统操作接口(update/rollback/restart)幂等记录 TTL(秒)
|
||||
system_operation_ttl_seconds: 3600
|
||||
# processing 锁超时(秒)
|
||||
processing_timeout_seconds: 30
|
||||
# 可重试失败退避窗口(秒)
|
||||
failed_retry_backoff_seconds: 5
|
||||
# 持久化响应体最大长度(字节)
|
||||
max_stored_response_len: 65536
|
||||
# 过期幂等记录清理周期(秒)
|
||||
cleanup_interval_seconds: 60
|
||||
# 每轮清理最大删除条数
|
||||
cleanup_batch_size: 500
|
||||
|
||||
# =============================================================================
|
||||
# Concurrency Wait Configuration
|
||||
# 并发等待配置
|
||||
@@ -381,9 +705,22 @@ database:
|
||||
# Database name
|
||||
# 数据库名称
|
||||
dbname: "sub2api"
|
||||
# SSL mode: disable, require, verify-ca, verify-full
|
||||
# SSL 模式:disable(禁用), require(要求), verify-ca(验证CA), verify-full(完全验证)
|
||||
sslmode: "disable"
|
||||
# SSL mode: disable, prefer, require, verify-ca, verify-full
|
||||
# SSL 模式:disable(禁用), prefer(优先加密,默认), require(要求), verify-ca(验证CA), verify-full(完全验证)
|
||||
# 默认值为 "prefer",数据库支持 SSL 时自动使用加密连接,不支持时回退明文
|
||||
sslmode: "prefer"
|
||||
# Max open connections (高并发场景建议 256+,需配合 PostgreSQL max_connections 调整)
|
||||
# 最大打开连接数
|
||||
max_open_conns: 256
|
||||
# Max idle connections (建议为 max_open_conns 的 50%,减少频繁建连开销)
|
||||
# 最大空闲连接数
|
||||
max_idle_conns: 128
|
||||
# Connection max lifetime (minutes)
|
||||
# 连接最大存活时间(分钟)
|
||||
conn_max_lifetime_minutes: 30
|
||||
# Connection max idle time (minutes)
|
||||
# 空闲连接最大存活时间(分钟)
|
||||
conn_max_idle_time_minutes: 5
|
||||
|
||||
# =============================================================================
|
||||
# Redis Configuration
|
||||
@@ -402,6 +739,12 @@ redis:
|
||||
# Database number (0-15)
|
||||
# 数据库编号(0-15)
|
||||
db: 0
|
||||
# Connection pool size (max concurrent connections)
|
||||
# 连接池大小(最大并发连接数)
|
||||
pool_size: 1024
|
||||
# Minimum number of idle connections (高并发场景建议 128+,保持足够热连接)
|
||||
# 最小空闲连接数
|
||||
min_idle_conns: 128
|
||||
# Enable TLS/SSL connection
|
||||
# 是否启用 TLS/SSL 连接
|
||||
enable_tls: false
|
||||
@@ -428,9 +771,14 @@ jwt:
|
||||
# 重要:生产环境中请更改为随机字符串!
|
||||
# Generate with / 生成命令: openssl rand -hex 32
|
||||
secret: "change-this-to-a-secure-random-string"
|
||||
# Token expiration time in hours (max 24)
|
||||
# 令牌过期时间(小时,最大 24)
|
||||
# Token expiration time in hours (max 168)
|
||||
# 令牌过期时间(小时,最大 168)
|
||||
expire_hour: 24
|
||||
# Access Token 过期时间(分钟)
|
||||
# 优先级说明:
|
||||
# - >0: 按分钟生效(优先于 expire_hour)
|
||||
# - =0: 回退使用 expire_hour
|
||||
access_token_expire_minutes: 0
|
||||
|
||||
# =============================================================================
|
||||
# TOTP (2FA) Configuration
|
||||
@@ -515,12 +863,12 @@ rate_limit:
|
||||
# 定价数据源(可选)
|
||||
# =============================================================================
|
||||
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"
|
||||
# URL to fetch model pricing data (default: pinned model-price-repo commit)
|
||||
# 获取模型定价数据的 URL(默认:固定 commit 的 model-price-repo)
|
||||
remote_url: "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.json"
|
||||
# Hash verification URL (optional)
|
||||
# 哈希校验 URL(可选)
|
||||
hash_url: ""
|
||||
hash_url: "https://raw.githubusercontent.com/Wei-Shaw/model-price-repo/c7947e9871687e664180bc971d4837f1fc2784a9/model_prices_and_context_window.sha256"
|
||||
# Local data directory for caching
|
||||
# 本地数据缓存目录
|
||||
data_dir: "./data"
|
||||
@@ -583,10 +931,14 @@ turnstile:
|
||||
# 默认:使用 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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"
|
||||
# OAuth 客户端配置说明:
|
||||
# 1) 留空 client_id/client_secret:使用 Gemini CLI 内置 OAuth Client(其 client_secret 需通过环境变量注入)
|
||||
# - GEMINI_CLI_OAUTH_CLIENT_SECRET
|
||||
# 2) 同时设置 client_id/client_secret:使用你自建的 OAuth Client(推荐,权限更完整)
|
||||
#
|
||||
# 注意:client_id 与 client_secret 必须同时为空或同时非空。
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
# Optional scopes (space-separated). Leave empty to auto-select based on oauth_type.
|
||||
# 可选的权限范围(空格分隔)。留空则根据 oauth_type 自动选择。
|
||||
scopes: ""
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
@@ -62,6 +62,10 @@ services:
|
||||
- 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}
|
||||
|
||||
# =======================================================================
|
||||
# Redis Configuration
|
||||
@@ -70,6 +74,8 @@ services:
|
||||
- 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}
|
||||
|
||||
# =======================================================================
|
||||
@@ -117,6 +123,11 @@ services:
|
||||
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
|
||||
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
|
||||
|
||||
# Built-in OAuth client secrets (optional)
|
||||
# SECURITY: This repo does not embed third-party client_secret.
|
||||
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
||||
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
||||
|
||||
# =======================================================================
|
||||
# Security Configuration (URL Allowlist)
|
||||
# =======================================================================
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
# =============================================================================
|
||||
# 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
|
||||
# =============================================================================
|
||||
@@ -48,6 +48,10 @@ services:
|
||||
- DATABASE_PASSWORD=${DATABASE_PASSWORD:?DATABASE_PASSWORD is required}
|
||||
- DATABASE_DBNAME=${DATABASE_DBNAME:-sub2api}
|
||||
- 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
|
||||
@@ -56,6 +60,8 @@ services:
|
||||
- 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}
|
||||
|
||||
# =======================================================================
|
||||
@@ -82,6 +88,11 @@ services:
|
||||
- GEMINI_OAUTH_CLIENT_SECRET=${GEMINI_OAUTH_CLIENT_SECRET:-}
|
||||
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
|
||||
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
|
||||
|
||||
# Built-in OAuth client secrets (optional)
|
||||
# SECURITY: This repo does not embed third-party client_secret.
|
||||
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
||||
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
|
||||
@@ -54,6 +54,10 @@ services:
|
||||
- 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}
|
||||
|
||||
# =======================================================================
|
||||
# Redis Configuration
|
||||
@@ -62,6 +66,8 @@ services:
|
||||
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||
- REDIS_PASSWORD=${REDIS_PASSWORD:-redis_JCHeKT}
|
||||
- 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}
|
||||
|
||||
# =======================================================================
|
||||
@@ -109,6 +115,11 @@ services:
|
||||
- GEMINI_OAUTH_SCOPES=${GEMINI_OAUTH_SCOPES:-}
|
||||
- GEMINI_QUOTA_POLICY=${GEMINI_QUOTA_POLICY:-}
|
||||
|
||||
# Built-in OAuth client secrets (optional)
|
||||
# SECURITY: This repo does not embed third-party client_secret.
|
||||
- GEMINI_CLI_OAUTH_CLIENT_SECRET=${GEMINI_CLI_OAUTH_CLIENT_SECRET:-}
|
||||
- ANTIGRAVITY_OAUTH_CLIENT_SECRET=${ANTIGRAVITY_OAUTH_CLIENT_SECRET:-}
|
||||
|
||||
# =======================================================================
|
||||
# Security Configuration (URL Allowlist)
|
||||
# =======================================================================
|
||||
@@ -153,6 +164,10 @@ services:
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
# postgres:18-alpine 默认 PGDATA=/var/lib/postgresql/18/docker(位于镜像声明的匿名卷 /var/lib/postgresql 内)。
|
||||
# 若不显式设置 PGDATA,则即使挂载了 postgres_data 到 /var/lib/postgresql/data,数据也不会落盘到该命名卷,
|
||||
# docker compose down/up 后会触发 initdb 重新初始化,导致用户/密码等数据丢失。
|
||||
- PGDATA=/var/lib/postgresql/data
|
||||
- POSTGRES_USER=${POSTGRES_USER:-sub2api}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-sub2api}
|
||||
|
||||
222
deploy/flow.md
222
deploy/flow.md
@@ -1,222 +0,0 @@
|
||||
```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
|
||||
```
|
||||
123
deploy/install-datamanagementd.sh
Executable file
123
deploy/install-datamanagementd.sh
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 用法:
|
||||
# sudo ./install-datamanagementd.sh --binary /path/to/datamanagementd
|
||||
# 或:
|
||||
# sudo ./install-datamanagementd.sh --source /path/to/sub2api/repo
|
||||
|
||||
BIN_PATH=""
|
||||
SOURCE_PATH=""
|
||||
INSTALL_DIR="/opt/sub2api"
|
||||
DATA_DIR="/var/lib/sub2api/datamanagement"
|
||||
SERVICE_FILE_NAME="sub2api-datamanagementd.service"
|
||||
|
||||
function print_help() {
|
||||
cat <<'EOF'
|
||||
用法:
|
||||
install-datamanagementd.sh [--binary <datamanagementd二进制路径>] [--source <仓库路径>]
|
||||
|
||||
参数:
|
||||
--binary 指定已构建的 datamanagementd 二进制路径
|
||||
--source 指定 sub2api 仓库路径(脚本会执行 go build)
|
||||
-h, --help 显示帮助
|
||||
|
||||
示例:
|
||||
sudo ./install-datamanagementd.sh --binary ./datamanagement/datamanagementd
|
||||
sudo ./install-datamanagementd.sh --source /opt/sub2api-src
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--binary)
|
||||
BIN_PATH="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
--source)
|
||||
SOURCE_PATH="${2:-}"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
print_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "未知参数: $1"
|
||||
print_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -n "$BIN_PATH" && -n "$SOURCE_PATH" ]]; then
|
||||
echo "错误: --binary 与 --source 只能二选一"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "$BIN_PATH" && -z "$SOURCE_PATH" ]]; then
|
||||
echo "错误: 必须提供 --binary 或 --source"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "错误: 请使用 root 权限执行(例如 sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$SOURCE_PATH" ]]; then
|
||||
if [[ ! -d "$SOURCE_PATH/datamanagement" ]]; then
|
||||
echo "错误: 无效仓库路径,未找到 $SOURCE_PATH/datamanagement"
|
||||
exit 1
|
||||
fi
|
||||
echo "[1/6] 从源码构建 datamanagementd..."
|
||||
(cd "$SOURCE_PATH/datamanagement" && go build -o datamanagementd ./cmd/datamanagementd)
|
||||
BIN_PATH="$SOURCE_PATH/datamanagement/datamanagementd"
|
||||
fi
|
||||
|
||||
if [[ ! -f "$BIN_PATH" ]]; then
|
||||
echo "错误: 二进制文件不存在: $BIN_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! id sub2api >/dev/null 2>&1; then
|
||||
echo "[2/6] 创建系统用户 sub2api..."
|
||||
useradd --system --no-create-home --shell /usr/sbin/nologin sub2api
|
||||
else
|
||||
echo "[2/6] 系统用户 sub2api 已存在,跳过创建"
|
||||
fi
|
||||
|
||||
echo "[3/6] 安装 datamanagementd 二进制..."
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
install -m 0755 "$BIN_PATH" "$INSTALL_DIR/datamanagementd"
|
||||
|
||||
echo "[4/6] 准备数据目录..."
|
||||
mkdir -p "$DATA_DIR"
|
||||
chown -R sub2api:sub2api /var/lib/sub2api
|
||||
chmod 0750 "$DATA_DIR"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SERVICE_TEMPLATE="$SCRIPT_DIR/$SERVICE_FILE_NAME"
|
||||
if [[ ! -f "$SERVICE_TEMPLATE" ]]; then
|
||||
echo "错误: 未找到服务模板 $SERVICE_TEMPLATE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[5/6] 安装 systemd 服务..."
|
||||
cp "$SERVICE_TEMPLATE" "/etc/systemd/system/$SERVICE_FILE_NAME"
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now sub2api-datamanagementd
|
||||
|
||||
echo "[6/6] 完成,当前状态:"
|
||||
systemctl --no-pager --full status sub2api-datamanagementd || true
|
||||
|
||||
cat <<'EOF'
|
||||
|
||||
下一步建议:
|
||||
1. 查看日志:sudo journalctl -u sub2api-datamanagementd -f
|
||||
2. 在 sub2api(容器部署时)挂载 socket:
|
||||
/tmp/sub2api-datamanagement.sock:/tmp/sub2api-datamanagement.sock
|
||||
3. 进入管理后台“数据管理”页面确认 agent=enabled
|
||||
|
||||
EOF
|
||||
22
deploy/sub2api-datamanagementd.service
Normal file
22
deploy/sub2api-datamanagementd.service
Normal file
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=Sub2API Data Management Daemon
|
||||
After=network.target
|
||||
Wants=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=sub2api
|
||||
Group=sub2api
|
||||
WorkingDirectory=/opt/sub2api
|
||||
ExecStart=/opt/sub2api/datamanagementd \
|
||||
-socket-path /tmp/sub2api-datamanagement.sock \
|
||||
-sqlite-path /var/lib/sub2api/datamanagement/datamanagementd.db \
|
||||
-version 1.0.0
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
LimitNOFILE=100000
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=false
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user