对话笔记直发: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. 验收矩阵

序号验收
1http://局域网IP(已脱敏):5232/feng/ 局域网可访问且 auth 生效(401 无密码)
2https://radicale.unixcs.eu.org 公网可访问(HTTPS)
3Hermes 写入 → 手机出现任务(含时间块)不重复
4手机勾完成 → Obsidian 变 [x](含 FNS replace 实测验证)
5连续两天闭环无人工介入(验证 Hermes 次日滚动既有能力)

9. 风险

风险对策
镜像 CMD 挂载错配官方 /data+/config 布局 + 401 验证
PUT 无法建集合MKCOL + supported VTODO
时区偏移统一 本地−8h→UTC Z 规则,禁直接加 Z
探测-PUT 竞态If-None-Match: *
同文同日撞 UIDuniform+时间段+序号
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 已脱敏,请替换为你自己的地址。


相关: 00-索引-AI工程索引 8.10 vikunja-任务-计划