Files
cdxs/docs/sync-server.md
T

13 KiB
Raw Permalink Blame History

配置同步/服务端

本文说明 cdxs 当前配置同步和同步服务端功能的实现方式、调用机制和运行过程。

功能范围

配置同步/服务端主要覆盖以下能力:

  • 启动一个最小 HTTP 同步服务。
  • 在服务端配置文件中添加或更新登录用户。
  • 客户端使用用户名和密码登录同步服务。
  • 客户端把本地可移植配置推送到服务端。
  • 客户端从服务端拉取可移植配置并覆盖本地状态。
  • 查看当前客户端同步配置状态。
  • 使用 Docker 或 docker-compose 运行同步服务。

当前同步的数据只包含 cdxs.toml 中的可移植状态:

  • meta
  • accounts
  • homes

不会同步客户端本地的 sync 配置,也不会把服务端的 server.usersserver.sessions 下发给客户端。

核心文件

  • src/main.rsCLI 入口,负责把 serversync 命令分发到具体模块。
  • src/cli.rs:定义同步服务端和客户端命令参数。
  • src/server.rs:HTTP 同步服务端实现,包含用户管理、登录、鉴权、状态读写。
  • src/sync_client.rs:客户端同步逻辑,包含登录、拉取、推送和状态查看。
  • src/config_store.rscdxs.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。后续 pullpush 使用:

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,把以下字段清空:

  • server
  • sync

这样客户端不会拿到服务端用户、密码哈希、已签发 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路径>

运行过程:

  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 保存服务端配置。
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路径>

运行过程:

  1. 调用 resolve_data_path 解析服务端数据文件和默认 home。
  2. 解析 --bindSocketAddr
  3. 创建 AppState,保存数据路径、默认 home 和一个异步 Mutex
  4. 注册 /health/v1/login/v1/state 路由。
  5. 绑定 TCP listener。
  6. 调用 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>

运行过程:

  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_urlStore.sync.usernameStore.sync.token
  11. 客户端保存本地配置。
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

运行过程:

  1. 加载本地 cdxs.toml
  2. Store.sync 读取 server URL 和 token。
  3. 如果未登录或缺少 token,返回错误。
  4. <server>/v1/state 发送 GET 请求,并附带 Authorization header。
  5. 服务端校验 bearer token。
  6. 服务端加载服务端 Store
  7. 服务端清空 serversync 字段后返回 JSON。
  8. 客户端解析远端 Store
  9. 客户端用远端 metaaccountshomes 覆盖本地对应字段。
  10. 客户端保留本地 sync 配置,并更新 last_pull_at
  11. 保存本地 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

运行过程:

  1. 加载本地 cdxs.toml
  2. Store.sync 读取 server URL 和 token。
  3. 克隆本地 Store 作为上传 payload。
  4. 上传前把 payload 中的 serversync 字段清空。
  5. <server>/v1/state 发送 PUT 请求,并附带 Authorization header。
  6. 服务端校验 bearer token。
  7. 服务端加载原服务端 Store
  8. 服务端只用 payload 中的 metaaccountshomes 替换服务端状态。
  9. 服务端把 sync 置为默认值,保留原服务端的 server.usersserver.sessions
  10. 服务端保存配置。
  11. 客户端更新本地 last_push_at
  12. 客户端保存本地 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

  • server
  • user
  • token
  • last_pull_at
  • last_push_at

如果本地保存了 token,输出只显示:

<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。

容器默认命令:

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::saveStore::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 字段,只更新本地可移植状态并保留本地同步登录信息。