Cal.diy 日常运维与排障手册

Cal.diy v6.2.0 日常运维与排障:混合大小写表结构规则、pg_dump 备份与恢复演练(522K/131 段实测)、版本升级七步流程与回退预案、Webhook 注册与 BOOKING_CREATED 推送 HMAC-SHA256 验签实测、启动/访问/SQL/升级四类故障排查。

Cal.diy运维PostgreSQL2026/10/19

Cal.diy 日常运维与排障手册

目标:为 Cal.diy v6.2.0 自托管环境提供日常运维手册:数据库维护与备份恢复、版本升级流程、Webhook 管理与出站集成验证、常见故障排查。全部命令与回显均在 Docker Compose 实测环境(Web 8127 / API v2 8129 / 数据库维护口 8128)取得,实测日期 2026-10-19。

一、环境信息

  • 部署形态:《Cal.diy Docker Compose 部署指南》产物:cal-com-6-2-0-web(calcom/cal.com:v6.2.0)、cal-com-6-2-0-db(postgres:16)、cal-com-6-2-0-redis(redis:7)、cal-com-6-2-0-api(自构建 v6.2.0)
  • 软件版本:Cal.diy v6.2.0(2026-03-01 稳定版);数据层 PostgreSQL 16,Prisma 迁移 588 个(2026-10-19 实查)
  • 运维入口约定:数据库操作统一 docker exec cal-com-6-2-0-db psql -U cal_com -d cal_com

二、数据库维护

2.1 表结构命名规则(先读,避免撞错)

Cal.diy 的 Prisma schema 对表名采用混合大小写(PascalCase),列名采用驼峰,少数表映射为小写。实测两条例子:

# 错误:SELECT * FROM passwords;  → ERROR: relation "passwords" does not exist
# 正确:
# docker exec cal-com-6-2-0-db psql -U cal_com -d cal_com -c '\dt' | head -8
             List of relations
 Schema |           Name           | Type  |  Owner
--------+--------------------------+-------+---------
 public | Account                  | table | cal_com
 public | ApiKey                   | table | cal_com
 public | App                      | table | cal_com
 public | Booking                  | table | cal_com
 public | users                    | table | cal_com

要点:users、Membership 等部分表小写,"Booking"、"Schedule"、"EventType"、"Deployment" 等核心业务表 PascalCase;混合大小写标识符在任何 SQL 中必须用双引号包裹("Booking"."startTime")。写维护脚本前先 \dt 确认实际表名。

2.2 例行备份与恢复演练

逻辑备份(实测 exit=0,当前库 522K):

# docker exec cal-com-6-2-0-db pg_dump -U cal_com -d cal_com > /tmp/cal_com_backup.sql
# echo $?
0
# ls -lh /tmp/cal_com_backup.sql | awk '{print $5}'
522K

建议频率:每日一次全量(cron 调度可参考站内《Cronicle》或《Dagu》),保留策略按团队规范;备份文件务必异机存放。

恢复演练(实测到临时库验证,不污染主库):

# docker exec cal-com-6-2-0-db psql -U cal_com -c "CREATE DATABASE cal_com_restore_test;"
CREATE DATABASE
# docker exec -i cal-com-6-2-0-db psql -U cal_com -d cal_com_restore_test < /tmp/cal_com_backup.sql
(大量 ALTER TABLE / COPY 输出,结束无 ERROR 即成功)
# docker exec cal-com-6-2-0-db psql -U cal_com -d cal_com_restore_test \
    -c 'SELECT count(*) FROM users;' -c 'SELECT count(*) FROM "Booking";'
 count
-------
     1
 count
-------
     2
# docker exec cal-com-6-2-0-db psql -U cal_com -c "DROP DATABASE cal_com_restore_test;"
DROP DATABASE

真实故障恢复即把上述目标库换为 cal_com(先停止 Web 与 API 服务避免写入竞争)。

2.3 常用维护 SQL

-- 用户清单与角色
SELECT id, email, username, role FROM users;

-- 管理员提权(首个用户)
UPDATE users SET role='ADMIN' WHERE id=1;

-- 补全显示名与时区(预约标题里的 Nameless 就来自 name 为空)
UPDATE users SET name='管理员', "timeZone"='Asia/Shanghai' WHERE id=1;

-- 预约记录核对
SELECT uid, title, status, "startTime" FROM "Booking" ORDER BY id DESC LIMIT 5;

-- API Key 清单(哈希存储,无法查看明文)
SELECT id, note, "expiresAt", "createdAt" FROM "ApiKey";

-- 部署/license 表(API v2 license 校验相关,社区版通常为空)
SELECT * FROM "Deployment";

2.4 数据库结构迁移

应用每次启动自动执行 npx prisma migrate deploy,日志以 All migrations have been successfully applied. 收尾。手工核对迁移状态:

# docker exec cal-com-6-2-0-db psql -U cal_com -d cal_com \
    -c "SELECT count(*) AS applied FROM _prisma_migrations;"
 applied
---------
     588

(v6.2.0 空库全新部署实测值 588。)升级版本后该计数增长即迁移成功;若出现 failed 状态行,先处理失败原因再重启应用容器重试,不要手工删 _prisma_migrations 行。

三、版本升级流程

官方升级文档(https://cal.diy/upgrading,2026-10-19 实查)的流程与 compose 部署对应如下:

  1. 备份:按 2.2 完成数据库全量备份并验证可恢复。
  2. 确认目标版本:订阅 https://github.com/calcom/cal.diy/releases.atom;使用普通 release(非 Pre-release)tag。版本节奏参考:v6.1.10(2026-02-02)→ v6.2.0(2026-03-01)一个月 8 个版本,更新频繁,建议按月跟进。
  3. 修改镜像 tag:编辑 docker-compose.yml 中 calcom/cal.com:v6.2.0 为目标版本(如 v6.3.0)。若自构建 API v2 镜像,同步用新版本源码 tag 重建 cal-com-6-2-0-api。
  4. 滚动更新:
# docker compose pull calcom
# docker compose up -d
  1. 核对迁移:docker logs cal-com-6-2-0-web 2>&1 | grep -c "Applying migration" 观察新迁移执行;2.4 的 _prisma_migrations 计数应增长到新版本数量。
  2. 功能抽查:登录、打开预约页、API v2 /v2/me 三项冒烟。
  3. 回退预案:compose 改回旧 tag 重新 up -d 即回滚应用;数据库结构升级一般不可自动回退,此时用第 1 步备份按 2.2 恢复——这是每次升级前必须备份的原因。

注意:PostgreSQL 大版本升级(如 15→16)数据卷不兼容,需要 pg_dump 逻辑迁移而非直接复用卷;本文实测基线即 postgres:16,新部署直接使用 16 及以上。

四、Webhook 与工作流管理

4.1 Cal.diy 的工作流能力边界(先说结论)

Cal.diy 社区版不含 Workflows 模块(多步自动化提醒序列随 EE 代码移除,《Cal.diy 介绍与选型指南》第三章有说明)。出站集成的标准形态是 Webhook:预约生命周期事件推送到外部系统,由外部系统完成 IM 通知、工单、订阅邮件等自动化。官方内置的邮件提醒保留基础形态(依赖 SMTP 配置)。

4.2 注册 Webhook(实测)

界面路径:「设置 - Webhook - 新建」。curl 复现实测(tRPC 通道):

# curl -s -b /tmp/cal_cookies.txt -X POST \
  'http://192.168.146.128:8127/api/trpc/webhook/create?batch=1' \
  -H 'Content-Type: application/json' \
  -d '{"0":{"json":{"subscriberUrl":"http://192.168.146.128:8130/hook",
       "eventTriggers":["BOOKING_CREATED","BOOKING_CANCELLED"],
       "active":true,"payloadTemplate":null,"secret":"whsec_doc_test_2026"}}}'
[{"result":{"data":{"json":{"id":"f479ebca-ce24-4b24-8585-54b4b04e300a","userId":1,
 "subscriberUrl":"http://192.168.146.128:8130/hook","active":true,
 "eventTriggers":["BOOKING_CREATED","BOOKING_CANCELLED"],
 "secret":"whsec_doc_test_2026",...}}}}]

可订阅的触发事件枚举(源码 packages/features/webhooks/lib/constants.ts 实查):BOOKING_CANCELLED、BOOKING_CREATED、BOOKING_RESCHEDULED、BOOKING_PAID、BOOKING_PAYMENT_INITIATED、MEETING_ENDED、MEETING_STARTED、BOOKING_REQUESTED、BOOKING_REJECTED、RECORDING_READY 等。eventTypeId 省略表示作用于全部事件类型。

4.3 推送验证与签名验真(实测)

在接收端收到 BOOKING_CREATED 推送的实际报文(预约者「李四」下单触发):

Content-Type: application/json
X-Cal-Signature-256: 3a8ebe02c7e71580e524b352109ae48da948cf49bce37f26c2c5e0a28f9e7ecf

{
  "triggerEvent": "BOOKING_CREATED",
  "createdAt": "2026-10-19T12:14:09.546Z",
  "payload": {
    "uid": "pjJ9sfhN6GRBVDykUhZioW",
    "title": "技术交流 between 管理员 and 李四",
    "startTime": "...", "endTime": "...",
    "type": "技术交流", "eventTypeId": 1, ...
  }
}

签名算法为 HMAC-SHA256(secret, 完整请求体),接收端用注册时的 secret 逐字节验证(实测 Python 复核 MATCH):

import hmac, hashlib
computed = hmac.new(b'whsec_doc_test_2026', body_bytes, hashlib.sha256).hexdigest()
assert hmac.compare_digest(computed, received_sig)

验签不通过直接丢弃请求,防止伪造预约事件。运维要点:接收端 URL 必须从服务器可达(compose 网络内服务用宿主 IP 访问外部地址没问题,反之接收端在内网时要用宿主可达地址);推送失败不会重试阻塞业务,但会写应用日志。

4.4 Webhook 日常管理

-- 查看已注册 webhook
SELECT id, "subscriberUrl", active, "eventTriggers" FROM "Webhook";
-- 临时停用(排查接收端故障时)
UPDATE "Webhook" SET active=false WHERE id='f479ebca-...';

删除在界面「设置 - Webhook」操作或 DELETE FROM "Webhook" WHERE ...。

五、日常巡检清单

# 1. 服务健康
# docker compose ps
NAME                  IMAGE                      STATUS
cal-com-6-2-0-web     calcom/cal.com:v6.2.0      Up X days (healthy)
cal-com-6-2-0-db      postgres:16                Up X days (healthy)
cal-com-6-2-0-redis   redis:7                    Up X days

# 2. 应用入口
# curl -s -o /dev/null -w "%{http_code}\n" http://192.168.146.128:8127/
200
# curl -s -o /dev/null -w "%{http_code}\n" http://192.168.146.128:8129/v2/me
401        ← API v2 就绪时返回业务级 401(未带凭据),502/404 才是异常

# 3. 错误日志扫描(近 200 行内无 fatal/unhandled 即可)
# docker logs --tail 200 cal-com-6-2-0-web 2>&1 | grep -ci "fatal\|unhandled"
0

# 4. 内存水位(四个服务 mem_limit 3.6G)
# docker stats --no-stream --format '{{.Name}}\t{{.MemUsage}}' | grep cal-com

# 5. 备份文件按日更新
# ls -lht /backup/cal_com_*.sql | head -3

六、常见故障排查

6.1 启动类

Web 容器反复重启或 502:

# docker logs cal-com-6-2-0-web 2>&1 | tail -20

常见三类:数据库连接串错(DATABASE_URL 密码特殊字符未转义)、迁移中断(重启容器让 migrate deploy 幂等重试)、内存不足被 OOM(docker inspect cal-com-6-2-0-web --format '{{.State.OOMKilled}}',调大 compose mem_limit)。

启动日志刷 Unknown argument: database(wait-for-it.sh 缺陷):v6.2.0 上游脚本以裸主机名调用 wait-for-it.sh 导致的报错(要求 host:port 格式),实测脚本报错后继续执行迁移,服务正常拉起,可忽略;若偶发迁移期数据库未就绪,docker compose restart calcom 重试。

API v2 崩溃循环 Missing environment variable: STRIPE_API_KEY.:环境变量必须存在(自托管留空即可)。.env 补 STRIPE_API_KEY= 与 STRIPE_WEBHOOK_SECRET= 后 docker compose up -d calcom-api。

API v2 报 502/404:502 为服务未就绪(看日志最后三行应出现 RedisService IoRedis connected!);404 检查路径前缀(服务挂在 /v2/)与端口映射。

6.2 访问与认证类

页面 403:ALLOWED_HOSTNAMES 未包含访问用的主机名:端口,修改后 --force-recreate 重建 Web 容器。

登录循环回登录页:NEXTAUTH_URL 与实际访问地址不一致导致 Cookie 域不匹配;三处 URL 变量(NEXTAUTH_URL、NEXT_PUBLIC_WEBAPP_URL、NEXT_PUBLIC_WEBSITE_URL)必须与浏览器地址栏完全一致。

管理后台看不到:首个用户需按《Cal.diy 快速入门与实战示例》2.2 提权;INACTIVE_ADMIN 是激活状态提示而非权限缺失,管理页面实际可达。

API v2 全部 401 Invalid or missing CALCOM_LICENSE_KEY:社区版 API Key 通道的企业 license 门槛(API Key 校验已通过,卡在 license 检查)。用 Web 会话 Cookie 通道(NEXT_AUTH)替代,或采购 license。此为实测确认的社区版行为,非配置故障。

6.3 SQL/数据类

relation "xxx" does not exist:表名大小写问题,先 \dt 再带双引号(2.1)。

column "start_time" does not exist:列名是驼峰("startTime"),不是下划线。

预约数据丢失嫌疑:先核对库再怀疑应用——SELECT count(*) FROM "Booking"; 与最近备份比对;确认连的是同一个卷(docker volume inspect cal-diy_database-data)。

API 建资源后页面看不到:API v2 与 Web 共库,数据实时互通;多为账号/团队过滤器不同(API 用的 userId 与页面登录账号不一致)。

6.4 升级类

升级后启动失败:先看是不是目标版本要求新的环境变量(v6.2.0 曾要求 STRIPE_API_KEY 占位);迁移失败按 2.4 处理;回退按第三章第 7 步。

升级后预约页样式错乱:浏览器缓存旧静态资源,强刷(Ctrl+Shift+R)或等 CDN/浏览器缓存过期;NEXT_PUBLIC_* 变量变更必须 --force-recreate 重建。

七、参考链接