13 KiB
配置同步/服务端
本文说明 cdxs 当前配置同步和同步服务端功能的实现方式、调用机制和运行过程。
功能范围
配置同步/服务端主要覆盖以下能力:
- 启动一个最小 HTTP 同步服务。
- 在服务端配置文件中添加或更新登录用户。
- 客户端使用用户名和密码登录同步服务。
- 客户端把本地可移植配置推送到服务端。
- 客户端从服务端拉取可移植配置并覆盖本地状态。
- 查看当前客户端同步配置状态。
- 使用 Docker 或 docker-compose 运行同步服务。
当前同步的数据只包含 cdxs.toml 中的可移植状态:
metaaccountshomes
不会同步客户端本地的 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 下的配置:
<CODEX_HOME>\cdxs.toml
如果没有设置 CODEX_HOME,默认使用:
%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、最近拉取/推送时间。
服务端用户保存在:
Store.server.users
服务端登录 session 保存在:
Store.server.sessions
客户端同步配置保存在:
Store.sync
服务端用户与 Token 原理
添加用户时,server::add_user 会生成随机 salt,并把密码保存为哈希值。
密码哈希方式:
sha256(salt + ":" + password)
登录成功后,服务端生成一个随机 bearer token,并只保存 token 的哈希值。
token 哈希方式:
sha256("cdxs-token:" + token)
原始 token 只在 /v1/login 响应中返回一次,由客户端保存到本地 Store.sync.token。后续 pull 和 push 使用:
Authorization: Bearer <token>
服务端收到请求后,对请求中的 token 重新计算哈希,并检查是否存在于 Store.server.sessions。
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,把以下字段清空:
serversync
这样客户端不会拿到服务端用户、密码哈希、已签发 session,也不会拿到服务端自己的同步配置。
命令调用机制
同步相关命令由 src/cli.rs 定义,再由 src/main.rs 分发。
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]
添加服务端用户运行过程
命令:
cdxs server user add <username> --password <password>
可选指定服务端数据文件:
cdxs server user add <username> --password <password> --data <cdxs.toml路径>
运行过程:
- 调用
resolve_data_path解析服务端数据文件路径。 - 调用
Store::load_from_path加载服务端cdxs.toml,文件不存在时生成默认 store。 - 校验用户名和密码不能为空。
- 生成随机 salt。
- 使用 salt 和密码计算
password_hash。 - 如果用户已存在,替换原用户记录。
- 如果用户不存在,追加到
Store.server.users。 - 调用
Store::save_to_path保存服务端配置。
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: 用户已添加/更新
启动服务端运行过程
命令:
cdxs server run
默认监听:
127.0.0.1:8765
可选参数:
cdxs server run --bind 0.0.0.0:8765
cdxs server run --data <cdxs.toml路径>
运行过程:
- 调用
resolve_data_path解析服务端数据文件和默认 home。 - 解析
--bind为SocketAddr。 - 创建
AppState,保存数据路径、默认 home 和一个异步Mutex。 - 注册
/health、/v1/login、/v1/state路由。 - 绑定 TCP listener。
- 调用
axum::serve持续处理请求。
服务端在每个会读写配置文件的请求中都会先获取 AppState.lock。这个锁用于串行化文件读写,避免并发请求同时读写同一个 cdxs.toml。
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]
客户端登录运行过程
命令:
cdxs sync login --server http://127.0.0.1:8765 --user alice --password <password>
运行过程:
- 客户端规范化 server URL,去掉末尾
/。 - 向
<server>/v1/login发送 JSON 请求。 - 服务端加载
cdxs.toml。 - 服务端按用户名查找
Store.server.users。 - 服务端校验密码哈希。
- 登录成功后生成 bearer token。
- 服务端保存 token 哈希到
Store.server.sessions。 - 客户端解析响应中的原始 token。
- 客户端加载本地
cdxs.toml。 - 客户端写入
Store.sync.server_url、Store.sync.username、Store.sync.token。 - 客户端保存本地配置。
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 成功
拉取配置运行过程
命令:
cdxs sync pull
运行过程:
- 加载本地
cdxs.toml。 - 从
Store.sync读取 server URL 和 token。 - 如果未登录或缺少 token,返回错误。
- 向
<server>/v1/state发送GET请求,并附带Authorizationheader。 - 服务端校验 bearer token。
- 服务端加载服务端
Store。 - 服务端清空
server和sync字段后返回 JSON。 - 客户端解析远端
Store。 - 客户端用远端
meta、accounts、homes覆盖本地对应字段。 - 客户端保留本地
sync配置,并更新last_pull_at。 - 保存本地
cdxs.toml。
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]
推送配置运行过程
命令:
cdxs sync push
运行过程:
- 加载本地
cdxs.toml。 - 从
Store.sync读取 server URL 和 token。 - 克隆本地
Store作为上传 payload。 - 上传前把 payload 中的
server和sync字段清空。 - 向
<server>/v1/state发送PUT请求,并附带Authorizationheader。 - 服务端校验 bearer token。
- 服务端加载原服务端
Store。 - 服务端只用 payload 中的
meta、accounts、homes替换服务端状态。 - 服务端把
sync置为默认值,保留原服务端的server.users和server.sessions。 - 服务端保存配置。
- 客户端更新本地
last_push_at。 - 客户端保存本地
cdxs.toml。
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 并保存
查看同步状态
命令:
cdxs sync status
输出内容来自本地 Store.sync:
serverusertokenlast_pull_atlast_push_at
如果本地保存了 token,输出只显示:
<stored>
不会打印 token 明文。
Docker 运行方式
Dockerfile 使用两阶段构建:
- 使用
rust:1-bookworm构建 release 版本cdxs。 - 使用
debian:bookworm-slim作为运行镜像。 - 安装
ca-certificates。 - 把
cdxs复制到/usr/local/bin/cdxs。 - 暴露
8765端口。 - 声明
/datavolume。
容器默认命令:
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。
保存时会:
- 如果目标文件已存在,先备份到对应 home 下的
cdxs-backups。 - 将
Store序列化为 TOML。 - 写入同目录临时文件。
- 使用 rename 替换目标文件。
服务端请求还会使用 tokio::sync::Mutex 串行化文件读写,降低并发请求导致状态覆盖或文件损坏的风险。
当前实现边界
- 同步没有字段级合并或冲突解决,
pull会用远端meta/accounts/homes覆盖本地,push会用本地meta/accounts/homes覆盖服务端。 - bearer session 当前没有过期时间和撤销命令。
- 服务端用户只有添加/更新命令,没有删除、列表或改密命令。
- 服务端没有多租户隔离;所有通过鉴权的用户访问同一份服务端状态。
- HTTP 服务没有在代码中直接配置 TLS,需要由外部反向代理或部署环境处理。
sync push不上传客户端本地synctoken,也不上传客户端本地server配置。sync pull不会写入远端server或远端sync字段,只更新本地可移植状态并保留本地同步登录信息。