折腾日记:给 Hermes AI 助手搞了个进化日志自动更新
🛠️ 折腾日记:给 Hermes AI 助手搞了个进化日志自动更新
今天又是一个"本来只想修个小 bug,结果一头扎进去出不来了"的典型周末。
事情的起因很简单:我的 Hermes AI Agent 跑在一台 Oracle ARM64 的 1C1G 小机上,平时通过飞书跟我交互,时不时帮我干点活。它有个"进化日志"页面挂在 Typecho 博客上,用来展示 Agent 的能力成长轨迹——加载了多少技能、有哪些工具、一共跑了多少次任务。但问题是:这页面好久没更新了,上面的数据还是上周的。
用户(也就是我自己)问了一句:"你这进化日志怎么不更新了?"
于是,一场横跨安全审计、容器调试、Shell 脚本重构、PHP 动态渲染和 cron 表达式斗智斗勇的折腾之旅就此展开。
🔍 第一关:安全审计——先看看房子有没有漏风
既然要动工,先得摸清家底。我的 Hermes Agent 跑在 1Panel 管理的 Docker 容器里,底层是 Debian Trixie。扫了一圈,发现问题还真不少:
Dashboard 直接暴露在 0.0.0.0:9119 上,没有任何认证——这意味着同一网络内的任何人只要知道端口号,就能看到我的 Agent 状态面板。更离谱的是,.env 文件的权限是 644,也就是说任何能登录这台机器的用户都能读到里面的 API Key。
二话不说,chmod 600 .env,把备份文件全部清理干净,Dashboard 加上访问控制。安全底线先守住,才能安心搞功能。
🌐 第二关:浏览器工具——1C1G 的倔强
Hermes Agent 自带了浏览器工具,理论上可以自动打开网页、截图、抓取内容。但问题是——容器里根本没有 Chrome。
先试 Playwright 官方安装脚本:npx playwright install chromium。结果 165MB 的下载,在 1C1G 的小水管上跑到一半就超时了。不死心,换成 apt install chromium——好嘛,容器里根本没 sudo。再试直接 wget 下载 Chromium 二进制,跑到 85MB 再次超时。
1C1G 就是这样,它不拒绝你,但也不完全答应你。卡在一个暧昧的中间地带。
最后把目光转向了云服务:Browserbase 有免费额度(3 个并发浏览器,1 小时时长),Firecrawl 和 Browser Use 也都是备选方案。这件事暂时搁置,等后续接入云浏览器再说——毕竟今天的主角不是它。
🕐 第三关:进化日志——从"静态假数据"到"动态真流水"
回到主线任务:让进化日志自动更新。
首先检查 cron 任务——果然,网关重启之后 cron job 状态变成了 disabled。重新启用,跑了一遍,却发现只有卡片统计和页脚更新了,近期任务时间线纹丝不动。
打开源码一看,差点气笑了:时间线那一段是硬编码的静态 HTML。每次 cron 跑完,Shell 脚本只会更新几个数字,然后把同样的 HTML 片段原封不动地写回去。难怪永远显示一样的内容。
真正的改造开始了。
Shell 脚本升级:从"数数机器"到"数据采集器"
原来的 Shell 脚本只收集最基本的统计:技能数量、分类数量。我把它升级成一个完整的数据采集器,新增了以下维度的统计:
- 各分类下的技能分布
- 今日会话数量
- 历史 cron 运行次数
- 对接平台列表
- 记忆条目数
这些数据通过 scp 传到 VPS 上的一个 JSON 文件中,作为动态渲染的原料。
PHP 脚本重构:让时间线活起来
在 VPS 上,PHP 脚本负责管理时间线 JSON 文件。核心逻辑很简单:
- 维护一个最多 20 条记录的时间线数组
- 每次 cron 触发时,生成一条新记录(时间戳 + 当前统计快照 + 变更说明)
- 新记录插入数组头部,超过 20 条就截断
- 写入 JSON 文件供博客页面读取
这里踩了一个坑:emoji 卡片的正则匹配。博客页面上各种带有 emoji 的统计卡片,用正则去匹配替换时很容易错位。最后换成了基于标签上下文的匹配方式——先找到卡片前后的标记文字,再定位到具体数值位置进行替换,准确率直接从"随缘"提升到了 100%。
标记注入:让静态页面找到动态入口
为了让博客页面知道在哪里插入动态时间线,我在数据库的页面内容里加了一个 <!-- EVO_TIMELINE_INJECT --> 标记。PHP 渲染时扫描这个标记,找到后就把 JSON 里的时间线数据动态拼接成 HTML 注入进去。
现在,每 15 分钟,Agent 的进化日志就会自动多出一条新记录——"今天新增了 2 个会话"、"记忆库扩充了 5 条"、"XX 技能被调用了 3 次",像一本不断书写的成长日记。
⏱️ 第四关:cron 的"一次性"陷阱
部署完兴冲冲等了半小时——没更新。再等半小时——还是没更新。
回到 1Panel 的定时任务面板检查,发现一个让我哭笑不得的问题:cron 任务的类型是 "执行一次",而不是"重复执行"。也就是说,它跑完第一次之后就原地退休了。
删掉重建,cron 表达式写上 */15 * * * *,类型选"重复执行"。这下终于乖乖每 15 分钟跑一次了。
回头看这个问题,其实是一个典型的"想当然"错误——以为任务启用了就会一直跑,却忽略了执行模式这个关键参数。开发者的直觉在运维场景里,有时候反而是坑。
🧰 工具箱一览
这次折腾涉及的技术栈还真不少,列出来自己都觉得杂乱又有趣:
核心组件:Hermes Agent(Python)、DeepSeek API、Typecho 博客(PHP + MySQL)、LEMP Stack VPS、1Panel 容器管理、飞书消息平台
调试工具:SSH 密钥、MySQL 命令行、PHP 脚本、Shell 脚本、Python、SCP、curl、sed、grep、find——以及无尽的 echo "checkpoint" >> /tmp/debug.log
💡 写在最后
回头看这一天,其实核心改动并不复杂——就是让一个静态页面能动态展示数据。但真正花时间的,是那些"意外":安全漏洞、权限问题、硬编码的假数据、cron 的执行模式、正则匹配的边界情况……
这些"意外"恰恰是自托管最有魅力的部分。你不是在用一个封装好的 SaaS 产品,而是在亲手搭建一个会呼吸的系统。每一个坑都是你对系统理解加深的印记。
就像那个每 15 分钟更新一次的进化日志一样——折腾本身,就是最好的进化。
- 上一篇: 站点迁移记:从老站到新 VPS 的一次完整搬迁
- 下一篇: 没有了