云同步两条路线:共享协议层下的 S3 与 WebDAV
CC Switch 的云同步在仓库里有两组命令、两个前端预设列表、两套设置字段,看名字像是 S3 与 WebDAV 各写了一份功能。读完源码会发现不是:它只有一套同步协议,S3 与 WebDAV 是挂在这套协议下面的两个传输层。更反直觉的是,走 S3 那条路上传上去的清单里,格式标识写的仍然是带 webdav 字样的字符串。
下面全部基于我们本地 clone 的 cc-switch 仓库快照 c39c903(提交日期 2026-08-10),仓库内版本号 3.19.2。我们只读源码文本,没有安装也没有运行过这个桌面应用,因此不涉及任何界面与操作方面的描述。
协议层在哪一个文件,它固定了什么
入口是 src-tauri/src/services/sync_protocol.rs,648 行。文件头注释自称 transport-agnostic,明写被 WebDAV、S3 以及 “future transports” 共用,并把产物集合固定为两个文件:db.sql 与 skills.zip(sync_protocol.rs:1-4)。
协议常量集中在同一个文件的开头(sync_protocol.rs:23-33),这几个值决定了两端能不能对上:
| 常量 | 值 | 作用 |
|---|---|---|
PROTOCOL_FORMAT | cc-switch-webdav-sync | 写进远端 manifest 的格式标识 |
PROTOCOL_VERSION | 2 | 协议版本 |
DB_COMPAT_VERSION | 6 | 数据库兼容版本 |
LEGACY_DB_COMPAT_VERSION | 5 | 老远端缺字段时的回落值 |
MAX_DEVICE_NAME_LEN | 64 | 设备名长度上限 |
MAX_MANIFEST_BYTES | 1 MiB | manifest 大小上限 |
MAX_SYNC_ARTIFACT_BYTES | 512 MiB | 单个产物大小上限 |
第一行就是那处反直觉的地方。PROTOCOL_FORMAT 的值是 cc-switch-webdav-sync,注释写明这是保留历史命名以兼容既有远端。也就是说,即便你只用 S3,远端 manifest 里落下的格式串照样带着 webdav 三个字——这不是配置项,是常量。
manifest 本身的字段是固定的七项:format、version、dbCompatVersion、deviceName、createdAt、artifacts(每个产物带 sha256 与 size)、snapshotId(sync_protocol.rs:62-77)。其中 snapshot_id 不是随机串,而是把各产物的 name:sha256 按 BTreeMap 排序拼起来再取一次 SHA-256(sync_protocol.rs:165-172)——这个计算式里不含随机量,只取决于产物名与内容摘要。
兼容校验函数 validate_manifest_compat 只拒绝三种情况:format 对不上、protocol version 不等于本地的 2、缺少数据库兼容版本(sync_protocol.rs:184-215)。老远端如果没有 dbCompatVersion 字段,会按 RemoteLayout::Legacy 布局回落到 5(sync_protocol.rs:88-101、:175-182)。这三条判定里没有任何一条与传输方式有关。至于换一条传输路线之后能不能直接接管既有远端目录,我们没有运行过,不下结论。
两个传输层:一个 530 行,一个 926 行
差异都在传输层这一层。
WebDAV 侧是 src-tauri/src/services/webdav.rs,530 行,提供 parse_base_url / build_remote_url / test_connection / ensure_remote_directories / put_bytes / get_bytes / head_etag 这一组原语,另外还有一个针对特定网盘的特判函数 is_jianguoyun(url)(webdav.rs:33-369)。
S3 侧是 src-tauri/src/services/s3.rs,926 行,最大的一块是自行实现的 AWS Signature Version 4 签名:AWS4-HMAC-SHA256,签名密钥链是 AWS4+secret → date → region → "s3" → "aws4_request"(s3.rs:1-17)。超时是两档:默认 30 秒,大文件传输走 TRANSFER_TIMEOUT_SECS = 300(s3.rs:199-215)。凭据结构是 access_key_id / secret_access_key / region / bucket / endpoint 五项,endpoint 留空表示 AWS 官方端点,split_scheme_host 支持裸 host、http://、https:// 三种写法(s3.rs:21-57)。
前端预设条数也不对称:S3 侧 7 项(AWS S3、MinIO、R2、OSS、COS、OBS、自定义),各带一个 region 占位符 us-east-1 / us-east-1 / auto / cn-hangzhou / ap-guangzhou / cn-north-4 / us-east-1;WebDAV 侧 4 项(坚果云、Nextcloud、群晖、自定义)(src/components/settings/WebdavSyncSection.tsx:54-146)。这里列的是对象存储与网盘服务,也就是同步文件放在哪,和你在 CC Switch 里配的 API 供应商是两件不相干的事;我们只照录预设条目,不做任何推荐或评价。
命令面是严格对称的五对:s3_test_connection / s3_sync_upload / s3_sync_download / s3_sync_save_settings / s3_sync_fetch_remote_info(src-tauri/src/commands/s3_sync.rs:79-160),以及同名换前缀的 webdav_* 五个(src-tauri/src/commands/webdav_sync.rs:84-168)。保存设置时有个细节:传入的 secret 若为空,走 resolve_secret_for_request 沿用已存的那份(commands/s3_sync.rs:40-51)。
上传顺序与落地顺序,各自有意排过
上传是先 db.sql、再 skills.zip、最后写 manifest,注释标的是 best-effort consistency;写完再 best-effort 取一次 ETag(src-tauri/src/services/webdav_sync.rs:63-99)。manifest 排在最后写,源码注释对这一顺序的自我定性就是 best-effort consistency 这一句;至于各种中断场景下远端到底会留下什么状态,我们没有运行过,不下结论。
落地方向是 apply_snapshot:先备份现有 skills → 覆盖 skills → 导入 db;db 导入失败时回滚 skills,两者都失败会返回合并后的错误(sync_protocol.rs:309-340)。skills 打包成确定性 ZIP(Deflated,时间戳固定为 DateTime::default()),解压条目数上限 MAX_EXTRACT_ENTRIES = 10_000(src-tauri/src/services/webdav_sync/archive.rs:17-18、:38-42)。
导入完成后统一跑一遍 run_post_import_sync,内容是 ProviderService::sync_current_to_live 加 settings::reload_settings;这一步失败不算整体失败,只作为 warning 字段附加在返回值里(src-tauri/src/commands/sync_support.rs:10-42)。
设备名的探测顺序值得单独记一笔:先看环境变量 CC_SWITCH_DEVICE_NAME,再看 COMPUTERNAME,再看 HOSTNAME,都没有才 fork 一个 hostname 命令(sync_protocol.rs:350-366)。这四级里只有 CC_SWITCH_DEVICE_NAME 排在最前,且不依赖平台自带什么;另外三级取决于你机器上实际有哪些环境变量。你这台机器最终命中的是哪一级,我们没有运行过,不下结论——想让多台机器在远端有个可辨认的名字,能自己控制的那一级就是排在最前的这一个。
另外,云同步走的导出函数和文件级的那套导入导出不是同一对:前者是 export_sql_string_for_sync / import_sql_string_for_sync(sync_protocol.rs:108-110、:326),后者是 export_config_to_file / import_config_from_file,落到磁盘上的文件扩展名是 sql(src-tauri/src/commands/import_export.rs:19-58、:80-120)。
自动同步不是「按间隔」,是表变更驱动
这是本篇第二处容易踩空的地方。
用户手册在设置章节写的是「自动同步|开启后按配置的间隔自动同步」「CC Switch 按配置的间隔自动将数据库同步到 WebDAV」(docs/user-manual/zh/1-getting-started/1.5-settings.md:234、:248)。而代码里 WebDavSyncSettings 的字段只有 enabled / auto_sync / base_url / username / password / remote_root / profile / status,没有任何间隔字段(src-tauri/src/settings.rs:110-142)。两处不一致,以我们实读的仓库状态为准;至于为什么会这样,本文不做推断。
代码里的实际触发逻辑是数据库表变更驱动,白名单 8 张表:providers、provider_endpoints、mcp_servers、prompts、skills、skill_repos、settings、proxy_config(src-tauri/src/services/webdav_auto_sync.rs:43-56;S3 侧同表,s3_auto_sync.rs:43-56)。收到首个 dirty 信号后,工作循环反复以 min(1000ms, 剩余至 10s 上限) 等待合并后续信号,超时或撞到 10 秒上限就触发一次上传(webdav_auto_sync.rs:165-193);两个常量是 AUTO_SYNC_DEBOUNCE_MS = 1000 与 MAX_AUTO_SYNC_WAIT_MS = 10_000(webdav_auto_sync.rs:15-16、s3_auto_sync.rs:15-16)。信号通道容量只有 1,注释直说「只需要 dirty 信号,不需要每个事件」(webdav_auto_sync.rs:155-157)。
三个结论性事实,照实记:
- 自动同步只在
enabled && auto_sync都为 true 时执行; - 它只做 upload(
run_auto_sync_upload),不会自动 download(webdav_auto_sync.rs:74-79、:106-135); - 有一个引用计数的可重入抑制守卫
AutoSyncSuppressionGuard,抑制期内notify_db_changed直接返回(webdav_auto_sync.rs:19-41、:137-148)。
结果通过事件 webdav-sync-status-updated 上报,payload 带 source: "auto"(webdav_auto_sync.rs:88-103)。
手册里查不到 S3,这件事本身要先知道
如果你打算按用户手册配 S3,会扑空。我们在 docs/user-manual 全部 78 篇 .md 里 grep “S3”,命中 0 次;设置章节只写「云同步(WebDAV)」(docs/user-manual/zh/1-getting-started/1.5-settings.md:222),README 的功能列表同样只列 WebDAV(README.md:251)。而 v3.16.2 的中文发布说明明确写了新增 S3 兼容云同步,并把读者指向该设置文档(docs/release-notes/v3.16.2-zh.md:13、:50),代码侧 s3.rs、s3_sync.rs、s3_auto_sync.rs、5 个命令、7 个前端预设也都在。
顺带还有一处措辞与现状对不上:s3.rs:5 的文件头注释写 “The sync protocol logic lives in the upcoming s3_sync module”,而 src-tauri/src/services/s3_sync.rs 已经是 319 行的实体文件,并且被命令层调用(src-tauri/src/commands/s3_sync.rs:10)。说完差异就停,不推断原因,也不拿它评价这个项目。
你自己怎么核一遍
三个动作,都不需要装这个应用,clone 下来就能跑:
grep -rn "S3" docs/user-manual --include=*.md | wc -l
grep -n "interval" src-tauri/src/settings.rs
grep -n "PROTOCOL_FORMAT" src-tauri/src/services/sync_protocol.rs
第一条对应「手册里有没有 S3」,第二条对应「设置结构里到底有没有间隔字段」,第三条对应本文开头那个格式标识。三条各自落在一个具体文件上,结论都是你自己数出来的,不必信本文的转述。
再说一句边界:上面这些是代码里的默认配置与常量,不是「你用起来会怎样」的保证。 1000ms 防抖、10 秒合并上限、512 MiB 产物上限、300 秒大文件超时,这些值决定的是代码路径怎么走,你实际会不会触发、会不会撞上限,取决于你的数据量和网络,本文不做任何推算。
什么情况说明你遇到的问题不在本文范围内:如果远端目录里连 manifest 都没有生成,那还没走到协议校验这一层,问题在传输层的连通性与凭据;如果同步能上去但本地某项配置没跟着变,先看返回值里的 warning 字段——run_post_import_sync 的失败是被降级成 warning 的,不会让整次导入报错。
最后一条必须说清楚:这套同步的产物包含整库的 SQL 导出,而 CC Switch 会在本机保存供应商配置与 API Key,这属于本机敏感数据。仓库自己的 SECURITY.md 也把「从 WebDAV/S3 还原的同步数据」列进了在范围内的不可信输入清单(SECURITY.md:59-70)。往任何一个远端上传之前,这一点得先想清楚;我们没有逐字段核过导出内容里具体包含什么,也不会给出「这样配就安全了」这种话。
本文依据 CC Switch 官方仓库(github.com/farion1231/cc-switch)的 README、docs/ 下的用户手册与发布说明、
src/config/ 的预设定义与 src-tauri/src/ 的后端源码整理,核对日 2026-08-10,对应仓库快照 c39c903。
本文内容为仓库源码与文档口径,我们没有安装或运行过这个桌面应用,
因此不涉及界面外观、操作手感与切换速度的任何描述。
文中出现的阈值与默认值均为源码中的默认配置,不构成对实际运行结果的保证。
该项目仍在快速迭代,版本与默认值随时可能变动,请以仓库最新内容为准。
安全相关做法请结合自身环境评估,本文不构成安全方案建议。