自建 Baïkal 日历和通讯录同步服务

用社区 Docker 镜像部署 Baïkal,接入 HTTPS 的 CalDAV、CardDAV,验证跨设备修改与删除,并保存 SQLite 和配置。

·6 mindocker

想让日历和联系人跨设备同步,不一定需要部署一整套办公平台。Baïkal 提供 CalDAV、CardDAV 服务,可以让兼容的客户端连接同一个数据源。网页后台负责账户、日历和通讯录管理,日常查看与编辑主要在手机和电脑客户端完成。

真正需要验证的是两台设备之间的新增、修改和删除是否一致,以及服务器故障后如何恢复。本文先用 SQLite 做个人实例,网页管理和日历同步使用各自的账号。

镜像来源与持久目录先说明白

以下使用 ckulka/baikal 维护的社区 Docker 镜像。部署方式对照该镜像仓库,协议与应用配置则参考 Baïkal 上游文档。

个人日历和通讯录同步适合先复用已有 Linux VPS,重点检查客户端能否稳定访问 HTTPS 域名,并安排独立备份。需要单独维护同步服务时,可以查看雨云云服务器,优惠码 KuZhuJi。选购前确认系统管理权限、公网访问和域名接入条件,运行后用两台设备验证时区、修改和删除的同步行为。

准备 Linux VPS、Docker Engine、Compose、一个域名及宿主机 Caddy。先固定所选 nginx 镜像:

mkdir -p ~/apps/baikal/config ~/apps/baikal/specific
cd ~/apps/baikal
umask 077
docker pull ckulka/baikal:nginx
printf 'BAIKAL_IMAGE=%s\n' "$(docker image inspect --format '{{index .RepoDigests 0}}' ckulka/baikal:nginx)" > .env

确认镜像摘要非空,保存为 compose.yml:

services:
  baikal:
    image: ${BAIKAL_IMAGE:?请固定镜像摘要}
    restart: unless-stopped
    ports:
      - "127.0.0.1:3021:80"
    volumes:
      - ./config:/var/www/baikal/config
      - ./specific:/var/www/baikal/Specific

路径 Specific 的大小写要保持准确。config 与 Specific 都是持久数据范围,不能只保留其中一个。该镜像默认在启动时修正相关文件权限;如果出现写入错误,先核对这两个目录和启动日志,不要随意关闭权限修正或开放整个目录。

docker compose config --quiet
docker compose up -d
docker compose ps
docker compose logs --tail=100 baikal

在电脑上建立隧道,打开 http://localhost:3021 完成安装:

ssh -N -L 3021:127.0.0.1:3021 user@VPS_IP

安装向导选择 SQLite,创建管理员密码,再建立普通同步用户、日历与通讯录。先用无敏感信息的测试资料,等待 HTTPS 入口完成后再接正式客户端。

给客户端一个稳定的 HTTPS 地址

宿主机 Caddy 的示例:

dav.example.com {
    redir /.well-known/caldav /dav.php 308
    redir /.well-known/carddav /dav.php 308
    reverse_proxy 127.0.0.1:3021
}

替换域名,核对 DNS、80 和 443 端口,验证配置后重载。客户端支持自动发现时,上面两个重定向有帮助;需要手动填服务器地址时,可以从 https://dav.example.com/dav.php 开始,再按客户端要求填写具体日历或通讯录地址。

不要给整个 DAV 入口再套一层不兼容客户端的网页登录流程。TLS 与 Baïkal 的用户认证应当能被实际客户端使用。若反向代理也在容器中,上游改为共同网络里的 baikal:80。

进入管理页后核对访问地址与配置。客户端使用普通用户的凭据,管理员密码只用于后台管理;日历名能看见却不能写入时,应检查账户权限和具体资源地址。

从一个跨时区事件和一个联系人开始

先建立一条带明确时区的测试事件,再建立一条全天事件。让另一台设备同步,比较开始时间、日期与提醒设置,然后从第二台设备修改标题,让第一台设备再次同步。

通讯录可以用一个虚构姓名、测试邮箱和简单备注检查往返同步,不必上传完整通讯录作为第一次验收。再删除一个专门的测试项目,确认删除也会传播。重复联系人有时来自同时启用多个同步来源,排查时先明确每台设备在使用哪个账户。

网页后台能登录,并不能证明 CalDAV、CardDAV 请求正常。客户端报错时,分别核对协议支持、HTTPS 证书、账户密码、资源地址和代理日志。某个设备工作正常,也不能替代另一种客户端的兼容性测试。

同步会传播误删,SQLite 备份要在停止写入后做

日历和联系人一旦被误删,删除可能很快同步到其他设备。客户端导出 ICS、vCard 有助于保留资料,服务器仍要备份完整目录。

关闭编辑客户端或暂停同步,短暂停止容器后执行:

mkdir -p backups
docker compose stop baikal
sudo tar -czf "backups/baikal-$(date +%Y%m%d-%H%M%S).tar.gz" config specific .env compose.yml
sudo chmod 600 backups/*.tar.gz
docker compose start baikal

SQLite 数据库与配置一起保存,避免只复制一个正在写入的数据库文件。将备份转移到独立位置,并保留当前镜像摘要与代理配置。

在隔离目录恢复时,解压两个持久目录,用原镜像和不同本机端口启动,先检查用户、日历和通讯录,再用测试客户端验证那几条记录。不要直接让日常设备连接测试实例,否则可能把演练中的修改继续同步出去。

协议与反向代理发现路径见 Baïkal 安装文档,镜像来源、目录和权限说明见 ckulka 的镜像仓库。