162 lines
7.9 KiB
Markdown
162 lines
7.9 KiB
Markdown
# 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 写入路径
|
||
```
|