<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>陆上江南</title>
  <subtitle>直道半生如汪洋，付一尾孤航。</subtitle>
  <link href="http://localhost:4321/"/>
  <link rel="self" href="http://localhost:4321/feed.xml"/>
  <updated>2026-08-26T10:58:19.000Z</updated>
  <author>
    <name>陆上江南</name>
    <email>kingstar718@foxmail.com</email>
  </author>
  <id>http://localhost:4321/</id>
    <entry>
      <title>memos 随记自动同步博客短文 #ai</title>
      <link href="http://localhost:4321/posts/memos-webhook-notes"/>
      <id>http://localhost:4321/posts/memos-webhook-notes</id>
      <updated>2026-08-26T10:58:19.000Z</updated>
      <content type="html">&lt;p&gt;memos 是我的随手记，博客短文区是 mars-blog 的时间线。想让两者打通：在 memos 里发一条公开随记，1-2 分钟后短文自动出现在博客上；删掉随记，短文也跟着消失。靠 memos 的 webhook 和 Cloudflare Pages Function 实现，中间踩了两个文档没写的坑，记下来。&lt;/p&gt;
&lt;h2&gt;链路：memos 到一篇短文&lt;/h2&gt;
&lt;p&gt;memos(ECS 上的 docker 容器)在随记创建、更新、删除时会 POST 到配置的 webhook URL。把这个 URL 指向博客的 &lt;code&gt;/api/memos-hook&lt;/code&gt;(Pages Function)，函数校验合法后把内容写成 &lt;code&gt;notes/memos-&amp;#x3C;id&gt;.md&lt;/code&gt; 放进 R2，再触发 Deploy Hook 重建静态站，短文就上线了。&lt;/p&gt;
&lt;p&gt;数据流全貌：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;memos 发公开随记
  → POST webhook(带 URL token)
  → /api/memos-hook(Pages Function)
  → 校验 token + 过滤非公开
  → 写 notes/memos-&amp;#x3C;id&gt;.md 到 R2
  → 触发 Deploy Hook
  → astro build 重新上线
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;坑一：0.29.1 的 webhook 不签名&lt;/h2&gt;
&lt;p&gt;官方文档写的是 Standard Webhooks 规范：投递带 &lt;code&gt;webhook-id&lt;/code&gt;、&lt;code&gt;webhook-timestamp&lt;/code&gt;、&lt;code&gt;webhook-signature&lt;/code&gt; 三个头，接收方用创建 webhook 时生成的 &lt;code&gt;whsec_&lt;/code&gt; 密钥验签。第一版代码就按这个写——结果 memos 投递的请求全部被我拒掉，日志里全是 &lt;code&gt;Failed to dispatch webhook ... status code: 401&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;抓包(临时把 webhook URL 指到 webhook.site)发现真相：&lt;strong&gt;memos 0.29.1 的投递只有 &lt;code&gt;content-type&lt;/code&gt; 一个头，没有任何签名&lt;/strong&gt;。签名机制是 0.29 之后的新版本才加的，我这个镜像的版本刚好处在新旧之间：webhook 能发，但发的是裸请求。文档描述的是新版行为，和 0.29.1 对不上。&lt;/p&gt;
&lt;h2&gt;坑二：payload 的字段和直觉不一样&lt;/h2&gt;
&lt;p&gt;抓到的请求体长这样：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;url&quot;: &quot;...&quot;,
  &quot;activityType&quot;: &quot;memos.memo.created&quot;,
  &quot;creator&quot;: &quot;users/wujinxing718&quot;,
  &quot;memo&quot;: {
    &quot;name&quot;: &quot;memos/DMp3VSztSZCkhWuB6EQzTo&quot;,
    &quot;create_time&quot;: { &quot;seconds&quot;: 1787739699 },
    &quot;content&quot;: &quot;你好，这是通过 memos webhook 发来的一条随记&quot;,
    &quot;visibility&quot;: 3
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三个地方和直觉不同：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;visibility&lt;/code&gt; 是&lt;strong&gt;数字枚举&lt;/strong&gt;：3 才是 PUBLIC，1 是 PRIVATE，2 是 PROTECTED。不能用字符串 &lt;code&gt;&quot;PUBLIC&quot;&lt;/code&gt; 去比&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_time&lt;/code&gt; 是 &lt;code&gt;{seconds}&lt;/code&gt; 对象，不是 ISO 字符串，要 &lt;code&gt;new Date(seconds * 1000)&lt;/code&gt; 转换&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memo.name&lt;/code&gt; 形如 &lt;code&gt;memos/DMp3VSzt...&lt;/code&gt;，id 是 UUID 不是自增数字，slug 直接取 &lt;code&gt;/&lt;/code&gt; 后面的部分就行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;鉴权：URL token 兜底&lt;/h2&gt;
&lt;p&gt;0.29.1 不签名，webhook 端点又必须鉴权(否则任何人 POST 都能往博客塞内容)。方案：memos 的 webhook URL 里带一个 query 参数：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://blog.wujinxing.site/api/memos-hook?token=whsec_xxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;函数先校验 &lt;code&gt;token&lt;/code&gt; 是否等于环境变量里的 &lt;code&gt;MEMOS_WEBHOOK_SECRET&lt;/code&gt;；同时保留新版 HMAC 签名校验作为第二通道——将来升级 memos 后签名请求也能过，两条路并存不用返工。&lt;/p&gt;
&lt;h2&gt;幂等：slug 就是 memo id&lt;/h2&gt;
&lt;p&gt;短文文件名用 &lt;code&gt;notes/memos-&amp;#x3C;id&gt;.md&lt;/code&gt;，id 来自 &lt;code&gt;memo.name&lt;/code&gt;。这样天然幂等：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;同一个 memo 反复投递、编辑覆盖写同一个文件，不会刷出重复短文&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memos.memo.deleted&lt;/code&gt; 事件到达时删同一个文件，随记和短文一一对应&lt;/li&gt;
&lt;li&gt;只在 &lt;code&gt;visibility = 3&lt;/code&gt;(PUBLIC) 时写，PRIVATE/PROTECTED 的随记不会漏到公开博客&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;验证&lt;/h2&gt;
&lt;p&gt;改动后验证分两步。第一步模拟：用 webhook.site 捕获一条真实投递，确认 payload 结构和请求头；第二步实测：在 memos 发一条公开随记，等重建完成，博客短文区出现对应内容，R2 里也多出 &lt;code&gt;notes/memos-&amp;#x3C;id&gt;.md&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;实测抓包比读文档快得多。memos 版本跨度大、行为差异明显，遇到 401 别急着怀疑鉴权逻辑，先确认投递的真实长相。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-26 18:52</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-26T10:52:02.000Z</updated>
      <content type="html">&lt;p&gt;着眼于现在，不要让宏大叙事偷走自己的注意力，把日子沉下来。&lt;/p&gt;
&lt;p&gt;圣人之道，吾性自足，向之求理于事物者误也。&lt;/p&gt;
&lt;p&gt;本自具足，无需外求。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-26 18:29</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-26T10:29:51.000Z</updated>
      <content type="html">&lt;p&gt;你好，这是通过memos webhook发来的一条随记。3&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-26 04:16</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-25T20:16:14.648Z</updated>
      <content type="html">&lt;p&gt;夜来幽梦忽还乡，明月夜，短松冈。&lt;/p&gt;
&lt;p&gt;一次梦境就串联起所有旧梦，得曾未有，始终无得。&lt;/p&gt;
&lt;p&gt;只是一遍一遍地陈述内心不愿面对的冷淡。&lt;/p&gt;
&lt;p&gt;是初始的欢愉与主动的代价。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>一台 2G ECS 的 pi-web 运维记录 #ai</title>
      <link href="http://localhost:4321/posts/pi-web-caddy-ops"/>
      <id>http://localhost:4321/posts/pi-web-caddy-ops</id>
      <updated>2026-08-25T03:21:40.000Z</updated>
      <content type="html">&lt;p&gt;给 pi-web(我的 AI 编码助手控制台)做的一次整机优化：2G 内存 ECS，Debian 12，Caddy 反代 + docker 一堆服务。记下问题和解法，下次直接抄。&lt;/p&gt;
&lt;h2&gt;Caddy 认证修复&lt;/h2&gt;
&lt;p&gt;四个坑，都是配置层面：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;根因&lt;/th&gt;
&lt;th&gt;修复&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;登录页 401 死路&lt;/td&gt;
&lt;td&gt;路径匹配器 &lt;code&gt;/auth/*&lt;/code&gt; 不匹配裸路径 &lt;code&gt;/auth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;改 &lt;code&gt;@portal path /auth /auth/*&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;改 Caddyfile 后不生效&lt;/td&gt;
&lt;td&gt;compose &lt;code&gt;configs.content&lt;/code&gt; 不随内容更新&lt;/td&gt;
&lt;td&gt;改 bind mount &lt;code&gt;Caddyfile:/etc/caddy/Caddyfile:ro&lt;/code&gt;，改文件即生效&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;登录后不跳回页面&lt;/td&gt;
&lt;td&gt;缺 &lt;code&gt;trust login redirect uri&lt;/code&gt;，redirect_url 被丢弃&lt;/td&gt;
&lt;td&gt;加 &lt;code&gt;trust login redirect uri domain &amp;#x3C;域名&gt; path prefix /&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 分钟重新登录&lt;/td&gt;
&lt;td&gt;JWT 令牌默认有效期 900 秒&lt;/td&gt;
&lt;td&gt;加 &lt;code&gt;crypto default token lifetime 172800&lt;/code&gt;(48 小时)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;教训：&lt;code&gt;path /auth/*&lt;/code&gt; 不匹配 &lt;code&gt;/auth&lt;/code&gt; 本体；compose 的 configs 内容改了不重发，配置源要 bind mount + &lt;code&gt;caddy reload&lt;/code&gt; 热加载。&lt;/p&gt;
&lt;h2&gt;内存：2G 机器的三板斧&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;2G swapfile，swappiness=30，写入 fstab&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NODE_OPTIONS=--max-old-space-size=512&lt;/code&gt;，防 node 子进程各自申请大堆导致 pnpm OOM&lt;/li&gt;
&lt;li&gt;pnpm 并发调低：&lt;code&gt;child-concurrency=1&lt;/code&gt;、&lt;code&gt;network-concurrency=8&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;停掉可恢复的服务：tailscaled、portainer、memos、exim4&lt;/li&gt;
&lt;li&gt;可用内存 640MB → 808MB&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;日志滚动与磁盘&lt;/h2&gt;
&lt;p&gt;journald 没设上限，悄悄占掉 1.1GB。设 &lt;code&gt;SystemMaxUse=50M&lt;/code&gt;、&lt;code&gt;SystemKeepFree=256M&lt;/code&gt; 后 vacuum，回收 928MB。docker 构建缓存 prune 掉 3.9GB，再删一批无引用镜像，磁盘可用从 6.5G 回到 11G。&lt;/p&gt;
&lt;p&gt;整个过程的先后顺序很重要：先建 swap 再动构建，先设 NODE_OPTIONS 再跑 pnpm，每一步都在 OOM 边缘试出来的。&lt;/p&gt;
&lt;h2&gt;sessiond 启动 OOM：全局插件一次性预热的代价&lt;/h2&gt;
&lt;p&gt;当天下午 pi-web 起不来，网页端会话接口全部 502/503，&lt;code&gt;pi-web-sessiond&lt;/code&gt; 每 ~27 秒崩一次。日志反复出现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先排掉三个嫌疑：浏览器连接(停掉 server 切断浏览器后依旧崩)、会话文件(移走 668 行/1.4MB 的旧会话依旧崩)、系统内存(崩溃是 V8 堆上限 ABRT，不是内核 OOM killer)。&lt;/p&gt;
&lt;p&gt;根因是当天 12:27 给 pi agent 新装了一批包(&lt;code&gt;rpiv-*&lt;/code&gt;、&lt;code&gt;recheck&lt;/code&gt;、&lt;code&gt;@ast-grep&lt;/code&gt; 等)，&lt;code&gt;settings.json&lt;/code&gt; 全局插件扩容后，12:28 一重启必崩。sessiond 启动会执行 &lt;code&gt;global extension provider bootstrap&lt;/code&gt;：把全部全局插件一次性加载、编译、注册进共享 runtime 并冻结，瞬时 V8 堆需求超过 512MB。而 &lt;code&gt;/etc/profile.d&lt;/code&gt; 里的全局 &lt;code&gt;NODE_OPTIONS=--max-old-space-size=512&lt;/code&gt; 把堆卡死在 512MB，启动即 ABRT，无限重启。&lt;/p&gt;
&lt;p&gt;验证方法：上限提到 1GB 后 18 秒完成引导(&lt;code&gt;global extension provider baseline bootstrapped and frozen&lt;/code&gt;)，稳定 ~200MB。峰值有界，不是内存泄漏。&lt;/p&gt;
&lt;p&gt;设计取舍：sessiond 先「预热共享 runtime 再冻结」，换来所有会话秒开、共享 provider，代价是启动时一次性加载全部插件；jiti 编译 TS 插件 + provider/模型目录/工具注册，瞬时峰值高。&lt;code&gt;pi&lt;/code&gt; CLI 没这个问题——按需加载，没有预热步骤，同一 512MB 限制下跑 TUI 峰值 ~270MB、稳定 ~190MB。&lt;/p&gt;
&lt;p&gt;修复只放宽 &lt;code&gt;pi-web-sessiond.service&lt;/code&gt; 的堆上限，全局 512MB 限制不动：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ExecStart=... bash -lc &quot;export NODE_OPTIONS=--max-old-space-size=2048; exec pi-web-sessiond&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先改 1024 验证能启动，再改 2048 留余量；原文件备份在 &lt;code&gt;~/.config/systemd/user/pi-web-sessiond.service.bak-20260825&lt;/code&gt;。2G 上限下验证 90 秒+：稳定 ~194MB、0 OOM，系统可用 ~1GB，swap ~280MB。恢复方式：把那行 2048 改回 1024/512，&lt;code&gt;systemctl --user daemon-reload &amp;#x26;&amp;#x26; systemctl --user restart pi-web-sessiond&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;全局插件重量清单(&lt;code&gt;settings.json&lt;/code&gt;，agent npm 目录共 391MB)：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;插件&lt;/th&gt;
&lt;th&gt;体积&lt;/th&gt;
&lt;th&gt;关键依赖/原生组件&lt;/th&gt;
&lt;th&gt;加载成本&lt;/th&gt;
&lt;th&gt;建议&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;@jmfederico/pi-web 本体&lt;/td&gt;
&lt;td&gt;5.9M&lt;/td&gt;
&lt;td&gt;node-pty(62M)、xterm、codemirror、fastify&lt;/td&gt;
&lt;td&gt;必带&lt;/td&gt;
&lt;td&gt;保留&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pi-lens&lt;/td&gt;
&lt;td&gt;27M&lt;/td&gt;
&lt;td&gt;@ast-grep(108M)、web-tree-sitter、recheck、13 个 wasm/原生&lt;/td&gt;
&lt;td&gt;最高&lt;/td&gt;
&lt;td&gt;不常用就移出全局&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pi-web-access&lt;/td&gt;
&lt;td&gt;7.3M&lt;/td&gt;
&lt;td&gt;linkedom、unpdf、turndown、undici&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;按需&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pi-mcp-adapter&lt;/td&gt;
&lt;td&gt;2.6M&lt;/td&gt;
&lt;td&gt;MCP client、keyring、recheck、zod&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;不用 MCP 就移出&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@juicesharp/rpiv-ask-user-question&lt;/td&gt;
&lt;td&gt;408K&lt;/td&gt;
&lt;td&gt;rpiv-config、typebox(TS 源码)&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;用到就留&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@juicesharp/rpiv-todo&lt;/td&gt;
&lt;td&gt;216K&lt;/td&gt;
&lt;td&gt;rpiv-config、typebox(TS 源码)&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@jmfederico/pi-relay&lt;/td&gt;
&lt;td&gt;184K&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;极低&lt;/td&gt;
&lt;td&gt;保留&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;2G 是「护栏上限」不是实际占用：实际启动峰值 ~600MB、稳态 ~200MB，设 2G 只为未来插件留余量。真正红线是物理内存 1.7G + 2G swap：swap 慢，峰值逼近 1.2-1.4G 会先明显卡顿，再上就内核 OOM 杀进程(可能误伤 caddy/docker)。插件按需装，重插件尽量项目级加载，每加一批插件重启一次看启动峰值；终极方案是 ECS 升配到 4G。全局 512MB 上限对 codex 等其它 node 进程仍有效，没动。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-19 19:29</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-19T11:29:30.072Z</updated>
      <content type="html">&lt;p&gt;今天重构一个很老的项目，vue2、element 的，使用 codex dsv4flash，快结束时 opencode 周限额了🤣，只好换用公司 glm5.2，下午实在实在太慢，没忍住推荐自己另一个号开 go，重新用回 dsv4flash，于是一切就平静起来😐。&lt;/p&gt;
&lt;p&gt;虽然 glm5.2 可能效果更好一点，但是真的太慢太慢了，迭代速度非常重要，加上 codex 严谨的风格，使得后训练的 dsv4flash，也能快速完成任务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://localhost:4321/media/74f8535f-025d-442c-82d0-ebdcb8409597&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-19 19:24</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-19T11:24:36.867Z</updated>
      <content type="html">&lt;p&gt;今日份前海妈湾，大风。&lt;/p&gt;
&lt;p&gt;山樾湾已交付，不能封窗。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://localhost:4321/media/007248e9-268d-469d-861f-5c3705f0a1d7&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-17 11:45</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-17T03:45:43.057Z</updated>
      <content type="html">&lt;p&gt;我们漫长的生活里，究竟什么才是值得的，什么才是需要被回忆的，什么时候才是需要往前的。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>博客：Workers、自部署与 Pages Functions #ai</title>
      <link href="http://localhost:4321/posts/2026-08-17-1786932968403"/>
      <id>http://localhost:4321/posts/2026-08-17-1786932968403</id>
      <updated>2026-08-17T02:16:08.403Z</updated>
      <content type="html">&lt;p&gt;mars-blog 的架构在三周里换了三次：先在 Cloudflare Workers 上做全动态站，然后搬去自己的服务器自部署，最后回到Cloudflare，但改成「静态站 + 边缘函数」。三次迁移没有一次是白折腾——同一个问题被越拆越清楚：一个人写的博客，到底需要多少「动态」？&lt;/p&gt;
&lt;h2&gt;起点：为什么离开纯静态站&lt;/h2&gt;
&lt;p&gt;前身是 astro-paper-blog：内容以 Markdown 存在 git 仓库，发布 = 写文件、提交、推送。在电脑前这条链路很顺，离开电脑就断了——手机上发一条带图短文，要在快捷指令里搭二十多个动作；长文干脆没法写。&lt;/p&gt;
&lt;p&gt;我最初的判断是「缺一个写作后台」，于是做了 v1：一个带后台的动态博客。回头看，这个判断只对了一半。&lt;/p&gt;
&lt;h2&gt;v1：Workers + D1 + R2 的全动态站&lt;/h2&gt;
&lt;p&gt;技术栈一句话：Astro 全站 SSR 跑在 Cloudflare Workers 上（@astrojs/cloudflare），内容存 D1，图片存 R2，后台是 /admin 里的 React island 编辑器（CodeMirror），登录先用 GitHub OAuth、后来换成单口令 + PBKDF2。&lt;/p&gt;
&lt;p&gt;// wrangler.jsonc（v1）：一个项目、一个部署、两个绑定&quot;d1_databases&quot;: [{ &quot;binding&quot;: &quot;DB&quot;, &quot;database_name&quot;: &quot;mars-blog&quot; }], &quot;r2_buckets&quot;: [{ &quot;binding&quot;: &quot;MEDIA&quot;, &quot;bucket_name&quot;: &quot;mars-blog-media&quot; }]&lt;/p&gt;
&lt;p&gt;跑起来之后，写作后台确实解决了手机发布的问题。但三个代价慢慢显出来：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公开页面每次访问都要跑一遍 SSR 加若干条 D1 查询，首页尤其亏，只能靠 60 秒边缘缓存兜着。&lt;/li&gt;
&lt;li&gt;内容锁在数据库里。D1 是唯一真相源，「哪天想搬走就能搬走」靠的是 GitHub Actions 每日拉 /api/export 把 Markdown 提交回仓库——这是一根明晃晃的保险丝，说明数据不归我。&lt;/li&gt;
&lt;li&gt;Workers 的运行时上限会撞到具体功能。PBKDF2 的迭代次数有硬上限，只能降到 10 万次；服务端跑不了 sharp，图片压缩只能放在浏览器端用 Canvas 做。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第三条后来成了整条项目线最持久的约束：图片始终在浏览器里压成 400/800/1600 三档 webp + jpeg 再上传，三代都没改过。&lt;/p&gt;
&lt;h2&gt;v2：自部署，脱离 Cloudflare&lt;/h2&gt;
&lt;p&gt;然后我决定把博客搬回自己的服务器：Node + SQLite（node:sqlite）+ 本地磁盘，Docker 镜像发布（GHCR / Docker Hub），Nginx反代 + microcache，登录是环境变量里的单口令 + HMAC 签名 cookie + 同 IP 十分钟五次限流，/settings 里还能配 S3 兼容存储。&lt;/p&gt;
&lt;p&gt;自部署把 v1 的约束全解掉了：没有运行时硬上限，SQLite 就是文件，备份就是 .backup 加目录同步。但换来一台要养活的服务器：镜像要构建和同步、容器运行时要升级、备份要自己排 cron、反代缓存与会话规则要自己维护。&lt;/p&gt;
&lt;p&gt;v2 没有活多久。它不是不好，而是和这个站的流量形态不匹配：读者看的是同一批页面，真正需要「动态」的只有作者一个人（登录、编辑、审核），加上评论和浏览数几个低频动作。为了这些养一台服务器，重了。&lt;/p&gt;
&lt;h2&gt;v3：静态站 + Pages Functions&lt;/h2&gt;
&lt;p&gt;第三次迁移回到 Cloudflare，但换了个思路：把「全站动态」拆成「静态内容 + 一薄层边缘动态」。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;内容真相源回到文件：R2 内容桶里的 Markdown（posts/、notes/、pages/ 三个前缀）。构建期 scripts/sync-content.mjs 用 S3 协议把 R2 拉到 src/content/，Astro 以 output: static 渲染成纯 HTML。内容重新变成能 grep、能带走的东西。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;动态能力收敛到 Pages Functions：口令登录与 HMAC 会话、/api/content 读写 R2 并触发 Deploy Hook 重建、评论与浏览量走 D1、图片经 http://localhost:4321/media/* 从私有 R2 桶代理出图。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;D1 从「全部内容」退到「只有动态数据」：评论（post_comments）、浏览量（page_views）、限流（rate_limits）三张表。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;保存 = 把 Markdown 写回 R2 + 触发 Deploy Hook，Pages 重建后约一两分钟上线：PUT /api/content/posts/xxx.md → R2 → Deploy Hook → 重建 → 上线&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为什么这次是对的：一个个人博客的阅读流量是压倒性的，静态页零计算、全缓存、免费额度用不完；需要动态的部分（一个月写几篇、几条评论、几个浏览数）边缘函数完全扛得住。&lt;/p&gt;
&lt;p&gt;v3 有一个明确的取舍：编辑保存不是即时的，要等一两分钟重建。代价换来的是读者端永远是缓存友好的纯静态页——作者等得起这两分钟，读者等不起每次 SSR。&lt;/p&gt;
&lt;h2&gt;三代没丢的东西&lt;/h2&gt;
&lt;p&gt;三次迁移动过界面、数据层、部署形态，但有四件事从头到尾没变：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;阅读页就是编辑界面。v1 最初有 /admin 后台，很快拆掉了——评论是回应某段话的，草稿是某天写了一半的，都得和所在位置待在一起才判断得了。登录之后点铅笔就地变编辑态，这个形态从 v1 后期一路沿袭到 v3。&lt;/li&gt;
&lt;li&gt;图片在浏览器里压。服务端跑不了 sharp 的约束，反而沉淀成了固定方案：端上压三档再传，谁也不用在服务端做图像处理。v3后期主档切成 jpeg、只下发 400/800 两档，1600 只归档。&lt;/li&gt;
&lt;li&gt;正文只存短引用：&lt;code&gt;![](http://localhost:4321/media/&amp;#x3C;uid&gt;)&lt;/code&gt;。渲染时才查变体拼 srcset，换域名、换存储只用改渲染一处，正文不动。&lt;/li&gt;
&lt;li&gt;单用户的极简。没有用户表、没有标签系统、没有分享按钮；登录只有一个口令加一个签名 cookie，「能证明你输对过口令」就够了。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;三代演进可以压缩成一个问题：一个博客需要多少动态？ v1 的答案是「内容也是动态的」（全存数据库），v2 的答案是「要动态就得自部署」，v3 的答案是「内容可以是静态文件，动态只留边缘一层」。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;v1&lt;/th&gt;
&lt;th&gt;v2&lt;/th&gt;
&lt;th&gt;v3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;托管&lt;/td&gt;
&lt;td&gt;Cloudflare Workers&lt;/td&gt;
&lt;td&gt;自部署 Docker + Nginx&lt;/td&gt;
&lt;td&gt;Cloudflare Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内容&lt;/td&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;R2 Markdown，构建期拉取&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;图片&lt;/td&gt;
&lt;td&gt;R2&lt;/td&gt;
&lt;td&gt;本地磁盘 / S3&lt;/td&gt;
&lt;td&gt;私有 R2 + &lt;code&gt;http://localhost:4321/media/*&lt;/code&gt; 代理&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;动态数据&lt;/td&gt;
&lt;td&gt;D1&lt;/td&gt;
&lt;td&gt;SQLite&lt;/td&gt;
&lt;td&gt;D1（仅评论 / 浏览量 / 限流）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;保存&lt;/td&gt;
&lt;td&gt;写 D1&lt;/td&gt;
&lt;td&gt;写 SQLite&lt;/td&gt;
&lt;td&gt;写 R2 + Deploy Hook 重建&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;登录&lt;/td&gt;
&lt;td&gt;OAuth → 口令&lt;/td&gt;
&lt;td&gt;口令&lt;/td&gt;
&lt;td&gt;口令&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;内容回归文件、动态收缩到边缘，这大概是我对这个博客架构最满意的一版。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-14 20:59</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-14T12:59:57.671Z</updated>
      <content type="html">&lt;p&gt;今日份回家之十字路口。&lt;/p&gt;
&lt;p&gt;夏至已经过去，晚 7 点的天也在逐日变得低沉暗淡。&lt;/p&gt;
&lt;p&gt;日子总是如此迅捷地过去，留不下一丝感觉的漫长。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://localhost:4321/media/324a2a9f-13ce-4886-98c8-d0c07feab05b&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;http://localhost:4321/media/e001c648-6373-45c0-bf00-24f3c9634172&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;http://localhost:4321/media/66e25ca7-e935-4572-9393-d2f5de5b65cd&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-14 18:56</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-14T10:56:19.091Z</updated>
      <content type="html">&lt;p&gt;高中的时候，不怎么喜欢篮球，只是有一位同学很是喜欢，那会听他念名字，甚至不知道威斯布鲁克是那个威。&lt;/p&gt;
&lt;p&gt;一晃好多年过去，喜欢威少的，喜欢麦迪的，高中宿舍那张挡在门旁窗户的纳什的那些记忆，都已通通模糊出去了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://localhost:4321/media/bd0fb416-67b5-42d8-b54a-cbc18ad3c97d&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-14 18:35</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-14T10:35:49.057Z</updated>
      <content type="html">&lt;p&gt;blog 又从 docker 回归到 cf pages ➕functions，阿里云小机器内存爆炸直接无法访问太尴尬了。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-13 10:48</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-13T02:48:34.000Z</updated>
      <content type="html">&lt;p&gt;在codex追赶的那会，我大概就猜到，未来所有的大模型厂商都会发布自己的harness。&lt;/p&gt;
&lt;p&gt;月初ds发布了后训练优化的ds v4，在codex上的效果非常好。&lt;/p&gt;
&lt;p&gt;今天发布了ds pro新版本，晚些会发布他们自己的harness，拭目以待。&lt;/p&gt;
&lt;p&gt;Claude code一家独大的时代过去了，A➗的时代也快过去了，而这还不到一年。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-11 12:31</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-11T04:31:12.000Z</updated>
      <content type="html">&lt;p&gt;昨天我儿子好像知道了怎么尖叫，六个月大的小宝，结果晚上拍睡睡不着一直尖叫，然后被妈妈打了，还是姥姥进来抱走哄睡再送回来的。也是很神奇，我扛着还是有点叽叽歪歪、哭哭啼啼的，到他姥姥那立马就好了安静了。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-11 12:29</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-11T04:29:04.000Z</updated>
      <content type="html">&lt;p&gt;上月的 Opus5 确实有我刚用上 codex+gpt5.5 那种，指哪打哪儿的感觉，在想着要不要续费，直接把我号干没了，这还是个 giffgaff 的号，那会刚注册上，giffgaff 没多久又要停，也是赶晚集。🤣。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-11 12:26</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-11T04:26:45.000Z</updated>
      <content type="html">&lt;p&gt;6 号到期的 Claude pro，未续费，今天就给我账号封了，A➗确实名不虚传。&lt;/p&gt;
&lt;p&gt;今早有个简单任务复制 copy 一个新接口、新增配置项、版本升级，然后该功能需要同步到其他四个类似项目上，算是很简单的逻辑，一套提示词，glm5.2 慢但是严谨的做出来，dsv4pro 甚至搞错了 git 库，最后也是修正完做出来了，然后新的 0731dsv4flash+codex很快做完，甚至还知道编译下有无问题，glm5.2 并没有编译，于是订阅了首月 opencode go 看看效果。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-08 09:29</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-08T01:29:50.000Z</updated>
      <content type="html">&lt;p&gt;像是开满整夜的路灯&lt;/p&gt;
&lt;p&gt;迎接晚霞，送来黎明&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-08-06 11:42</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-08-06T03:42:48.000Z</updated>
      <content type="html">&lt;p&gt;昨日下班时，时间晚 7 点。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://localhost:4321/media/d77b39c0-64c4-4a18-a5e2-92598bd567ca&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-07-31 12:40</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-07-31T04:40:01.000Z</updated>
      <content type="html">&lt;p&gt;今天看到一位北京的友人买了一辆理想 i6，去年也是我很想买却迟迟不发布的车型。&lt;/p&gt;
&lt;p&gt;没有想到的降价，也没有想到我提前妥协做决定。&lt;/p&gt;
&lt;p&gt;有时候事情本不想是这样，事后有再多情绪与思考，通通跌在没有后悔药。&lt;/p&gt;
&lt;p&gt;可如果时光真的倒流，我又会如何选呢，没有答案。&lt;/p&gt;</content>
    </entry>
    <entry>
      <title>随记 2026-07-28 19:05</title>
      <link href="http://localhost:4321/notes"/>
      <id>http://localhost:4321/notes</id>
      <updated>2026-07-28T11:05:00.000Z</updated>
      <content type="html">&lt;p&gt;多图的样子。markdown 里图片之间不空行就会落进同一段，渲染成大图 + 缩略图条：默认显示第一张，点缩略图切换。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://localhost:4321/media/e5c15be0-8017-4b43-a890-5e07abe91d0b&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;http://localhost:4321/media/e8aed9b2-c1c9-4b2b-86c0-115b4fb6d789&quot; alt=&quot;&quot;&gt;
&lt;img src=&quot;http://localhost:4321/media/b5674ec4-0ba8-4ebc-bb41-67a4f36a2d02&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</content>
    </entry>
</feed>