mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-22 17:07:46 +08:00
refactor: remove legacy environment variable aliases for configuration and concurrency settings
This commit is contained in:
@@ -52,8 +52,7 @@ cp config.example.json config.json
|
|||||||
Use it per deployment mode:
|
Use it per deployment mode:
|
||||||
|
|
||||||
- Local run: read `config.json` directly
|
- Local run: read `config.json` directly
|
||||||
- Docker / Vercel: generate Base64 from `config.json`, then set `DS2API_CONFIG_JSON`
|
- Docker / Vercel: generate Base64 from `config.json`, then set `DS2API_CONFIG_JSON`, or paste raw JSON directly
|
||||||
- Compatibility note: `DS2API_CONFIG_JSON` may also contain raw JSON directly; `CONFIG_JSON` is the legacy fallback variable
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||||
|
|||||||
3
API.md
3
API.md
@@ -52,8 +52,7 @@ cp config.example.json config.json
|
|||||||
按部署方式使用:
|
按部署方式使用:
|
||||||
|
|
||||||
- 本地运行:直接读取 `config.json`
|
- 本地运行:直接读取 `config.json`
|
||||||
- Docker / Vercel:从 `config.json` 生成 Base64,填入 `DS2API_CONFIG_JSON`
|
- Docker / Vercel:从 `config.json` 生成 Base64,填入 `DS2API_CONFIG_JSON`,也可以直接填原始 JSON
|
||||||
- 兼容写法:`DS2API_CONFIG_JSON` 也可直接填原始 JSON;`CONFIG_JSON` 是旧版兼容回退变量
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ cp config.example.json config.json
|
|||||||
|
|
||||||
后续部署建议:
|
后续部署建议:
|
||||||
- 本地运行:直接读取 `config.json`
|
- 本地运行:直接读取 `config.json`
|
||||||
- Docker / Vercel:由 `config.json` 生成 `DS2API_CONFIG_JSON`(Base64)注入环境变量
|
- Docker / Vercel:由 `config.json` 生成 `DS2API_CONFIG_JSON`(Base64)注入环境变量,也可以直接写原始 JSON
|
||||||
- 兼容写法:`DS2API_CONFIG_JSON` 也可以直接写原始 JSON;`CONFIG_JSON` 是旧版回退变量
|
|
||||||
|
|
||||||
### 方式一:本地运行
|
### 方式一:本地运行
|
||||||
|
|
||||||
@@ -341,7 +340,6 @@ cp opencode.json.example opencode.json
|
|||||||
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT 过期小时数 | `24` |
|
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT 过期小时数 | `24` |
|
||||||
| `DS2API_CONFIG_PATH` | 配置文件路径 | `config.json` |
|
| `DS2API_CONFIG_PATH` | 配置文件路径 | `config.json` |
|
||||||
| `DS2API_CONFIG_JSON` | 直接注入配置(JSON 或 Base64) | — |
|
| `DS2API_CONFIG_JSON` | 直接注入配置(JSON 或 Base64) | — |
|
||||||
| `CONFIG_JSON` | 旧版兼容配置注入 | — |
|
|
||||||
| `DS2API_ENV_WRITEBACK` | 环境变量模式下自动写回配置文件并切换文件模式(`1/true/yes/on`) | 关闭 |
|
| `DS2API_ENV_WRITEBACK` | 环境变量模式下自动写回配置文件并切换文件模式(`1/true/yes/on`) | 关闭 |
|
||||||
| `DS2API_WASM_PATH` | PoW WASM 文件路径 | 自动查找 |
|
| `DS2API_WASM_PATH` | PoW WASM 文件路径 | 自动查找 |
|
||||||
| `DS2API_STATIC_ADMIN_DIR` | 管理台静态文件目录 | `static/admin` |
|
| `DS2API_STATIC_ADMIN_DIR` | 管理台静态文件目录 | `static/admin` |
|
||||||
@@ -350,11 +348,8 @@ cp opencode.json.example opencode.json
|
|||||||
| `DS2API_DEV_PACKET_CAPTURE_LIMIT` | 本地抓包保留条数(超出自动淘汰) | `5` |
|
| `DS2API_DEV_PACKET_CAPTURE_LIMIT` | 本地抓包保留条数(超出自动淘汰) | `5` |
|
||||||
| `DS2API_DEV_PACKET_CAPTURE_MAX_BODY_BYTES` | 单条响应体最大记录字节数 | `2097152` |
|
| `DS2API_DEV_PACKET_CAPTURE_MAX_BODY_BYTES` | 单条响应体最大记录字节数 | `2097152` |
|
||||||
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号最大并发 in-flight 请求数 | `2` |
|
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号最大并发 in-flight 请求数 | `2` |
|
||||||
| `DS2API_ACCOUNT_CONCURRENCY` | 同上(兼容旧名) | — |
|
|
||||||
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
|
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
|
||||||
| `DS2API_ACCOUNT_QUEUE_SIZE` | 同上(兼容旧名) | — |
|
|
||||||
| `DS2API_GLOBAL_MAX_INFLIGHT` | 全局最大 in-flight 请求数 | `recommended_concurrency` |
|
| `DS2API_GLOBAL_MAX_INFLIGHT` | 全局最大 in-flight 请求数 | `recommended_concurrency` |
|
||||||
| `DS2API_MAX_INFLIGHT` | 同上(兼容旧名) | — |
|
|
||||||
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel 混合流式内部鉴权密钥 | 回退用 `DS2API_ADMIN_KEY` |
|
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel 混合流式内部鉴权密钥 | 回退用 `DS2API_ADMIN_KEY` |
|
||||||
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease 过期秒数 | `900` |
|
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease 过期秒数 | `900` |
|
||||||
| `DS2API_DEV_PACKET_CAPTURE` | 本地开发抓包开关(记录最近会话请求/响应体) | 本地非 Vercel 默认开启 |
|
| `DS2API_DEV_PACKET_CAPTURE` | 本地开发抓包开关(记录最近会话请求/响应体) | 本地非 Vercel 默认开启 |
|
||||||
@@ -365,7 +360,7 @@ cp opencode.json.example opencode.json
|
|||||||
| `VERCEL_TEAM_ID` | Vercel 团队 ID | — |
|
| `VERCEL_TEAM_ID` | Vercel 团队 ID | — |
|
||||||
| `DS2API_VERCEL_PROTECTION_BYPASS` | Vercel 部署保护绕过密钥(内部 Node→Go 调用) | — |
|
| `DS2API_VERCEL_PROTECTION_BYPASS` | Vercel 部署保护绕过密钥(内部 Node→Go 调用) | — |
|
||||||
|
|
||||||
> 提示:当检测到 `DS2API_CONFIG_JSON/CONFIG_JSON` 时,管理台会显示当前模式风险与自动持久化状态(含 `DS2API_CONFIG_PATH` 路径与模式切换说明)。
|
> 提示:当检测到 `DS2API_CONFIG_JSON` 时,管理台会显示当前模式风险与自动持久化状态(含 `DS2API_CONFIG_PATH` 路径与模式切换说明)。
|
||||||
|
|
||||||
## 鉴权模式
|
## 鉴权模式
|
||||||
|
|
||||||
|
|||||||
@@ -160,8 +160,7 @@ cp config.example.json config.json
|
|||||||
|
|
||||||
Recommended per deployment mode:
|
Recommended per deployment mode:
|
||||||
- Local run: read `config.json` directly
|
- Local run: read `config.json` directly
|
||||||
- Docker / Vercel: generate Base64 from `config.json` and inject as `DS2API_CONFIG_JSON`
|
- Docker / Vercel: generate Base64 from `config.json` and inject as `DS2API_CONFIG_JSON`, or paste raw JSON directly
|
||||||
- Compatibility note: `DS2API_CONFIG_JSON` may also contain raw JSON directly; `CONFIG_JSON` is the legacy fallback variable
|
|
||||||
|
|
||||||
### Option 1: Local Run
|
### Option 1: Local Run
|
||||||
|
|
||||||
@@ -341,17 +340,13 @@ cp opencode.json.example opencode.json
|
|||||||
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT TTL in hours | `24` |
|
| `DS2API_JWT_EXPIRE_HOURS` | Admin JWT TTL in hours | `24` |
|
||||||
| `DS2API_CONFIG_PATH` | Config file path | `config.json` |
|
| `DS2API_CONFIG_PATH` | Config file path | `config.json` |
|
||||||
| `DS2API_CONFIG_JSON` | Inline config (JSON or Base64) | — |
|
| `DS2API_CONFIG_JSON` | Inline config (JSON or Base64) | — |
|
||||||
| `CONFIG_JSON` | Legacy compatibility config input | — |
|
|
||||||
| `DS2API_ENV_WRITEBACK` | Auto-write env-backed config to file and transition to file mode (`1/true/yes/on`) | Disabled |
|
| `DS2API_ENV_WRITEBACK` | Auto-write env-backed config to file and transition to file mode (`1/true/yes/on`) | Disabled |
|
||||||
| `DS2API_WASM_PATH` | PoW WASM file path | Auto-detect |
|
| `DS2API_WASM_PATH` | PoW WASM file path | Auto-detect |
|
||||||
| `DS2API_STATIC_ADMIN_DIR` | Admin static assets dir | `static/admin` |
|
| `DS2API_STATIC_ADMIN_DIR` | Admin static assets dir | `static/admin` |
|
||||||
| `DS2API_AUTO_BUILD_WEBUI` | Auto-build WebUI on startup | Enabled locally, disabled on Vercel |
|
| `DS2API_AUTO_BUILD_WEBUI` | Auto-build WebUI on startup | Enabled locally, disabled on Vercel |
|
||||||
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Max in-flight requests per account | `2` |
|
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Max in-flight requests per account | `2` |
|
||||||
| `DS2API_ACCOUNT_CONCURRENCY` | Alias (legacy compat) | — |
|
|
||||||
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
|
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
|
||||||
| `DS2API_ACCOUNT_QUEUE_SIZE` | Alias (legacy compat) | — |
|
|
||||||
| `DS2API_GLOBAL_MAX_INFLIGHT` | Global max in-flight requests | `recommended_concurrency` |
|
| `DS2API_GLOBAL_MAX_INFLIGHT` | Global max in-flight requests | `recommended_concurrency` |
|
||||||
| `DS2API_MAX_INFLIGHT` | Alias (legacy compat) | — |
|
|
||||||
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
|
| `DS2API_VERCEL_INTERNAL_SECRET` | Vercel hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
|
||||||
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL seconds | `900` |
|
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL seconds | `900` |
|
||||||
| `DS2API_DEV_PACKET_CAPTURE` | Local dev packet capture switch (record recent request/response bodies) | Enabled by default on non-Vercel local runtime |
|
| `DS2API_DEV_PACKET_CAPTURE` | Local dev packet capture switch (record recent request/response bodies) | Enabled by default on non-Vercel local runtime |
|
||||||
@@ -362,7 +357,7 @@ cp opencode.json.example opencode.json
|
|||||||
| `VERCEL_TEAM_ID` | Vercel team ID | — |
|
| `VERCEL_TEAM_ID` | Vercel team ID | — |
|
||||||
| `DS2API_VERCEL_PROTECTION_BYPASS` | Vercel deployment protection bypass for internal Node→Go calls | — |
|
| `DS2API_VERCEL_PROTECTION_BYPASS` | Vercel deployment protection bypass for internal Node→Go calls | — |
|
||||||
|
|
||||||
> Note: when `DS2API_CONFIG_JSON/CONFIG_JSON` is detected, the Admin UI shows mode risk and auto-persistence status (including `DS2API_CONFIG_PATH` and mode-transition hints).
|
> Note: when `DS2API_CONFIG_JSON` is detected, the Admin UI shows mode risk and auto-persistence status (including `DS2API_CONFIG_PATH` and mode-transition hints).
|
||||||
|
|
||||||
## Authentication Modes
|
## Authentication Modes
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ Config source (choose one):
|
|||||||
|
|
||||||
- **File**: `config.json` (recommended for local/Docker)
|
- **File**: `config.json` (recommended for local/Docker)
|
||||||
- **Environment variable**: `DS2API_CONFIG_JSON` (recommended for Vercel; supports raw JSON or Base64)
|
- **Environment variable**: `DS2API_CONFIG_JSON` (recommended for Vercel; supports raw JSON or Base64)
|
||||||
- Compatibility note: `CONFIG_JSON` is the legacy fallback variable; `DS2API_CONFIG_JSON` may also contain raw JSON directly
|
|
||||||
|
|
||||||
Unified recommendation (best practice):
|
Unified recommendation (best practice):
|
||||||
|
|
||||||
@@ -200,10 +199,10 @@ Notes:
|
|||||||
2. **Import** the project on Vercel
|
2. **Import** the project on Vercel
|
||||||
3. **Set environment variables** (minimum required: one variable):
|
3. **Set environment variables** (minimum required: one variable):
|
||||||
|
|
||||||
| Variable | Description |
|
| Variable | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `DS2API_ADMIN_KEY` | Admin key (required) |
|
| `DS2API_ADMIN_KEY` | Admin key (required) |
|
||||||
| `DS2API_CONFIG_JSON` | Config content, raw JSON or Base64 (optional, recommended) |
|
| `DS2API_CONFIG_JSON` | Config content, raw JSON or Base64 (optional, recommended) |
|
||||||
|
|
||||||
4. **Deploy**
|
4. **Deploy**
|
||||||
|
|
||||||
@@ -246,11 +245,8 @@ VERCEL_TEAM_ID=team_xxxxxxxxxxxx # optional for personal accounts
|
|||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Per-account inflight limit | `2` |
|
| `DS2API_ACCOUNT_MAX_INFLIGHT` | Per-account inflight limit | `2` |
|
||||||
| `DS2API_ACCOUNT_CONCURRENCY` | Alias (legacy compat) | — |
|
|
||||||
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
|
| `DS2API_ACCOUNT_MAX_QUEUE` | Waiting queue limit | `recommended_concurrency` |
|
||||||
| `DS2API_ACCOUNT_QUEUE_SIZE` | Alias (legacy compat) | — |
|
|
||||||
| `DS2API_GLOBAL_MAX_INFLIGHT` | Global inflight limit | `recommended_concurrency` |
|
| `DS2API_GLOBAL_MAX_INFLIGHT` | Global inflight limit | `recommended_concurrency` |
|
||||||
| `DS2API_MAX_INFLIGHT` | Alias (legacy compat) | — |
|
|
||||||
| `DS2API_ENV_WRITEBACK` | When `DS2API_CONFIG_JSON` is present, auto-write to `DS2API_CONFIG_PATH` and switch to file-backed mode after success (`1/true/yes/on`) | Disabled |
|
| `DS2API_ENV_WRITEBACK` | When `DS2API_CONFIG_JSON` is present, auto-write to `DS2API_CONFIG_PATH` and switch to file-backed mode after success (`1/true/yes/on`) | Disabled |
|
||||||
| `DS2API_VERCEL_INTERNAL_SECRET` | Hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
|
| `DS2API_VERCEL_INTERNAL_SECRET` | Hybrid streaming internal auth | Falls back to `DS2API_ADMIN_KEY` |
|
||||||
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL | `900` |
|
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | Stream lease TTL | `900` |
|
||||||
|
|||||||
@@ -31,8 +31,7 @@
|
|||||||
配置来源(任选其一):
|
配置来源(任选其一):
|
||||||
|
|
||||||
- **文件方式**:`config.json`(推荐本地/Docker 使用)
|
- **文件方式**:`config.json`(推荐本地/Docker 使用)
|
||||||
- **环境变量方式**:`DS2API_CONFIG_JSON`(推荐 Vercel 使用,支持 JSON 字符串或 Base64 编码)
|
- **环境变量方式**:`DS2API_CONFIG_JSON`(推荐 Vercel 使用,支持 JSON 字符串或 Base64 编码,也可以直接写原始 JSON)
|
||||||
- 兼容写法:`CONFIG_JSON` 是旧版回退变量;`DS2API_CONFIG_JSON` 也可以直接写原始 JSON
|
|
||||||
|
|
||||||
统一建议(最优实践):
|
统一建议(最优实践):
|
||||||
|
|
||||||
@@ -200,10 +199,10 @@ healthcheck:
|
|||||||
2. **在 Vercel 上导入项目**
|
2. **在 Vercel 上导入项目**
|
||||||
3. **配置环境变量**(最少只需设置以下一项):
|
3. **配置环境变量**(最少只需设置以下一项):
|
||||||
|
|
||||||
| 变量 | 说明 |
|
| 变量 | 说明 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `DS2API_ADMIN_KEY` | 管理密钥(必填) |
|
| `DS2API_ADMIN_KEY` | 管理密钥(必填) |
|
||||||
| `DS2API_CONFIG_JSON` | 配置内容,JSON 字符串或 Base64 编码(可选,建议) |
|
| `DS2API_CONFIG_JSON` | 配置内容,JSON 字符串或 Base64 编码(可选,建议) |
|
||||||
|
|
||||||
4. **部署**
|
4. **部署**
|
||||||
|
|
||||||
@@ -246,11 +245,8 @@ VERCEL_TEAM_ID=team_xxxxxxxxxxxx # 个人账号可留空
|
|||||||
| 变量 | 说明 | 默认值 |
|
| 变量 | 说明 | 默认值 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号并发上限 | `2` |
|
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号并发上限 | `2` |
|
||||||
| `DS2API_ACCOUNT_CONCURRENCY` | 同上(兼容别名) | — |
|
|
||||||
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
|
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
|
||||||
| `DS2API_ACCOUNT_QUEUE_SIZE` | 同上(兼容别名) | — |
|
|
||||||
| `DS2API_GLOBAL_MAX_INFLIGHT` | 全局并发上限 | `recommended_concurrency` |
|
| `DS2API_GLOBAL_MAX_INFLIGHT` | 全局并发上限 | `recommended_concurrency` |
|
||||||
| `DS2API_MAX_INFLIGHT` | 同上(兼容别名) | — |
|
|
||||||
| `DS2API_ENV_WRITEBACK` | 检测到 `DS2API_CONFIG_JSON` 时自动写入 `DS2API_CONFIG_PATH`,并在成功后转为文件模式(`1/true/yes/on`) | 关闭 |
|
| `DS2API_ENV_WRITEBACK` | 检测到 `DS2API_CONFIG_JSON` 时自动写入 `DS2API_CONFIG_PATH`,并在成功后转为文件模式(`1/true/yes/on`) | 关闭 |
|
||||||
| `DS2API_VERCEL_INTERNAL_SECRET` | 混合流式内部鉴权 | 回退用 `DS2API_ADMIN_KEY` |
|
| `DS2API_VERCEL_INTERNAL_SECRET` | 混合流式内部鉴权 | 回退用 `DS2API_ADMIN_KEY` |
|
||||||
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease TTL | `900` |
|
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease TTL | `900` |
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ import (
|
|||||||
|
|
||||||
func TestPoolEmptyNoAccounts(t *testing.T) {
|
func TestPoolEmptyNoAccounts(t *testing.T) {
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "2")
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "2")
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
||||||
t.Setenv("DS2API_ACCOUNT_QUEUE_SIZE", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[]}`)
|
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[]}`)
|
||||||
pool := NewPool(config.LoadStore())
|
pool := NewPool(config.LoadStore())
|
||||||
if _, ok := pool.Acquire("", nil); ok {
|
if _, ok := pool.Acquire("", nil); ok {
|
||||||
@@ -165,9 +163,7 @@ func TestPoolAcquireWaitTargetAccount(t *testing.T) {
|
|||||||
|
|
||||||
func TestPoolMaxQueueSizeOverride(t *testing.T) {
|
func TestPoolMaxQueueSizeOverride(t *testing.T) {
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "5")
|
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "5")
|
||||||
t.Setenv("DS2API_ACCOUNT_QUEUE_SIZE", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[{"email":"acc1@example.com","token":"t1"}]}`)
|
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[{"email":"acc1@example.com","token":"t1"}]}`)
|
||||||
pool := NewPool(config.LoadStore())
|
pool := NewPool(config.LoadStore())
|
||||||
status := pool.Status()
|
status := pool.Status()
|
||||||
@@ -176,19 +172,6 @@ func TestPoolMaxQueueSizeOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoolQueueSizeAliasEnv(t *testing.T) {
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_QUEUE_SIZE", "7")
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[{"email":"acc1@example.com","token":"t1"}]}`)
|
|
||||||
pool := NewPool(config.LoadStore())
|
|
||||||
status := pool.Status()
|
|
||||||
if got, ok := status["max_queue_size"].(int); !ok || got != 7 {
|
|
||||||
t.Fatalf("expected max_queue_size=7, got %#v", status["max_queue_size"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPoolMultipleAcquireReleaseCycles(t *testing.T) {
|
func TestPoolMultipleAcquireReleaseCycles(t *testing.T) {
|
||||||
pool := newSingleAccountPoolForTest(t, "1")
|
pool := newSingleAccountPoolForTest(t, "1")
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
|
|||||||
@@ -29,13 +29,8 @@ func (p *Pool) ApplyRuntimeLimits(maxInflightPerAccount, maxQueueSize, globalMax
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maxInflightFromEnv() int {
|
func maxInflightFromEnv() int {
|
||||||
for _, key := range []string{"DS2API_ACCOUNT_MAX_INFLIGHT", "DS2API_ACCOUNT_CONCURRENCY"} {
|
if raw := strings.TrimSpace(os.Getenv("DS2API_ACCOUNT_MAX_INFLIGHT")); raw != "" {
|
||||||
raw := strings.TrimSpace(os.Getenv(key))
|
if n, err := strconv.Atoi(raw); err == nil && n > 0 {
|
||||||
if raw == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(raw)
|
|
||||||
if err == nil && n > 0 {
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,13 +48,8 @@ func defaultRecommendedConcurrency(accountCount, maxInflightPerAccount int) int
|
|||||||
}
|
}
|
||||||
|
|
||||||
func maxQueueFromEnv(defaultSize int) int {
|
func maxQueueFromEnv(defaultSize int) int {
|
||||||
for _, key := range []string{"DS2API_ACCOUNT_MAX_QUEUE", "DS2API_ACCOUNT_QUEUE_SIZE"} {
|
if raw := strings.TrimSpace(os.Getenv("DS2API_ACCOUNT_MAX_QUEUE")); raw != "" {
|
||||||
raw := strings.TrimSpace(os.Getenv(key))
|
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
|
||||||
if raw == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(raw)
|
|
||||||
if err == nil && n >= 0 {
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,7 @@ import (
|
|||||||
func newPoolForTest(t *testing.T, maxInflight string) *Pool {
|
func newPoolForTest(t *testing.T, maxInflight string) *Pool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", maxInflight)
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", maxInflight)
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
||||||
t.Setenv("DS2API_ACCOUNT_QUEUE_SIZE", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
||||||
"keys":["k1"],
|
"keys":["k1"],
|
||||||
"accounts":[
|
"accounts":[
|
||||||
@@ -29,9 +27,7 @@ func newPoolForTest(t *testing.T, maxInflight string) *Pool {
|
|||||||
func newSingleAccountPoolForTest(t *testing.T, maxInflight string) *Pool {
|
func newSingleAccountPoolForTest(t *testing.T, maxInflight string) *Pool {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", maxInflight)
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", maxInflight)
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
||||||
t.Setenv("DS2API_ACCOUNT_QUEUE_SIZE", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
||||||
"keys":["k1"],
|
"keys":["k1"],
|
||||||
"accounts":[{"email":"acc1@example.com","token":"token1"}]
|
"accounts":[{"email":"acc1@example.com","token":"token1"}]
|
||||||
@@ -170,9 +166,9 @@ func TestPoolStatusRecommendedConcurrencyRespectsOverride(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoolAccountConcurrencyAliasEnv(t *testing.T) {
|
func TestPoolGlobalMaxInflightEnv(t *testing.T) {
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "")
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "4")
|
t.Setenv("DS2API_GLOBAL_MAX_INFLIGHT", "4")
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
||||||
"keys":["k1"],
|
"keys":["k1"],
|
||||||
"accounts":[
|
"accounts":[
|
||||||
@@ -183,15 +179,15 @@ func TestPoolAccountConcurrencyAliasEnv(t *testing.T) {
|
|||||||
|
|
||||||
pool := NewPool(config.LoadStore())
|
pool := NewPool(config.LoadStore())
|
||||||
status := pool.Status()
|
status := pool.Status()
|
||||||
if got, ok := status["max_inflight_per_account"].(int); !ok || got != 4 {
|
if got, ok := status["global_max_inflight"].(int); !ok || got != 4 {
|
||||||
|
t.Fatalf("unexpected global_max_inflight: %#v", status["global_max_inflight"])
|
||||||
|
}
|
||||||
|
if got, ok := status["max_inflight_per_account"].(int); !ok || got != 1 {
|
||||||
t.Fatalf("unexpected max_inflight_per_account: %#v", status["max_inflight_per_account"])
|
t.Fatalf("unexpected max_inflight_per_account: %#v", status["max_inflight_per_account"])
|
||||||
}
|
}
|
||||||
if got, ok := status["recommended_concurrency"].(int); !ok || got != 8 {
|
if got, ok := status["recommended_concurrency"].(int); !ok || got != 2 {
|
||||||
t.Fatalf("unexpected recommended_concurrency: %#v", status["recommended_concurrency"])
|
t.Fatalf("unexpected recommended_concurrency: %#v", status["recommended_concurrency"])
|
||||||
}
|
}
|
||||||
if got, ok := status["max_queue_size"].(int); !ok || got != 8 {
|
|
||||||
t.Fatalf("unexpected max_queue_size: %#v", status["max_queue_size"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPoolDropsLegacyTokenOnlyAccountOnLoad(t *testing.T) {
|
func TestPoolDropsLegacyTokenOnlyAccountOnLoad(t *testing.T) {
|
||||||
@@ -217,9 +213,7 @@ func TestPoolDropsLegacyTokenOnlyAccountOnLoad(t *testing.T) {
|
|||||||
|
|
||||||
func TestPoolAcquireRotatesIntoTokenlessAccounts(t *testing.T) {
|
func TestPoolAcquireRotatesIntoTokenlessAccounts(t *testing.T) {
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
t.Setenv("DS2API_ACCOUNT_MAX_INFLIGHT", "1")
|
||||||
t.Setenv("DS2API_ACCOUNT_CONCURRENCY", "")
|
|
||||||
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
t.Setenv("DS2API_ACCOUNT_MAX_QUEUE", "")
|
||||||
t.Setenv("DS2API_ACCOUNT_QUEUE_SIZE", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{
|
t.Setenv("DS2API_CONFIG_JSON", `{
|
||||||
"keys":["k1"],
|
"keys":["k1"],
|
||||||
"accounts":[
|
"accounts":[
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
func newAdminTestHandler(t *testing.T, raw string) *Handler {
|
func newAdminTestHandler(t *testing.T, raw string) *Handler {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Setenv("DS2API_CONFIG_JSON", raw)
|
t.Setenv("DS2API_CONFIG_JSON", raw)
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
store := config.LoadStore()
|
store := config.LoadStore()
|
||||||
return &Handler{
|
return &Handler{
|
||||||
Store: store,
|
Store: store,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
func newHTTPAdminHarness(t *testing.T, rawConfig string, ds DeepSeekCaller) http.Handler {
|
func newHTTPAdminHarness(t *testing.T, rawConfig string, ds DeepSeekCaller) http.Handler {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Setenv("DS2API_CONFIG_JSON", rawConfig)
|
t.Setenv("DS2API_CONFIG_JSON", rawConfig)
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
store := config.LoadStore()
|
store := config.LoadStore()
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
Store: store,
|
Store: store,
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ func TestLoadStorePreservesFileBackedTokensForRuntime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", "")
|
t.Setenv("DS2API_CONFIG_JSON", "")
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_PATH", tmp.Name())
|
t.Setenv("DS2API_CONFIG_PATH", tmp.Name())
|
||||||
|
|
||||||
store := LoadStore()
|
store := LoadStore()
|
||||||
@@ -80,6 +79,31 @@ func TestLoadStorePreservesFileBackedTokensForRuntime(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadStoreIgnoresLegacyConfigJSONEnv(t *testing.T) {
|
||||||
|
tmp, err := os.CreateTemp(t.TempDir(), "config-*.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create temp config: %v", err)
|
||||||
|
}
|
||||||
|
path := tmp.Name()
|
||||||
|
_ = tmp.Close()
|
||||||
|
_ = os.Remove(path)
|
||||||
|
|
||||||
|
t.Setenv("DS2API_CONFIG_JSON", "")
|
||||||
|
t.Setenv("CONFIG_JSON", `{"keys":["legacy-key"],"accounts":[{"email":"legacy@example.com","password":"p"}]}`)
|
||||||
|
t.Setenv("DS2API_CONFIG_PATH", path)
|
||||||
|
|
||||||
|
store := LoadStore()
|
||||||
|
if store.HasEnvConfigSource() {
|
||||||
|
t.Fatal("expected legacy CONFIG_JSON to be ignored")
|
||||||
|
}
|
||||||
|
if store.IsEnvBacked() {
|
||||||
|
t.Fatal("expected store to remain file-backed/empty when only CONFIG_JSON is set")
|
||||||
|
}
|
||||||
|
if len(store.Keys()) != 0 || len(store.Accounts()) != 0 {
|
||||||
|
t.Fatalf("expected ignored legacy env to leave store empty, got keys=%d accounts=%d", len(store.Keys()), len(store.Accounts()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnvBackedStoreWritebackBootstrapsMissingConfigFile(t *testing.T) {
|
func TestEnvBackedStoreWritebackBootstrapsMissingConfigFile(t *testing.T) {
|
||||||
tmp, err := os.CreateTemp(t.TempDir(), "config-*.json")
|
tmp, err := os.CreateTemp(t.TempDir(), "config-*.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -90,7 +114,6 @@ func TestEnvBackedStoreWritebackBootstrapsMissingConfigFile(t *testing.T) {
|
|||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[{"email":"seed@example.com","password":"p"}]}`)
|
t.Setenv("DS2API_CONFIG_JSON", `{"keys":["k1"],"accounts":[{"email":"seed@example.com","password":"p"}]}`)
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_PATH", path)
|
t.Setenv("DS2API_CONFIG_PATH", path)
|
||||||
t.Setenv("DS2API_ENV_WRITEBACK", "1")
|
t.Setenv("DS2API_ENV_WRITEBACK", "1")
|
||||||
|
|
||||||
@@ -135,7 +158,6 @@ func TestEnvBackedStoreWritebackDoesNotBootstrapOnInvalidEnvJSON(t *testing.T) {
|
|||||||
_ = os.Remove(path)
|
_ = os.Remove(path)
|
||||||
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", "{invalid-json")
|
t.Setenv("DS2API_CONFIG_JSON", "{invalid-json")
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_PATH", path)
|
t.Setenv("DS2API_CONFIG_PATH", path)
|
||||||
t.Setenv("DS2API_ENV_WRITEBACK", "1")
|
t.Setenv("DS2API_ENV_WRITEBACK", "1")
|
||||||
|
|
||||||
@@ -166,7 +188,6 @@ func TestEnvBackedStoreWritebackFallsBackToPersistedFileOnInvalidEnvJSON(t *test
|
|||||||
_ = tmp.Close()
|
_ = tmp.Close()
|
||||||
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", "{invalid-json")
|
t.Setenv("DS2API_CONFIG_JSON", "{invalid-json")
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_PATH", path)
|
t.Setenv("DS2API_CONFIG_PATH", path)
|
||||||
t.Setenv("DS2API_ENV_WRITEBACK", "1")
|
t.Setenv("DS2API_ENV_WRITEBACK", "1")
|
||||||
|
|
||||||
@@ -265,7 +286,6 @@ func TestParseConfigStringSupportsRawURLBase64(t *testing.T) {
|
|||||||
func TestLoadConfigOnVercelWithoutConfigFileFallsBackToMemory(t *testing.T) {
|
func TestLoadConfigOnVercelWithoutConfigFileFallsBackToMemory(t *testing.T) {
|
||||||
t.Setenv("VERCEL", "1")
|
t.Setenv("VERCEL", "1")
|
||||||
t.Setenv("DS2API_CONFIG_JSON", "")
|
t.Setenv("DS2API_CONFIG_JSON", "")
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_PATH", "testdata/does-not-exist.json")
|
t.Setenv("DS2API_CONFIG_PATH", "testdata/does-not-exist.json")
|
||||||
|
|
||||||
cfg, fromEnv, err := loadConfig()
|
cfg, fromEnv, err := loadConfig()
|
||||||
@@ -293,7 +313,6 @@ func TestAccountTestStatusIsRuntimeOnlyAndNotPersisted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Setenv("DS2API_CONFIG_JSON", "")
|
t.Setenv("DS2API_CONFIG_JSON", "")
|
||||||
t.Setenv("CONFIG_JSON", "")
|
|
||||||
t.Setenv("DS2API_CONFIG_PATH", tmp.Name())
|
t.Setenv("DS2API_CONFIG_PATH", tmp.Name())
|
||||||
|
|
||||||
store := LoadStore()
|
store := LoadStore()
|
||||||
|
|||||||
@@ -35,9 +35,6 @@ func LoadStore() *Store {
|
|||||||
|
|
||||||
func loadConfig() (Config, bool, error) {
|
func loadConfig() (Config, bool, error) {
|
||||||
rawCfg := strings.TrimSpace(os.Getenv("DS2API_CONFIG_JSON"))
|
rawCfg := strings.TrimSpace(os.Getenv("DS2API_CONFIG_JSON"))
|
||||||
if rawCfg == "" {
|
|
||||||
rawCfg = strings.TrimSpace(os.Getenv("CONFIG_JSON"))
|
|
||||||
}
|
|
||||||
if rawCfg != "" {
|
if rawCfg != "" {
|
||||||
cfg, err := parseConfigString(rawCfg)
|
cfg, err := parseConfigString(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -120,13 +120,8 @@ func (s *Store) RuntimeAccountMaxInflight() int {
|
|||||||
if s.cfg.Runtime.AccountMaxInflight > 0 {
|
if s.cfg.Runtime.AccountMaxInflight > 0 {
|
||||||
return s.cfg.Runtime.AccountMaxInflight
|
return s.cfg.Runtime.AccountMaxInflight
|
||||||
}
|
}
|
||||||
for _, key := range []string{"DS2API_ACCOUNT_MAX_INFLIGHT", "DS2API_ACCOUNT_CONCURRENCY"} {
|
if raw := strings.TrimSpace(os.Getenv("DS2API_ACCOUNT_MAX_INFLIGHT")); raw != "" {
|
||||||
raw := strings.TrimSpace(os.Getenv(key))
|
if n, err := strconv.Atoi(raw); err == nil && n > 0 {
|
||||||
if raw == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(raw)
|
|
||||||
if err == nil && n > 0 {
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,13 +134,8 @@ func (s *Store) RuntimeAccountMaxQueue(defaultSize int) int {
|
|||||||
if s.cfg.Runtime.AccountMaxQueue > 0 {
|
if s.cfg.Runtime.AccountMaxQueue > 0 {
|
||||||
return s.cfg.Runtime.AccountMaxQueue
|
return s.cfg.Runtime.AccountMaxQueue
|
||||||
}
|
}
|
||||||
for _, key := range []string{"DS2API_ACCOUNT_MAX_QUEUE", "DS2API_ACCOUNT_QUEUE_SIZE"} {
|
if raw := strings.TrimSpace(os.Getenv("DS2API_ACCOUNT_MAX_QUEUE")); raw != "" {
|
||||||
raw := strings.TrimSpace(os.Getenv(key))
|
if n, err := strconv.Atoi(raw); err == nil && n >= 0 {
|
||||||
if raw == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(raw)
|
|
||||||
if err == nil && n >= 0 {
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,13 +151,8 @@ func (s *Store) RuntimeGlobalMaxInflight(defaultSize int) int {
|
|||||||
if s.cfg.Runtime.GlobalMaxInflight > 0 {
|
if s.cfg.Runtime.GlobalMaxInflight > 0 {
|
||||||
return s.cfg.Runtime.GlobalMaxInflight
|
return s.cfg.Runtime.GlobalMaxInflight
|
||||||
}
|
}
|
||||||
for _, key := range []string{"DS2API_GLOBAL_MAX_INFLIGHT", "DS2API_MAX_INFLIGHT"} {
|
if raw := strings.TrimSpace(os.Getenv("DS2API_GLOBAL_MAX_INFLIGHT")); raw != "" {
|
||||||
raw := strings.TrimSpace(os.Getenv(key))
|
if n, err := strconv.Atoi(raw); err == nil && n > 0 {
|
||||||
if raw == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.Atoi(raw)
|
|
||||||
if err == nil && n > 0 {
|
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ func (s *Store) IsEnvWritebackEnabled() bool {
|
|||||||
|
|
||||||
func (s *Store) HasEnvConfigSource() bool {
|
func (s *Store) HasEnvConfigSource() bool {
|
||||||
rawCfg := strings.TrimSpace(os.Getenv("DS2API_CONFIG_JSON"))
|
rawCfg := strings.TrimSpace(os.Getenv("DS2API_CONFIG_JSON"))
|
||||||
if rawCfg == "" {
|
|
||||||
rawCfg = strings.TrimSpace(os.Getenv("CONFIG_JSON"))
|
|
||||||
}
|
|
||||||
return rawCfg != ""
|
return rawCfg != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,6 @@ func (r *Runner) startServer(ctx context.Context) error {
|
|||||||
"DS2API_CONFIG_PATH": r.configCopyPath,
|
"DS2API_CONFIG_PATH": r.configCopyPath,
|
||||||
"DS2API_AUTO_BUILD_WEBUI": "false",
|
"DS2API_AUTO_BUILD_WEBUI": "false",
|
||||||
"DS2API_CONFIG_JSON": "",
|
"DS2API_CONFIG_JSON": "",
|
||||||
"CONFIG_JSON": "",
|
|
||||||
})
|
})
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
_ = logFd.Close()
|
_ = logFd.Close()
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
"deleteAllSessionsConfirm": "Are you sure you want to delete all sessions for this account? This action cannot be undone.",
|
"deleteAllSessionsConfirm": "Are you sure you want to delete all sessions for this account? This action cannot be undone.",
|
||||||
"deleteAllSessionsSuccess": "Successfully deleted all sessions",
|
"deleteAllSessionsSuccess": "Successfully deleted all sessions",
|
||||||
"envModeRiskTitle": "Environment-variable config mode detected (persistence risk)",
|
"envModeRiskTitle": "Environment-variable config mode detected (persistence risk)",
|
||||||
"envModeRiskDesc": "Detected DS2API_CONFIG_JSON/CONFIG_JSON. If DS2API_ENV_WRITEBACK is not enabled, Admin UI edits are in-memory only and may be lost after restart.",
|
"envModeRiskDesc": "Detected DS2API_CONFIG_JSON. If DS2API_ENV_WRITEBACK is not enabled, Admin UI edits are in-memory only and may be lost after restart.",
|
||||||
"envModeWritebackPendingTitle": "Env mode + auto-persistence enabled (pending file handoff)",
|
"envModeWritebackPendingTitle": "Env mode + auto-persistence enabled (pending file handoff)",
|
||||||
"envModeWritebackActiveTitle": "Env mode + auto-persistence active",
|
"envModeWritebackActiveTitle": "Env mode + auto-persistence active",
|
||||||
"envModeWritebackDesc": "The app will auto-create/write the config file and transition to file-backed mode. Current persistence path: {path}"
|
"envModeWritebackDesc": "The app will auto-create/write the config file and transition to file-backed mode. Current persistence path: {path}"
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
"deleteAllSessionsConfirm": "确定要删除该账号的所有会话吗?此操作不可恢复。",
|
"deleteAllSessionsConfirm": "确定要删除该账号的所有会话吗?此操作不可恢复。",
|
||||||
"deleteAllSessionsSuccess": "删除成功",
|
"deleteAllSessionsSuccess": "删除成功",
|
||||||
"envModeRiskTitle": "当前为环境变量配置模式(有持久化风险)",
|
"envModeRiskTitle": "当前为环境变量配置模式(有持久化风险)",
|
||||||
"envModeRiskDesc": "检测到 DS2API_CONFIG_JSON/CONFIG_JSON。若未开启 DS2API_ENV_WRITEBACK,管理台改动仅在内存生效,重启可能丢失。",
|
"envModeRiskDesc": "检测到 DS2API_CONFIG_JSON。若未开启 DS2API_ENV_WRITEBACK,管理台改动仅在内存生效,重启可能丢失。",
|
||||||
"envModeWritebackPendingTitle": "环境变量模式 + 自动持久化已开启(等待落盘)",
|
"envModeWritebackPendingTitle": "环境变量模式 + 自动持久化已开启(等待落盘)",
|
||||||
"envModeWritebackActiveTitle": "环境变量模式 + 自动持久化已生效",
|
"envModeWritebackActiveTitle": "环境变量模式 + 自动持久化已生效",
|
||||||
"envModeWritebackDesc": "程序会自动创建/写入配置文件并在后续切换为文件模式。当前持久化路径:{path}"
|
"envModeWritebackDesc": "程序会自动创建/写入配置文件并在后续切换为文件模式。当前持久化路径:{path}"
|
||||||
|
|||||||
Reference in New Issue
Block a user