File size: 22,817 Bytes
8d3471e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
# DS2API 部署指南

语言 / Language: [中文](DEPLOY.md) | [English](DEPLOY.en.md)

本指南基于当前 Go 代码库,详细说明各种部署方式。

本页导航:[文档总索引](./README.md)|[架构说明](./ARCHITECTURE.md)|[接口文档](../API.md)|[测试指南](./TESTING.md)

---

## 目录

- [部署方式优先级建议](#部署方式优先级建议)
- [前置要求](#0-前置要求)
- [一、下载 Release 构建包](#一下载-release-构建包)
- [二、Docker / GHCR 部署](#二docker--ghcr-部署)
- [三、Vercel 部署](#三vercel-部署)
- [四、本地源码运行](#四本地源码运行)
- [五、反向代理(Nginx)](#五反向代理nginx)
- [六、Linux systemd 服务化](#六linux-systemd-服务化)
- [七、部署后检查](#七部署后检查)
- [八、发布前进行本地回归](#八发布前进行本地回归)

---

## 部署方式优先级建议

推荐按以下顺序选择部署方式:

1. **下载 Release 构建包运行**:最省事,产物已编译完成,最适合大多数用户。
2. **Docker / GHCR 镜像部署**:适合需要容器化、编排或云环境部署。
3. **Vercel 部署**:适合已有 Vercel 环境且接受其平台约束的场景。
4. **本地源码运行 / 自行编译**:适合开发、调试或需要自行修改代码的场景。

---

## 0. 前置要求

| 依赖 | 最低版本 | 说明 |
| --- | --- | --- |
| Go | 1.26+ | 编译后端 |
| Node.js | `20.19+``22.12+` | 仅在需要本地构建 WebUI 时 |
| npm | 随 Node.js 提供 | 安装 WebUI 依赖 |

配置来源(任选其一):

- **文件方式**`config.json`(推荐本地/Docker 使用)
- **环境变量方式**`DS2API_CONFIG_JSON`(推荐 Vercel 使用,支持 JSON 字符串或 Base64 编码,也可以直接写原始 JSON)

统一建议(最优实践):

```bash
cp config.example.json config.json
# 编辑 config.json
```

建议把 `config.json` 作为唯一配置源:
- 本地运行:直接读 `config.json`
- Docker / Vercel:从 `config.json` 生成 `DS2API_CONFIG_JSON`(Base64)注入环境变量

---

## 一、下载 Release 构建包

仓库内置 GitHub Actions 工作流:`.github/workflows/release-artifacts.yml`

- **触发条件**:默认仅在 Release `published` 时自动触发;也支持在 Actions 页面手动 `workflow_dispatch`,并填写 `release_tag` 复跑/补发
- **构建产物**:多平台二进制压缩包、Linux Docker 镜像导出包 + `sha256sums.txt`
- **容器镜像发布**:仅发布到 GHCR(`ghcr.io/cjackhwang/ds2api`)

| 平台 | 架构 | 文件格式 |
| --- | --- | --- |
| Linux | amd64, arm64, armv7 | `.tar.gz` |
| macOS | amd64, arm64 | `.tar.gz` |
| Windows | amd64, arm64 | `.zip` |

每个压缩包包含:

- `ds2api` 可执行文件(Windows 为 `ds2api.exe`- `static/admin/`(WebUI 构建产物)
- `config.example.json``.env.example`
- `README.MD``README.en.md``LICENSE`

### 使用步骤

```bash
# 1. 下载对应平台的压缩包
# 2. 解压
tar -xzf ds2api_<tag>_linux_amd64.tar.gz
cd ds2api_<tag>_linux_amd64

# 3. 配置
cp config.example.json config.json
# 编辑 config.json

# 4. 启动
./ds2api
```

### 维护者发布步骤

1. 在 GitHub 创建并发布 Release(带 tag,如 `vX.Y.Z`2. 等待 Actions 工作流 `Release Artifacts` 完成
3. 在 Release 的 Assets 下载对应平台压缩包

---

## 二、Docker / GHCR 部署

### 2.1 基本步骤

```bash
# 拉取预编译镜像
docker pull ghcr.io/cjackhwang/ds2api:latest

# 复制环境变量模板和配置文件
cp .env.example .env
cp config.example.json config.json

# 编辑 .env(请改成你的强密码),至少设置:
#   DS2API_ADMIN_KEY=your-admin-key
# 如需修改宿主机端口,可额外设置:
#   DS2API_HOST_PORT=6011

# 启动
docker-compose up -d

# 查看日志
docker-compose logs -f
```

默认 `docker-compose.yml` 直接使用 `ghcr.io/cjackhwang/ds2api:latest`,并把宿主机 `6011` 映射到容器内的 `5001`。如果你希望直接对外暴露 `5001`,请设置 `DS2API_HOST_PORT=5001`(或者手动调整 `ports` 配置)。
Compose 模板还会默认设置 `DS2API_CONFIG_PATH=/data/config.json` 并挂载 `./config.json:/data/config.json`,优先避免 `/app` 只读带来的配置持久化问题。
镜像内会预创建 `/data` 并授权给非 root 的 `ds2api` 用户;如果你使用 bind mount 单文件,请确保宿主机 `config.json` 至少可被容器用户读取/写入,例如 `chmod 644 config.json`,否则 Linux UID/GID 不一致时仍可能出现 `open /data/config.json: permission denied`。
兼容说明:若未设置 `DS2API_CONFIG_PATH` 且运行目录是 `/app`,新版本会优先使用 `/data/config.json`;当该文件不存在但检测到历史 `/app/config.json` 时,会自动回退读取旧路径,避免升级后“配置丢失”。

如需固定版本,也可以直接拉取指定 tag:

```bash
docker pull ghcr.io/cjackhwang/ds2api:v3.0.0
```

### 2.2 更新

```bash
docker-compose up -d --build
```

### 2.3 Docker 架构说明

`Dockerfile` 提供两条构建路径:

1. **本地/开发默认路径(`runtime-from-source`)**:三阶段构建(WebUI 构建 + Go 构建 + 运行阶段)。
2. **Release 路径(`runtime-from-dist`)**:发布工作流先生成 tag 命名的 Release 压缩包,再把 Linux 产物复制成 `dist/docker-input/linux_amd64.tar.gz` / `linux_arm64.tar.gz`;Docker 构建阶段直接消费这些输入,不再重复执行 `npm build`/`go build`。

Release 路径可确保 Docker 镜像与 release 压缩包使用同一套产物,减少重复构建带来的差异。

容器内启动命令:`/usr/local/bin/ds2api`,默认暴露端口 `5001`### 2.4 开发环境

```bash
docker-compose -f docker-compose.dev.yml up
```

开发模式特性:
- 源代码挂载(修改即生效)
- `LOG_LEVEL=DEBUG`
- 不自动重启

### 2.5 健康检查

Docker Compose 已配置内置健康检查:

```yaml
healthcheck:
  test: ["CMD", "/usr/local/bin/busybox", "wget", "-qO-", "http://localhost:${PORT:-5001}/healthz"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 10s
```

### 2.6 Docker 常见排查

如果容器日志正常但面板打不开,优先检查:

1. **端口是否一致**`PORT` 改成非 `5001` 时,访问地址也要改成对应端口(如 `http://localhost:8080/admin`)。
2. **开发 compose 的 WebUI 静态文件**`docker-compose.dev.yml` 使用 `go run` 开发镜像,不会在容器内自动安装 Node.js;若仓库里没有 `static/admin``/admin` 会返回 404。可先在宿主机构建一次:`./scripts/build-webui.sh`### 2.7 Zeabur 一键部署(Dockerfile)

仓库提供 `zeabur.yaml` 模板,可在 Zeabur 上一键部署:

[![Deploy on Zeabur](https://zeabur.com/button.svg)](https://zeabur.com/templates/L4CFHP)

部署要点:

- **端口**:服务默认监听 `5001`,模板会固定设置 `PORT=5001`- **配置持久化**:模板挂载卷 `/data`,并设置 `DS2API_CONFIG_PATH=/data/config.json`;首次空卷启动时会先使用空的文件模式配置,在管理台导入配置后,会写入并持久化到该路径。
- **`open /app/config.json: permission denied`**:说明当前实例在尝试把运行时 token 持久化到只读路径(常见于镜像内 `/app`)。  
  处理建议:
  1. 显式设置可写路径:`DS2API_CONFIG_PATH=/data/config.json`(并挂载持久卷到 `/data`);  
  2. 若你使用 `DS2API_CONFIG_JSON` 启动且不需要运行时落盘,可保持环境变量模式(`DS2API_ENV_WRITEBACK` 关闭);  
  3. 最新版本中,即使持久化失败,登录/会话测试仍会继续,仅提示“token 未持久化(重启后丢失)”。
- **构建版本号**:Zeabur / 普通 `docker build` 默认不需要传 `BUILD_VERSION`;镜像会优先使用该构建参数,未提供时自动回退到仓库根目录的 `VERSION` 文件。
- **首次登录**:部署完成后访问 `/admin`,使用 Zeabur 环境变量/模板指引中的 `DS2API_ADMIN_KEY` 登录(建议首次登录后自行更换为强密码)。

#### 不使用模板手动部署

如果你不想使用 `zeabur.yaml` 一键模板,可以直接用 Zeabur 的 GitHub 集成从仓库根目录构建:

1. Fork 本仓库,或把代码推送到你自己的 GitHub 仓库。
2. 在 Zeabur Dashboard 中创建 Project,然后添加 Service,选择 GitHub/Git 仓库来源。
3. 选择仓库与分支,Root Directory 保持 `/`4. 构建方式使用 Dockerfile。Zeabur 会自动检测仓库根目录的 `Dockerfile`;不要设置 `ZBPACK_IGNORE_DOCKERFILE=true`。如果界面要求填写 Dockerfile 名称,填写 `Dockerfile`5. 在 Service 配置中添加持久卷,挂载目录填写 `/data`6. 配置环境变量:

| 变量 | 推荐值 | 说明 |
| --- | --- | --- |
| `PORT` | `5001` | 服务监听端口,需要和 Zeabur 暴露的 HTTP 端口一致。 |
| `DS2API_ADMIN_KEY` | 强随机字符串 | 管理台登录密钥,必填。 |
| `DS2API_CONFIG_PATH` | `/data/config.json` | 配置持久化路径,建议必填。 |
| `LOG_LEVEL` | `INFO` | 可选,日志级别。 |
| `DS2API_CONFIG_JSON` | 原始 JSON 或 Base64 JSON | 可选,用于用环境变量初始化配置。 |
| `DS2API_ENV_WRITEBACK` | `1` | 可选;当设置了 `DS2API_CONFIG_JSON` 且希望首次启动后写入 `/data/config.json` 时再启用。 |

7. 暴露 HTTP 端口 `5001`,健康检查路径可填 `/healthz`8. 部署完成后访问 `/admin`,用 `DS2API_ADMIN_KEY` 登录,然后在管理台导入或编辑配置。首次空卷可以没有 `/data/config.json`,服务会先启动,第一次保存时自动创建该文件。

常见问题:

- **启动日志出现 `open /data/config.json: no such file or directory`**:请确认已经部署包含“首次空卷启动”修复的版本,并重新部署最新代码。
- **出现 `open /app/config.json: permission denied`**:说明配置路径仍指向镜像内只读目录;设置持久卷 `/data`,并确认 `DS2API_CONFIG_PATH=/data/config.json`- **管理台保存后重启配置丢失**:检查 `/data` 持久卷是否已挂载到当前服务;如果使用了 `DS2API_CONFIG_JSON`,但想让管理台保存落盘,请启用 `DS2API_ENV_WRITEBACK=1`。

参考:Zeabur 官方文档的 [GitHub/Git 集成](https://zeabur.com/docs/en-US/deploy/github)、[Dockerfile 部署](https://zeabur.com/docs/zh-CN/deploy/dockerfile) 与 [Volumes](https://zeabur.com/docs/data-management/volumes)。

---

## 三、Vercel 部署

### 3.1 部署步骤

1. **Fork 仓库**到你的 GitHub 账号
2. **在 Vercel 上导入项目**
3. **配置环境变量**(最少只需设置以下一项):

| 变量 | 说明 |
| --- | --- |
| `DS2API_ADMIN_KEY` | 管理密钥(必填) |
| `DS2API_CONFIG_JSON` | 配置内容,JSON 字符串或 Base64 编码(可选,建议) |

4. **部署**

### 3.1.1 推荐填写方式(避免 `DS2API_CONFIG_JSON` 填错)

如果你想先完成一键部署,也可以先不填 `DS2API_CONFIG_JSON`,部署后进入 `/admin` 导入配置,再在「Vercel 同步」里写回环境变量。

建议先在仓库目录复制示例配置,再按实际账号填写:

```bash
cp config.example.json config.json
# 编辑 config.json
```

不要在 Vercel 面板里手写复杂 JSON,建议本地生成 Base64 后粘贴:

```bash
# 在仓库根目录执行
DS2API_CONFIG_JSON="$(base64 < config.json | tr -d '\n')"
echo "$DS2API_CONFIG_JSON"
```

如果你选择在部署前就预置配置,请在 Vercel Project Settings -> Environment Variables 配置:

```text
DS2API_ADMIN_KEY=请替换为强密码
DS2API_CONFIG_JSON=上一步生成的一整行 Base64
```

可选但推荐(用于 WebUI 一键同步 Vercel 配置):

```text
VERCEL_TOKEN=你的 Vercel Token
VERCEL_PROJECT_ID=prj_xxxxxxxxxxxx
VERCEL_TEAM_ID=team_xxxxxxxxxxxx   # 个人账号可留空
```

### 3.2 可选环境变量

| 变量 | 说明 | 默认值 |
| --- | --- | --- |
| `DS2API_ACCOUNT_MAX_INFLIGHT` | 每账号并发上限 | `2` |
| `DS2API_ACCOUNT_MAX_QUEUE` | 等待队列上限 | `recommended_concurrency` |
| `DS2API_GLOBAL_MAX_INFLIGHT` | 全局并发上限 | `recommended_concurrency` |
| `DS2API_ENV_WRITEBACK` | 检测到 `DS2API_CONFIG_JSON` 时自动写入 `DS2API_CONFIG_PATH`,并在成功后转为文件模式(`1/true/yes/on`) | 关闭 |
| `DS2API_VERCEL_INTERNAL_SECRET` | 混合流式内部鉴权 | 回退用 `DS2API_ADMIN_KEY` |
| `DS2API_VERCEL_STREAM_LEASE_TTL_SECONDS` | 流式 lease TTL | `900` |
| `DS2API_RAW_STREAM_SAMPLE_ROOT` | raw stream 样本保存/读取根目录 | `tests/raw_stream_samples` |
| `VERCEL_TOKEN` | Vercel 同步 token | — |
| `VERCEL_PROJECT_ID` | Vercel 项目 ID | — |
| `VERCEL_TEAM_ID` | Vercel 团队 ID | — |
| `DS2API_CHAT_HISTORY_PATH` | Chat history 存储路径(Vercel 上必须设为 `/tmp/chat_history.json`,否则因文件系统只读而不可用) | `data/chat_history.json` |
| `DS2API_VERCEL_PROTECTION_BYPASS` | 部署保护绕过密钥(内部 Node→Go 调用) | — |

### 3.3 运行时行为配置(通过 Admin API 设置)

部分运行时行为无法通过环境变量直接配置,需要在部署后通过 Admin API 设置,例如:

- **自动删除会话模式** (`auto_delete.mode`):支持 `none` / `single` / `all`,默认为 `none`。可通过 `PUT /admin/settings` 更新。
- **每账号并发上限** (`account_max_inflight`):环境变量已支持,但也可通过 Admin API 热更新。
- **全局并发上限** (`global_max_inflight`):同上。

详细说明参见 [API.md](../API.md#admin-接口) 中 `/admin/settings` 部分。

### 3.4 Vercel 架构说明

```text
请求 ─────┐


     vercel.json 路由规则

    ┌─────┴─────┐
    │           │
    ▼           ▼
api/index.go  api/chat-stream.js
(Go Runtime)  (Node Runtime)
```

- **入口文件**`api/index.go`(Serverless Go)
- **流式入口**`api/chat-stream.js`(Node Runtime,保证实时 SSE)
- **路由重写**`vercel.json`
- **构建命令**`npm ci --prefix webui && npm run build --prefix webui`(自动执行)

#### 流式处理链路

由于 Vercel Go Runtime 存在平台层响应缓冲,本项目在 Vercel 上采用"**Go prepare + Node stream**"的混合链路:

1. `api/chat-stream.js` 收到 `/v1/chat/completions` 请求
2. Node 调用 Go 内部 prepare 接口(`?__stream_prepare=1`),获取会话 ID、PoW、token 等
3. Go prepare 创建 stream lease,锁定账号
4. Node 直连 DeepSeek 上游,实时流式转发 SSE 给客户端(含 OpenAI chunk 封装与 tools 防泄漏筛分)
5. 流结束后 Node 调用 Go release 接口(`?__stream_release=1`),释放账号

> 该适配**仅在 Vercel 环境生效**;本地与 Docker 仍走纯 Go 链路。

#### 非流式回退与 Tool Call 处理

- `api/chat-stream.js` 仅对非流式请求回退到 Go 入口(`?__go=1`- 流式请求(包括带 `tools`)走 Node 路径,并执行与 Go 对齐的 tool-call 防泄漏处理
- Node 流式路径同时对齐 Go 的终结态语义:空可见输出会返回同形状错误 SSE,空 `content_filter` 会返回 `content_filter` 错误
- WebUI 的"非流式测试"直接请求 `?__go=1`,避免 Node 中转造成长请求超时

#### 函数时长

`vercel.json` 已将 `api/chat-stream.js``api/index.go``maxDuration` 设为 `300`(受 Vercel 套餐上限约束)。

### 3.5 Vercel 常见报错排查

#### Go 构建失败

```text
Error: Command failed: go build -ldflags -s -w -o .../bootstrap ...
```

**原因**:Vercel 项目的 Go 构建参数配置不正确(`-ldflags` 没有作为一个整体字符串传递)。

**解决**1. 进入 Vercel Project Settings → Build and Development Settings
2. **清空**自定义 Go Build Flags / Build Command(推荐)
3. 若必须设置 ldflags,使用 `-ldflags="-s -w"`(保证它是一个参数)
4. 确认仓库 `go.mod` 为受支持版本(当前为 `go 1.26.0`5. 重新部署(建议清缓存后 Redeploy)

#### Internal 包导入错误

```text
use of internal package ds2api/internal/server not allowed
```

**原因**:Vercel Go 入口文件直接 `import internal/...`**解决**:当前仓库已通过公开桥接包 `app` 解决:`api/index.go``ds2api/app``internal/server`#### 输出目录错误

```text
No Output Directory named "public" found after the Build completed.
```

**解决**:当前仓库使用 `static` 作为输出目录(`vercel.json``"outputDirectory": "static"`)。若你在项目设置里手动改过 Output Directory,请设为 `static` 或清空让仓库配置生效。

#### 部署保护拦截

如果接口返回 Vercel HTML 页面 `Authentication Required`- **方案 A**:关闭该部署/环境的 Deployment Protection(推荐用于公开 API)
- **方案 B**:请求中添加 `x-vercel-protection-bypass`- **方案 C**:设置 `VERCEL_AUTOMATION_BYPASS_SECRET`(或 `DS2API_VERCEL_PROTECTION_BYPASS`),仅影响内部 Node→Go 调用

#### Chat History 不可用(read-only file system)

```text
create chat history dir: mkdir /var/task/data: read-only file system
```

**原因**:Vercel Serverless 函数的文件系统(`/var/task`)为只读,chat history 尝试在该路径下创建目录失败。

**解决**:在 Vercel Project Settings → Environment Variables 中添加:

```text
DS2API_CHAT_HISTORY_PATH=/tmp/chat_history.json
```

`/tmp` 是 Vercel Serverless 环境中唯一可写的目录。数据在函数冷启动之间不会持久化(ephemeral),但在单个实例生命周期内功能正常。

### 3.6 仓库不提交构建产物

- `static/admin` 目录不在 Git 中
- Vercel / Docker 构建阶段自动生成 WebUI 静态文件

---

## 四、本地源码运行

### 4.1 基本步骤

```bash
# 克隆仓库
git clone https://github.com/CJackHwang/ds2api.git
cd ds2api

# 复制并编辑配置
cp config.example.json config.json
# 使用你喜欢的编辑器打开 config.json,填入:
#   - keys: 你的 API 访问密钥
#   - accounts: DeepSeek 账号(email 或 mobile + password)

# 启动服务
go run ./cmd/ds2api
```

默认本地访问地址是 `http://127.0.0.1:5001`;服务实际绑定 `0.0.0.0:5001`,可通过 `PORT` 环境变量覆盖。

### 4.2 WebUI 构建

本地首次启动时,若 `static/admin/` 不存在,服务会自动尝试构建 WebUI(需要 Node.js/npm;缺依赖时会先执行 `npm ci`,再执行 `npm run build -- --outDir static/admin --emptyOutDir`)。

你也可以手动构建:

```bash
./scripts/build-webui.sh
```

或手动执行:

```bash
cd webui
npm ci
npm run build
# 产物输出到 static/admin/
```

通过环境变量控制自动构建行为:

```bash
# 强制关闭自动构建
DS2API_AUTO_BUILD_WEBUI=false go run ./cmd/ds2api

# 强制开启自动构建
DS2API_AUTO_BUILD_WEBUI=true go run ./cmd/ds2api
```

### 4.3 编译为二进制文件

```bash
go build -o ds2api ./cmd/ds2api
./ds2api
```

---

## 五、反向代理(Nginx)

如果在 Nginx 后部署,**必须关闭缓冲**以保证 SSE 流式响应正常工作:

```nginx
location / {
    proxy_pass http://127.0.0.1:5001;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;
    proxy_cache off;
    chunked_transfer_encoding on;
    tcp_nodelay on;
}
```

如果需要 HTTPS,可以在 Nginx 层配置 SSL 证书:

```nginx
server {
    listen 443 ssl;
    server_name api.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:5001;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
        proxy_cache off;
        chunked_transfer_encoding on;
        tcp_nodelay on;
    }
}
```

---

## 六、Linux systemd 服务化

### 6.1 安装

```bash
# 将编译好的二进制文件和相关文件复制到目标目录
sudo mkdir -p /opt/ds2api
sudo cp ds2api config.json /opt/ds2api/
sudo cp -r static/admin /opt/ds2api/static/admin
```

### 6.2 创建 systemd 服务文件

```ini
# /etc/systemd/system/ds2api.service

[Unit]
Description=DS2API (Go)
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/ds2api
Environment=PORT=5001
Environment=DS2API_CONFIG_PATH=/opt/ds2api/config.json
Environment=DS2API_ADMIN_KEY=your-admin-key-here
ExecStart=/opt/ds2api/ds2api
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
```

### 6.3 常用命令

```bash
# 加载服务配置
sudo systemctl daemon-reload

# 设置开机自启
sudo systemctl enable ds2api

# 启动服务
sudo systemctl start ds2api

# 查看状态
sudo systemctl status ds2api

# 查看日志
sudo journalctl -u ds2api -f

# 重启服务
sudo systemctl restart ds2api

# 停止服务
sudo systemctl stop ds2api
```

---

## 七、部署后检查

无论使用哪种部署方式,启动后建议依次检查:

```bash
# 1. 存活探针
curl -s http://127.0.0.1:5001/healthz
# 预期: {"status":"ok"}

# 2. 就绪探针
curl -s http://127.0.0.1:5001/readyz
# 预期: {"status":"ready"}

# 3. 模型列表
curl -s http://127.0.0.1:5001/v1/models
# 预期: {"object":"list","data":[...]}(包含 `*-nothinking` 变体)

# 4. 管理台页面(如果已构建 WebUI)
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:5001/admin
# 预期: 200

# 5. 测试 API 调用
curl http://127.0.0.1:5001/v1/chat/completions \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hello"}]}'
```

---

## 八、发布前进行本地回归

建议在发布前执行完整的端到端测试集(使用真实账号):

```bash
./tests/scripts/run-live.sh
```

可自定义参数:

```bash
go run ./cmd/ds2api-tests \
  --config config.json \
  --admin-key admin \
  --out artifacts/testsuite \
  --timeout 120 \
  --retries 2
```

测试集自动执行内容:

- ✅ 语法/构建/单测 preflight
- ✅ 隔离副本配置启动服务(不污染原始 `config.json`- ✅ 真实调用场景验证(OpenAI/Claude/Admin/并发/toolcall/流式)
- ✅ 全量请求与响应日志落盘(用于故障复盘)

详细测试集说明参阅 [TESTING.md](TESTING.md)。PR 前的固定本地门禁以 [TESTING.md](TESTING.md#pr-门禁--pr-gates) 为准。