Files
cdxs/docs/sync-server.md
T

439 lines
13 KiB
Markdown
Raw 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.
# 配置同步/服务端
本文说明 `cdxs` 当前配置同步和同步服务端功能的实现方式、调用机制和运行过程。
## 功能范围
配置同步/服务端主要覆盖以下能力:
- 启动一个最小 HTTP 同步服务。
- 在服务端配置文件中添加或更新登录用户。
- 客户端使用用户名和密码登录同步服务。
- 客户端把本地可移植配置推送到服务端。
- 客户端从服务端拉取可移植配置并覆盖本地状态。
- 查看当前客户端同步配置状态。
- 使用 Docker 或 docker-compose 运行同步服务。
当前同步的数据只包含 `cdxs.toml` 中的可移植状态:
- `meta`
- `accounts`
- `homes`
不会同步客户端本地的 `sync` 配置,也不会把服务端的 `server.users``server.sessions` 下发给客户端。
## 核心文件
- `src/main.rs`CLI 入口,负责把 `server``sync` 命令分发到具体模块。
- `src/cli.rs`:定义同步服务端和客户端命令参数。
- `src/server.rs`:HTTP 同步服务端实现,包含用户管理、登录、鉴权、状态读写。
- `src/sync_client.rs`:客户端同步逻辑,包含登录、拉取、推送和状态查看。
- `src/config_store.rs``cdxs.toml` 数据模型、加载、保存和默认 home 初始化。
- `Dockerfile`:构建并运行 `cdxs server run` 的容器镜像。
- `docker-compose.yml`:使用具名 volume 持久化 `/data/cdxs.toml` 并暴露 `8765` 端口。
## 数据保存方式
同步功能复用 `Store` 结构,也就是 `cdxs.toml` 的配置模型。
客户端默认读取当前 `CODEX_HOME` 下的配置:
```text
<CODEX_HOME>\cdxs.toml
```
如果没有设置 `CODEX_HOME`,默认使用:
```text
%USERPROFILE%\.codex\cdxs.toml
```
服务端有两种数据路径:
- 如果 `server run --data <路径>` 被指定,服务端直接使用这个路径保存数据。
- 如果未指定 `--data`,服务端使用当前 `CODEX_HOME` 下的 `cdxs.toml`
`server user add` 也使用同一套路径规则,因此添加用户和启动服务必须指向同一个数据文件,才能让服务端读取到该用户。
## Store 中的相关字段
`Store` 中和同步相关的字段包括:
- `meta`:当前账号、当前 home 等元数据。
- `accounts`:账号列表。
- `homes`:受管理的 `CODEX_HOME` 列表。
- `server`:服务端用户和 bearer session,仅服务端使用。
- `sync`:客户端保存的服务端地址、用户名、token、最近拉取/推送时间。
服务端用户保存在:
```text
Store.server.users
```
服务端登录 session 保存在:
```text
Store.server.sessions
```
客户端同步配置保存在:
```text
Store.sync
```
## 服务端用户与 Token 原理
添加用户时,`server::add_user` 会生成随机 salt,并把密码保存为哈希值。
密码哈希方式:
```text
sha256(salt + ":" + password)
```
登录成功后,服务端生成一个随机 bearer token,并只保存 token 的哈希值。
token 哈希方式:
```text
sha256("cdxs-token:" + token)
```
原始 token 只在 `/v1/login` 响应中返回一次,由客户端保存到本地 `Store.sync.token`。后续 `pull``push` 使用:
```http
Authorization: Bearer <token>
```
服务端收到请求后,对请求中的 token 重新计算哈希,并检查是否存在于 `Store.server.sessions`
```mermaid
flowchart TD
A[server user add] --> B[生成 salt]
B --> C[计算 password_hash]
C --> D[写入 Store.server.users]
E[sync login] --> F[校验 username/password]
F --> G[生成 bearer token]
G --> H[保存 token_hash 到 Store.server.sessions]
H --> I[原始 token 返回给客户端]
J[sync pull/push] --> K[Authorization Bearer token]
K --> L[服务端计算 token_hash]
L --> M{sessions 中是否存在}
M -->|是| N[允许访问 /v1/state]
M -->|否| O[返回 401]
```
## HTTP API
同步服务端使用 `axum` 提供三个接口:
- `GET /health`:健康检查,返回 `ok`
- `POST /v1/login`:使用用户名和密码登录,返回 bearer token。
- `GET /v1/state`:鉴权后返回服务端保存的可移植状态。
- `PUT /v1/state`:鉴权后用客户端提交的可移植状态替换服务端状态。
`/v1/state` 的读写都会经过 `authorize` 鉴权。
服务端返回给客户端前会调用 `sanitized_for_client`,把以下字段清空:
- `server`
- `sync`
这样客户端不会拿到服务端用户、密码哈希、已签发 session,也不会拿到服务端自己的同步配置。
## 命令调用机制
同步相关命令由 `src/cli.rs` 定义,再由 `src/main.rs` 分发。
```mermaid
flowchart TD
A[用户执行 cdxs 命令] --> B[Cli::parse]
B --> C[src/main.rs match Commands]
C --> D{命令类型}
D -->|cdxs server run| E[server::run_server]
D -->|cdxs server user add| F[server::add_user]
D -->|cdxs sync login| G[sync_client::login]
D -->|cdxs sync pull| H[sync_client::pull]
D -->|cdxs sync push| I[sync_client::push]
D -->|cdxs sync status| J[sync_client::status]
```
## 添加服务端用户运行过程
命令:
```powershell
cdxs server user add <username> --password <password>
```
可选指定服务端数据文件:
```powershell
cdxs server user add <username> --password <password> --data <cdxs.toml路径>
```
运行过程:
1. 调用 `resolve_data_path` 解析服务端数据文件路径。
2. 调用 `Store::load_from_path` 加载服务端 `cdxs.toml`,文件不存在时生成默认 store。
3. 校验用户名和密码不能为空。
4. 生成随机 salt。
5. 使用 salt 和密码计算 `password_hash`
6. 如果用户已存在,替换原用户记录。
7. 如果用户不存在,追加到 `Store.server.users`
8. 调用 `Store::save_to_path` 保存服务端配置。
```mermaid
sequenceDiagram
participant U as 用户
participant M as main.rs
participant S as server.rs
participant C as config_store.rs
U->>M: cdxs server user add alice --password ***
M->>S: add_user(data, username, password)
S->>S: resolve_data_path
S->>C: Store::load_from_path
C-->>S: Store
S->>S: random_token 生成 salt
S->>S: hash_secret
S->>S: upsert ServerUser
S->>C: save_to_path
C-->>U: 用户已添加/更新
```
## 启动服务端运行过程
命令:
```powershell
cdxs server run
```
默认监听:
```text
127.0.0.1:8765
```
可选参数:
```powershell
cdxs server run --bind 0.0.0.0:8765
cdxs server run --data <cdxs.toml路径>
```
运行过程:
1. 调用 `resolve_data_path` 解析服务端数据文件和默认 home。
2. 解析 `--bind``SocketAddr`
3. 创建 `AppState`,保存数据路径、默认 home 和一个异步 `Mutex`
4. 注册 `/health``/v1/login``/v1/state` 路由。
5. 绑定 TCP listener。
6. 调用 `axum::serve` 持续处理请求。
服务端在每个会读写配置文件的请求中都会先获取 `AppState.lock`。这个锁用于串行化文件读写,避免并发请求同时读写同一个 `cdxs.toml`
```mermaid
flowchart TD
A[cdxs server run] --> B[resolve_data_path]
B --> C[解析 bind 地址]
C --> D[创建 AppState]
D --> E[注册 axum Router]
E --> F[绑定 TcpListener]
F --> G[axum::serve]
G --> H[处理 /health /v1/login /v1/state]
```
## 客户端登录运行过程
命令:
```powershell
cdxs sync login --server http://127.0.0.1:8765 --user alice --password <password>
```
运行过程:
1. 客户端规范化 server URL,去掉末尾 `/`
2.`<server>/v1/login` 发送 JSON 请求。
3. 服务端加载 `cdxs.toml`
4. 服务端按用户名查找 `Store.server.users`
5. 服务端校验密码哈希。
6. 登录成功后生成 bearer token。
7. 服务端保存 token 哈希到 `Store.server.sessions`
8. 客户端解析响应中的原始 token。
9. 客户端加载本地 `cdxs.toml`
10. 客户端写入 `Store.sync.server_url``Store.sync.username``Store.sync.token`
11. 客户端保存本地配置。
```mermaid
sequenceDiagram
participant U as 用户
participant C as sync_client.rs
participant H as HTTP
participant S as server.rs
participant FS as cdxs.toml
U->>C: cdxs sync login
C->>H: POST /v1/login username/password
H->>S: login_handler
S->>FS: 加载服务端 Store
S->>S: 校验 password_hash
S->>S: 生成 bearer token 并保存 token_hash
S->>FS: 保存服务端 Store
S-->>C: token
C->>FS: 加载本地 Store
C->>FS: 保存 sync.server_url / username / token
C-->>U: sync login 成功
```
## 拉取配置运行过程
命令:
```powershell
cdxs sync pull
```
运行过程:
1. 加载本地 `cdxs.toml`
2.`Store.sync` 读取 server URL 和 token。
3. 如果未登录或缺少 token,返回错误。
4.`<server>/v1/state` 发送 `GET` 请求,并附带 `Authorization` header。
5. 服务端校验 bearer token。
6. 服务端加载服务端 `Store`
7. 服务端清空 `server``sync` 字段后返回 JSON。
8. 客户端解析远端 `Store`
9. 客户端用远端 `meta``accounts``homes` 覆盖本地对应字段。
10. 客户端保留本地 `sync` 配置,并更新 `last_pull_at`
11. 保存本地 `cdxs.toml`
```mermaid
flowchart TD
A[cdxs sync pull] --> B[加载本地 Store]
B --> C[sync_endpoint 读取 server/token]
C --> D[GET /v1/state]
D --> E[服务端 authorize]
E --> F[服务端 sanitized_for_client]
F --> G[返回远端 Store]
G --> H[覆盖本地 meta/accounts/homes]
H --> I[更新 last_pull_at]
I --> J[保存本地 cdxs.toml]
```
## 推送配置运行过程
命令:
```powershell
cdxs sync push
```
运行过程:
1. 加载本地 `cdxs.toml`
2.`Store.sync` 读取 server URL 和 token。
3. 克隆本地 `Store` 作为上传 payload。
4. 上传前把 payload 中的 `server``sync` 字段清空。
5.`<server>/v1/state` 发送 `PUT` 请求,并附带 `Authorization` header。
6. 服务端校验 bearer token。
7. 服务端加载原服务端 `Store`
8. 服务端只用 payload 中的 `meta``accounts``homes` 替换服务端状态。
9. 服务端把 `sync` 置为默认值,保留原服务端的 `server.users``server.sessions`
10. 服务端保存配置。
11. 客户端更新本地 `last_push_at`
12. 客户端保存本地 `cdxs.toml`
```mermaid
sequenceDiagram
participant C as sync_client.rs
participant S as server.rs
participant FS as cdxs.toml
C->>FS: 加载本地 Store
C->>C: payload.server/default, payload.sync/default
C->>S: PUT /v1/state + Bearer token
S->>FS: 加载服务端 Store
S->>S: authorize
S->>S: 替换 meta/accounts/homes
S->>FS: 保存服务端 Store
S-->>C: sanitized Store
C->>FS: 更新 last_push_at 并保存
```
## 查看同步状态
命令:
```powershell
cdxs sync status
```
输出内容来自本地 `Store.sync`
- `server`
- `user`
- `token`
- `last_pull_at`
- `last_push_at`
如果本地保存了 token,输出只显示:
```text
<stored>
```
不会打印 token 明文。
## Docker 运行方式
`Dockerfile` 使用两阶段构建:
1. 使用 `rust:1-bookworm` 构建 release 版本 `cdxs`
2. 使用 `debian:bookworm-slim` 作为运行镜像。
3. 安装 `ca-certificates`
4.`cdxs` 复制到 `/usr/local/bin/cdxs`
5. 暴露 `8765` 端口。
6. 声明 `/data` volume。
容器默认命令:
```text
cdxs server run --bind 0.0.0.0:8765 --data /data/cdxs.toml
```
`docker-compose.yml` 会:
- 构建当前目录镜像。
- 将容器命名为 `cdxs-server`
- 设置 `restart: unless-stopped`
- 映射宿主机 `8765` 到容器 `8765`
- 使用具名 volume `cdxs-data` 挂载到 `/data`
## 写入安全机制
服务端和客户端保存 `cdxs.toml` 都调用 `Store::save``Store::save_to_path`
保存时会:
1. 如果目标文件已存在,先备份到对应 home 下的 `cdxs-backups`
2.`Store` 序列化为 TOML。
3. 写入同目录临时文件。
4. 使用 rename 替换目标文件。
服务端请求还会使用 `tokio::sync::Mutex` 串行化文件读写,降低并发请求导致状态覆盖或文件损坏的风险。
## 当前实现边界
- 同步没有字段级合并或冲突解决,`pull` 会用远端 `meta/accounts/homes` 覆盖本地,`push` 会用本地 `meta/accounts/homes` 覆盖服务端。
- bearer session 当前没有过期时间和撤销命令。
- 服务端用户只有添加/更新命令,没有删除、列表或改密命令。
- 服务端没有多租户隔离;所有通过鉴权的用户访问同一份服务端状态。
- HTTP 服务没有在代码中直接配置 TLS,需要由外部反向代理或部署环境处理。
- `sync push` 不上传客户端本地 `sync` token,也不上传客户端本地 `server` 配置。
- `sync pull` 不会写入远端 `server` 或远端 `sync` 字段,只更新本地可移植状态并保留本地同步登录信息。