Files

162 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dsh-sync — DeepSeek Harness 团队同步服务端
为 DeepSeek Harness 提供 **团队设置 / 用户设置 / 插件分发** 的私有同步服务。
架构设计见 [ARCHITECTURE.md](ARCHITECTURE.md),移动端见 [MOBILE_APP.md](MOBILE_APP.md)。
**已完成并通过 88 个 pytest 用例(PostgreSQL 16)+ 真实 HTTP 冒烟验证。**
## 目录结构
```
server/
dsh_sync/
main.py # 应用组装(配置 / 中间件 / 路由注册)
config.py # 环境变量配置(含 DSH_DATABASE_URL)
db.py # PostgreSQL schema + 审计写入(psycopg 3)
auth.py # token 签发 / 认证 / 请求上下文
storage.py # 制品与 APK 的路径解析
ratelimit.py # 邀请码兑换限流
routers/ # 按资源拆分的路由(pages/connect/tokens/users/settings/plugins/audit)
static/ # mobile.html(PWA)、apk.html(安装页)、sw.js
tests/ # pytest 套件(88 例,跑在专用测试库 postgres_test 上)
client/
dsh_sync_client.py # 参考客户端(移植进 harness 用)
android/ # 安卓 WebView 壳工程 + build.sh
data/dist/ # APK 构建产物(dsh-sync.apk + 元数据)
Dockerfile / docker-compose.yml / Caddyfile
```
## 安卓 App
```bash
bash android/build.sh # 需要 JDK 17 + Android SDK 34 + Gradle 8.7
```
构建后访问 `http://<服务器>:8020/apk` 下载安装(免登录页面,含二维码与安装步骤)。
## 本地运行(开发)
需要一份 PostgreSQL(本地服务或容器均可):
```bash
# 一次性测试库(二选一):
# a) Docker:docker run -d --rm --name dsh-test-pg -p 15433:5432 \
# -e POSTGRES_USER=dsh -e POSTGRES_PASSWORD=dsh -e POSTGRES_DB=dsh_test postgres:16-alpine
# b) 任意已有 PG:建一个空库即可
cd server
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt -r requirements-dev.txt # Windows
# Linux/macOS: .venv/bin/pip install ...
# 测试。默认用生产实例上的独立库 postgres_test(不存在会自动建)。
# 注意:测试库每个用例都会 DROP SCHEMA 重建,所以 conftest 会在
# DSH_TEST_DATABASE_URL 指向生产库时直接拒绝运行。
.venv/Scripts/python -m pytest -q
DSH_DATABASE_URL="postgresql://dsh:secret@127.0.0.1:5432/dsh" \
DSH_BOOTSTRAP_ADMIN_TOKEN=<随机串> DSH_INVITE_CODE=<邀请码> \
.venv/Scripts/python -m uvicorn dsh_sync.main:app --reload --port 8000
```
> 注意 DSN 里用 `127.0.0.1` 而不是 `localhost`:部分 Windows 机器 `localhost` 先解析到 IPv6,
> 若 PG 未监听 IPv6 会导致每次连接白等一个 connect_timeout。
## Docker 部署
```bash
cp .env.example .env # 填入 POSTGRES_PASSWORD / DSH_BOOTSTRAP_ADMIN_TOKEN / DSH_INVITE_CODE / DSH_SYNC_DOMAIN
docker compose up -d --build
```
compose 会启动三个服务:`postgres:16`(数据存 `pg_data` 卷,带健康检查)→ `dsh-sync`(等库就绪后启动)→ `caddy`。
- 公网部署:Caddy 按 `DSH_SYNC_DOMAIN` 自动签发 TLS 证书。
- 更推荐**内网 / Tailscale 部署**(见 Caddyfile 中 `:8080` 注释块),不暴露公网。
- 备份:`docker compose exec postgres pg_dump -U dsh dsh > backup.sql` + 打包 `./data/`(插件制品)。
## 环境变量
| 变量 | 说明 | 默认 |
|---|---|---|
| `DSH_DATABASE_URL` | PostgreSQL 连接串(**必填**),建议用 `127.0.0.1` 而非 `localhost` | 无 |
| `DSH_DATA_DIR` | 插件制品存储目录 | `./data`(容器内 `/data`) |
| `DSH_BOOTSTRAP_ADMIN_TOKEN` | 首次启动时为 admin 用户创建的固定 token(幂等) | 无则跳过 |
| `DSH_ADMIN_USERNAME` | admin 用户名 | `admin` |
| `DSH_INVITE_CODE` | 团队邀请码,成员自助注册用;不设则关闭邀请注册 | 无 |
| `DSH_MAX_PLUGIN_MB` | 插件制品上传上限(MB) | `20` |
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | compose 创建 PG 实例用 | `dsh` / 必填 / `dsh` |
## API 一览(Bearer token 认证)
```
GET /healthz # 探活
POST /v1/tokens # 邀请码换 token(仅新用户)
POST /v1/tokens/self # 给自己加签 token
GET /v1/tokens # 列自己的 token
DELETE /v1/tokens/{token_hash} # 撤销 token(本人或 admin)
POST /v1/users # admin 直接建用户发 token
GET /v1/me # 身份信息
GET /v1/connect/info # admin:扫码连接数据(内网地址候选 + 邀请码)
GET /v1/connect/qr?host=x.x.x.x # admin:连接二维码(SVG,深链 /app#invite=…)
GET /v1/settings # team ∪ user 合并视图,带 ETag(304 缓存)
PUT /v1/settings/user/{key} # 写自己的设置,body {value, base_version?}
PUT /v1/settings/team/{key} # admin 写团队设置
GET /v1/plugins?harness_version=x.y # 插件列表(按最低 harness 版本过滤)
GET /v1/plugins/{id}/{version} # 插件详情(manifest + sha256)
GET /v1/plugins/{id}/{version}/download # 下载制品,响应头 X-Sha256
POST /v1/plugins/{id} # admin 上传新版本(tar.gz,内含 manifest.json)
DELETE /v1/plugins/{id}/{version} # admin 删除版本
GET /v1/audit # admin 审计日志
```
错误语义:401 未认证 / 403 无权限 / 404 不存在 / 409 版本冲突或重复 / 400 参数非法。
### manifest.json 格式(打包在 tar.gz 根目录或单一顶层目录内)
```json
{
"id": "demo",
"version": "1.0.0",
"name": "Demo Plugin",
"description": "…",
"channel": "stable",
"min_harness_version": "0.0.0",
"files": ["main.py"]
}
```
## 客户端(参考实现)
`client/dsh_sync_client.py` 演示了完整协议,可整体移植进 harness 的 `sync` 模块:
```bash
python client/dsh_sync_client.py login http://127.0.0.1:8000 <邀请码> --username alice
python client/dsh_sync_client.py pull
python client/dsh_sync_client.py push theme '"dark"'
python client/dsh_sync_client.py install demo --harness-version 1.0.0
python client/dsh_sync_client.py status
```
要点(与 ARCHITECTURE.md §6/§7 对应):
- 配置层叠:`defaults < team < user < 本地覆盖`;本地覆盖永不上传。
- `pull` 带 ETag,304 或服务器不可达时回退本地缓存并标记 `stale`。
- `push` 携带 `base_version`,409 冲突时提示重新拉取,不静默覆盖。
- `install` 强校验 sha256 与大小,防 zip-slip,临时目录解压后原子替换。
- **API Key 永远不要放进来**:每人自己的 DeepSeek Key 存本地(keychain/环境变量);团队统一管 Key 请前置 LLM 网关(one-api / LiteLLM)。
## 已验证
- `server/tests`:88 个用例(认证、token 撤销、设置合并/冲突/权限、插件上传下载/哈希/路径穿越/版本过滤/渠道优先、扫码连接 info/qr/权限/HTTPS 场景、备份恢复、APK 分发、客户端协议、平台矩阵、审计)在 **PostgreSQL 16** 上全部通过。
- 真实 uvicorn + PostgreSQL 启动 + curl 全链路冒烟:健康检查、admin 认证、团队设置写入、邀请码注册、插件上传、成员下载,sha256 两侧一致。
- 扫码连接(PWA「设置 → 连接手机」):二维码深链 + 候选地址切换 + `#invite=` 自动预填,已在 390×844 手机视口实测(截图 `dsh-app-connect-qr.png`)。
## 模型探测(I-01,最小闭环)
`tools/probe_models.py` 只读 `~/.dsh/settings.yaml`,对每组模型跑基线/思考/视觉三组探测,
报告写 `tools/probe-reports/`(本地留存,不进仓):
```bash
python tools/probe_models.py --all --compare # 全量探测 + 写报告 + 与上次对比
python tools/probe_models.py --check-readonly # 自证无 settings 写入路径
```