本文以 Skill 形式编写,可直接作为 Muse 的技能使用:把以下元数据保存为 SKILL.md,Muse 在接到搭建博客的需求时即可按本文执行;也可以直接按本文步骤手动执行。默认环境为单台 Muse VM,不要求其它设备。

name: muse-vm-blog-hosting
description: "在 Muse 虚拟机上搭建 Typecho + PostgreSQL 博客,经 Cloudflare Tunnel 对外提供公网访问,并在 VM 被替换后自行恢复。适用于用户要求用 Muse 主机免费搭建博客站的场景。"

适用场景

当用户提出以下任一需求时,适用本技能:

  • 在 Muse 虚拟机上搭建一个博客站
  • 博客程序为 Typecho,数据库为 PostgreSQL
  • 博客需经域名在公网访问,域名由 Cloudflare 管理
  • VM 被替换后博客应自行恢复,不得每次手动重装

不适用场景:用户要求博客跑在自有服务器、虚拟主机或其它云厂商实例上;用户要求使用 MySQL、WordPress 等其它程序和数据库组合。上述情况应另选对应技能,不得硬套本文。

前置条件:需用户提供的参数

以下参数缺失时,应先停下询问,不得自行编造。

参数说明示例(仅为示例,应替换为实际值)
博客域名须已在 Cloudflare 拥有对应 zone,或可通过外部 CNAME 接入muse.toolcalls.org
Cloudflare 登录方式三选一:①用户在云端浏览器登录后交还控制权 ②用户经安全卡片提供 API Token ③用户指定另一台已登录 Cloudflare 的助手创建 Tunnel任选其一
Tunnel 名称小写英文加连字符,已被占用时应更换,不得覆盖已有 Tunnelmuse-vm
Typecho 站点标题安装向导中填写Hello World
Typecho 管理员账号和密码安装向导中填写,密码仅经安装页或安全卡片提供,不得写入文章、脚本和记忆用户现场提供

Muse 自行生成、无需用户编造的参数:

  • 数据库密码:随机生成一次,存放于家目录的 .db-password(600 权限)。安装后该密码还会写入 Typecho 的 config.inc.php,后续应复用该文件,不得每次重设。
  • Tunnel 运行 token:Cloudflare 创建 Tunnel 后给出,存放于家目录的 .tunnel-token(600 权限),仅供 cloudflared 使用,不得贴入文章、聊天记录和代码仓库。
  • 数据库名和用户名:可直接使用 typecho。

步骤 0:核查

在新 VM 上应先核查以下内容,核查结果决定后续路径:

  1. VM 的持久性边界:确认家目录为持久存储,/etc 中的配置在替换后会丢失;确认 systemd 默认目标为 default.target,单元应同时挂载 WantedBy=default.target multi-user.target,仅挂载后者时,替换后不会自启。
  2. 域名的 Cloudflare 归属:确认域名的 NS 为 Cloudflare,并确认待用子域名当前不存在(NXDOMAIN)。已存在的记录不得覆盖,应更换子域名。
  3. Typecho 的既有状态:若家目录中已有 www/config.inc.php,表明已安装完成,不得重装、不得重置数据库,仅做恢复和域名接入。

步骤 1:安装 Typecho + PostgreSQL(VM 内)

本文示例版本:Typecho v1.3.0、PostgreSQL 16、PHP 8.3、nginx 1.24。版本可替换,但以下规则不应替换:

  1. 安装软件包:nginx、php-fpm、php-pgsql、php-mbstring、php-xml、php-curl、php-zip、postgresql、unzip、curl。恢复脚本中仅在缺包时安装,不得每次重装。
  2. PostgreSQL 数据目录不得使用默认的 /var/lib/postgresql,该路径在 VM 替换后会丢失。应存放于家目录,例如 ~/workspace/typecho/pgdata,属主设为 postgres、权限设为 700。家目录的父目录应赋予 postgres 和 www-data 可执行权限,否则上述用户无法进入家目录。
  3. 应使用自定义 systemd 单元运行 PostgreSQL,数据目录指向家目录路径,仅监听 127.0.0.1:5432。系统自带的默认集群数据位于 /var,不应使用。
  4. 数据库和用户仅创建一次:数据库名 typecho、用户名 typecho、密码读取上述 .db-password 文件。已存在时不得重建,不得重置已安装 Typecho 的密码。
  5. Typecho 程序存放于家目录,例如 ~/workspace/typecho/www,从官方发布包下载并解压至该路径。nginx 监听 VM 本机的 8080,根目录指向该 www 路径,PHP 经 php-fpm 的 socket 处理,伪静态规则为 try_files $uri $uri/ /index.php?$args。
  6. 安装向导:用户在浏览器中打开安装页,填写数据库主机 127.0.0.1、端口 5432、数据库名、用户名、密码,再填写站点标题和管理员账号密码。安装完成后应确认:首页可打开,安装页 302 跳回首页(表明已安装完成,无需再次安装)。

步骤 2:公网入口:Cloudflare Tunnel

判断依据:Typecho 位于 VM 本机 8080,Cloudflare Workers 和 DNS 代理无法直接访问该地址,仅凭二者无法实现公网访问。可行方式为 Cloudflare Tunnel:由 VM 上的 cloudflared 主动向外连接 Cloudflare,Cloudflare 再将域名流量转发回 VM。

  1. 在 Cloudflare Zero Trust 中新建 Tunnel,名称使用前置条件中用户提供的名称,不得覆盖已有 Tunnel。
  2. 为 Tunnel 添加 Public Hostname:域名使用前置条件中用户提供的子域名,服务类型为 HTTP,服务地址为 http://localhost:8080。该 localhost 指运行 cloudflared 的 VM 本机。
  3. 确认域名 zone 中已自动生成该 DNS 记录(CNAME 至 <Tunnel ID>.cfargotunnel.com,proxied)。若 zone 在当前登录账号中不存在,应立即停止并汇报,不得新建 zone、不得修改 NS、不得注册域名。
  4. 运行 token 存入家目录的 .tunnel-token(600),不得对外公开。
  5. cloudflared 二进制存放于家目录(VM 替换后仍保留),并以 systemd 单元运行 cloudflared tunnel run --token,Restart=always。单元同样应挂载 default.target。
  6. 仅在公网返回 200 后,方可将 Typecho 的站点地址修改为公网域名。修改方式为数据库中 typecho_options 的 siteUrl 改为 https://域名。公网未通之前不得修改该项。

步骤 3:合并进 5 分钟看门狗(VM 替换后复活)

VM 内可另设短周期循环检查,但该检查本身也会在 VM 替换时被清除,因此还需设置一个 5 分钟一轮的兜底检查。

兜底检查每轮应执行:

  1. 检查 VM 上全部相关服务是否为 active:typecho-postgres、nginx、php-fpm、cloudflared-tunnel;检查 8080、5432 是否在监听;从 VM 侧 curl 一次公网域名,应返回 200。
  2. 全部通过时安静结束,不向用户发送消息。
  3. 按组恢复,不得全量重装:

    • Typecho 组:仅在缺包时安装软件包,仅在缺数据时初始化数据目录,仅在缺文件时下载 Typecho,数据库密码复用 .db-password,不得重置已安装配置。
    • Cloudflare 组:仅在缺二进制时下载,token 文件缺失时应明确报错,不得编造 token。
  4. 恢复后重新全量复查一次,成功则安静结束;仍失败时方可向用户汇报:失败项、已执行的操作、恢复脚本的报错内容。
  5. 恢复脚本应事先实测:主动停止一个服务后再运行恢复脚本,确认其可在可接受时间内自行恢复。不得等到 VM 替换当天才首次运行恢复脚本。

如用户另有其它服务需纳入检查,应按相同模式追加服务名和监听端口,不得将无关服务混入同一恢复脚本。

校验清单

执行完成后应逐项核对,任一项不通过不得交付:

  • [ ] VM 本机 curl http://127.0.0.1:8080/ 返回 200
  • [ ] systemctl is-active 中 typecho-postgres、nginx、php-fpm、cloudflared-tunnel 均为 active
  • [ ] 从 VM 出口代理 curl https://域名/ 返回 200,且标题为安装时填写的标题
  • [ ] 数据库中 siteUrl 已为公网域名
  • [ ] 恢复脚本已实测:主动停止一个服务后可自行恢复
  • [ ] 5 分钟看门狗已包含上述服务和公网 200 检查

排障表

现象应先检查的内容处理方式
cloudflared 持续重连,QUIC 失败网络环境是否封禁 UDP改用 --protocol http2,经 TCP 连接,不得硬等 QUIC
argotunnel 域名解析为 198.18.x 类地址,代理无法 CONNECT沙箱 DNS 是否返回 fake-IP使用实测的真实边缘 IP 写入 hosts,不得直接修改只读的 /etc/hosts,应以 bind-mount 覆盖
cloudflared 重启后无法启动,报错 metrics 绑定到非本机 IP/etc/hosts 是否在重启时被截空不得使用 grep /etc/hosts > /run/hosts.cf; mount --bind /run/hosts.cf /etc/hosts 的写法。重启时 /etc/hosts 本身即为该文件,grep 会先截空输入文件,导致 localhost 丢失,metrics 随后绑定到 DNS 返回的假 IP。正确做法为:先卸除已有的 bind-mount,再从真实原始文件或标准模板重建 hosts 文件,最后再 bind-mount 一次。该逻辑应写为独立的 fix-hosts.sh,恢复脚本和单元均调用该脚本
公网返回 530、隧道未连接cloudflared 日志中是否存在 Registered tunnel connection,公网是否为刚建好刚建好时隧道处于重试阶段属正常现象,应待注册成功后再测试;不得在此时修改数据库
部分客户端解析公网域名失败、返回 000,但 VM 侧和绕 DNS 后均为 200是否为 DNS 负缓存新域名建好前查询过一次 NXDOMAIN 的解析器会将否定结果缓存一段时间(示例中为 SOA 1800 秒)。该现象不属于隧道故障,应待缓存过期,或在客户端刷新 DNS,或临时使用 --resolve 绕过 DNS 验证隧道本身
恢复脚本首次运行即遇 apt 占锁、索引失败是否与其它 apt 进程冲突重新运行一次恢复脚本,不得直接判定恢复失败

交付清单

完成后应向用户提供以下内容,仅说明“已完成”不足以验收:

  1. 公网地址:https://域名/,并附实测 http 码和首页标题。
  2. 服务清单:VM 上各服务是否为 active、各端口是否在监听。
  3. 恢复方式:恢复脚本各自位于家目录的哪个路径、5 分钟看门狗的名称、VM 替换后预计多久可自行恢复。
  4. 未变更项:未覆盖已有 Tunnel、未改动已有域名记录、未修改已安装 Typecho 的配置(除上述经用户同意修改的站点地址外)。
  5. 未存储项:数据库密码和 Tunnel token 仅存放于家目录的 600 文件中,未写入文章、记忆和代码仓库;管理员密码仅在安装页使用一次。

按本技能执行,一台全新的 Muse 在拿到前置条件参数表后,可将一台会被替换的 VM 复现为一个公网博客站。