对话笔记直发:8.8 radicale-tasks-计划。分类:AI工程。
Radicale + Tasks.org 每日待办闭环 — 实施方案(评审稿 v3)
版本:v3(二轮评审剩余文字级修正全部落实) 日期:2026-08-08 服务器:jp(
局域网IP(已脱敏),aarch64 / RK3566,Docker host) 评审状态:待终审 实施状态:已实施✅(2026-08-09)。脚本 A/B 已上线并通过端到端验证,见 §10。
1. 目标闭环
Hermes(宿主机进程 /opt/hermes·凌晨3:00)
│ ① 生成计划 → FNS API 写入 Obsidian 01_工作台/<今日>.md
│ ② 解析 "- [ ] 任务(HH:MM-HH:MM)" 生成 VTODO(规范文本 → 确定性 UID)
│ ③ HTTP PUT → Radicale(CalDAV,If-None-Match 原子幂等)
▼
Radicale(NAS docker :5232,30MB,纯 .ics 文件)
│ CalDAV / HTTPS(CF Tunnel)
▼
手机 Tasks.org(Schedule 拖块 + Android 本地系统闹钟)
│ 勾完成 / 拖时间
▼
脚本B(NAS cron 每8分钟)→ 读 Radicale STATUS:COMPLETED → FNS replace 翻 Obsidian [x]
▼
Obsidian ← Hermes 次日读含 [x] 的昨日文件 → 滚动生成今日计划(既有能力,验收#5验证)
2. 关键事实(已核实)
2.1 服务器现状(本期实地探活结论)
- Hermes = 宿主机进程(非容器):
/opt/hermes/.venv/bin/python -m hermes_cli.main gateway run,另有 dashboard。 - cloudflared = 宿主机进程(非容器):
/usr/bin/cloudflared --config /etc/cloudflared/config.yml tunnel run。 - 域名根 =
unixcs.eu.org(不是 unicsx/unics)。ingress 规则在/etc/cloudflared/config.yml,本机服务用http://127.0.0.1:<port>直连(进程在宿主网络,非容器网络)。 - FNS = 容器
fast-note-sync-service(:9000),REST API。 - 结论:Radicale 的 cloudflared ingress =
radicale.unixcs.eu.org → http://127.0.0.1:5232。
2.2 tomsquest/docker-radicale 镜像(必须遵守)
- CMD 固定
["/venv/bin/radicale","--config","/config/config"],镜像自带/config/config默认无认证。 - 官方目录范式:
./data:/data、./config:/config:ro,config 指向/data/collections、/config/users。 - ⚠️ 不能挂
/etc/radicale、/var/lib/radicale(默认 CMD 不会读 → 无认证+数据丢匿名卷)。 - 镜像内无 htpasswd 二进制 → 宿主机 apache2-utils 或容器内置 python 生成 bcrypt
$2b$。 - 官方 arm64 支持;pin tag(
latest每日重建),docker pull现场确认。
2.3 Radicale v3 CalDAV 语义
- PUT 不能自动建集合:
/feng/cal必须先MKCOL(resourcetype=calendar + supported-calendar-component-set=VTODO)。 - 子资源写入
PUT /feng/cal/<uid>.ics,文件名=内容 UID。 - 幂等:PUT 带
If-None-Match: *→ 已存在 412 → 跳过(已对照源码验证:不存在→201,存在→412,父集合缺→409)。 - 读完成:读集合
.ics或 CalDAV REPORT 找STATUS:COMPLETED。 - 默认 rights
owner_only,auth 生效后/feng/*无需额外 ACL。 - 备份排除
.Radicale.lock/.Radicale.cache/.Radicale.tmp-*。
2.4 时区(明确换算规则)
- Asia/Shanghai 固定 UTC+8、无 DST → 脚本统一换算:
本地 HH:MM − 8h → UTC,写入为DTSTART:20260809T010000Z/DUE:20260809T030000Z。禁止直接把本地时间加 Z。 - 每条含
DTSTAMP:...Z(UTC now)与PRODID。
2.5 CF Tunnel 安全策略
- 公网仅 HTTPS(Tunnel 自带 TLS),CalDAV 路径不启用 CF Access(DAVx5/Tasks.org 仅发 Basic,过不了 Access 会话制)。主防线 = htpasswd;防爆破 = CF WAF 限速规则(可选)。
- 新增子域:
cloudflared tunnel route dns <TUNNEL> radicale.unixcs.eu.org→ 编辑/etc/cloudflared/config.yml追加 ingress → 重启 cloudflared 进程。
3. Phase 0 — 部署 Radicale
/opt/radicale/ 下 compose:
services:
radicale:
image: tomsquest/docker-radicale:<pinned-tag> # 现场 docker pull 确认
container_name: radicale
restart: unless-stopped
ports:
- "5232:5232"
volumes:
- ./data:/data
- ./config:/config:ro
environment:
TZ: Asia/Shanghai./config/config:
[server]
hosts = 0.0.0.0:5232
[storage]
type = multifilesystem
filesystem_folder = /data/collections
[auth]
type = htpasswd
htpasswd_filename = /config/users
htpasswd_encryption = bcrypt
创建用户(宿主机):
apt-get install -y apache2-utils # 若已装则跳过
htpasswd -B /opt/radicale/config/users feng首日建集合(必须,否则 PUT 409):
curl -u feng:xxx -X MKCOL -H "Content-Type: application/xml" --data \
'<?xml version="1.0"?><C:mkcol xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:"><D:set><D:prop><D:resourcetype><D:collection/><C:calendar/></D:resourcetype><C:supported-calendar-component-set><C:comp name="VTODO"/></C:supported-calendar-component-set></D:prop></D:set></C:mkcol>' \
http://127.0.0.1:5232/feng/cal/验收:curl -u feng:xxx http://127.0.0.1:5232/feng/ → 200;无密码 → 401(证明 auth 生效)。
4. Phase 1 — 手机 Tasks.org
- CalDAV 账户:服务器
https://radicale.unixcs.eu.org/feng/、用户feng、密码。 - 清单「每日待办」;Schedule 拖块;App 默认提醒 + 通知权限。
- 验收:建测试任务 → 时间轴色块 → 到点响铃。
5. Phase 2 — 脚本A(Hermes 侧推送)
- 规范函数 uniform():A/B 共用(日期格式、空格、全/半角括号、ASCII 折叠)。
- 时区换算:
本地 HH:MM − 8h → UTC Z(写死规则,见 2.4)。 - UID =
sha1(日期 + uniform(文本) + 时间段);同文同日加序号。 - VTODO 模板:PRODID + DTSTAMP(UTC now) + SUMMARY + DTSTART/DUE(UTC Z);无时间 → 无 DTSTART。
- 首次 MKCOL(Phase 0 已做,脚本幂等重试)。
- 推送:
PUT .../cal/<uid>.ics+If-None-Match: *;412 → 跳过。 - 401 → FNS token 重登一次;指数退避;日志滚动。
6. Phase 3 — 回写脚本B
- cron 每 8 分钟;host python3 + requests。
- 读 Radicale
.ics(或 REPORT)找STATUS:COMPLETED,按 UID 反查 Obsidian 行。 - FNS replace:正则锚定整行(转义任务文本中
[ ] ( ) .),校验matchCount==1。 - 单向语义:Obsidian 手动勾 [x] 不回传;Radicale 删任务行保持原样。
- 清理:定期 DELETE 已完成/过期 UID。
- FNS replace 实测:上线前先对 fast-note-sync 3.5.1 的
/api/note/replace做一次真调用(确认 matchCount 语义、能否按任意 path 替换、不硬编码文件名),并入验收 #4。
7. 运维/备份
- 固定镜像 tag;rsync 备份
data/collections(排除.Radicale.*)。 - FNS 备用
POST /api/backup/execute。 /etc/cloudflared/config.yml纳入备份。- 公网仅 HTTPS;不启用 CF Access;可选 WAF 限速。
8. 验收矩阵
| 序号 | 验收 |
|---|---|
| 1 | http://局域网IP(已脱敏):5232/feng/ 局域网可访问且 auth 生效(401 无密码) |
| 2 | https://radicale.unixcs.eu.org 公网可访问(HTTPS) |
| 3 | Hermes 写入 → 手机出现任务(含时间块)不重复 |
| 4 | 手机勾完成 → Obsidian 变 [x](含 FNS replace 实测验证) |
| 5 | 连续两天闭环无人工介入(验证 Hermes 次日滚动既有能力) |
9. 风险
| 风险 | 对策 |
|---|---|
| 镜像 CMD 挂载错配 | 官方 /data+/config 布局 + 401 验证 |
| PUT 无法建集合 | MKCOL + supported VTODO |
| 时区偏移 | 统一 本地−8h→UTC Z 规则,禁直接加 Z |
| 探测-PUT 竞态 | If-None-Match: * |
| 同文同日撞 UID | uniform+时间段+序号 |
| FNS replace 语义未知 | 上线前实测并入验收#4 |
| 域名/命名混乱 | 已核实 unixcs.eu.org + 全篇统一 FNS |
10. 实施记录(2026-08-09 上线)
关键变更:FNS 回写改用 MCP 通道
- 原计划用 REST
/api/note/replace,实测不可用:服务端 JWT 签名密钥 ≠ 配置里的auth-token-key(被轮换/另有存储),且插件 token scope 仅c:ObsidianPlugin、Hermes token 仅p:mcp,REST replace 一律 315/308 拒。 - 改用 FNS MCP(Hermes 已在用的通道):
https://notes.unixcs.eu.org/api/mcp/sse+ Hermes token。note_get(vault, path)读工作台;note_replace(vault, path, find, replace, regex, all)翻[ ]→[x]。- 实测验证:replace 真实持久化(version 0→1,note_get 回读可见),与”FNS replace 有 bug”的
注:本文涉及的服务器公网 IP / Tailnet / 局域网 IP 已脱敏,请替换为你自己的地址。