machinaix-docs 维护手册
📘 元文档
元文档 · 关于本站

machinaix-docs 文档站维护手册

Static Docs Pipeline · Markdown → HTML → Cloudflare Workers

本站自己的说明书:它是怎么搭起来的、在新机器上怎么跑、怎么写新文档、怎么部署到 Cloudflare、部署时踩过哪些坑、以及怎样把整套东西交给 AI 阅读。看完这一篇就能独立维护和复现整套系统。

线上地址 docs.machinaix.com仓库 ANDREW-SVIP/machinaix-docs(私有)托管 Cloudflare Workers依赖 仅 Python 标准库

概览

这是什么

一套静态文档流水线:你写 Markdown,它产出一整个带导航、搜索、代码高亮、深浅色主题的文档站,并自动部署到 Cloudflare 边缘节点。

src/*.md ┐ theme.html ├──► build.py ──► dist/ ──► Cloudflare Workers ──► docs.machinaix.com site.json ┘ │ └──► llms.txt / llms-full.txt ──► 供 AI 读取

它解决的三个问题

样式统一

consistency

6 份文档共用一套模板。改一次配色或交互,全站生效,不用逐个文件改。

凭证安全

safety

源文件只写占位符,真值单独存放且从不提交。公开产物出现真值时构建直接失败。

AI 可读

machine-readable

产出 llms.txtllms-full.txt,AI 拿一个 URL 就能读完整个知识库。

核心特征

特征说明
零第三方依赖只用 Python 标准库,没有 requirements.txt、没有 node_modules
自包含单文件每个 HTML 内嵌全部 CSS/JS,可离线打开、可单独发给别人
双轨产出公开版(脱敏)与完整版(含真值)由同一份源生成
构建即体检每次构建自动扫描凭证泄漏,不通过就中断
推送即上线git push 后约 40 秒自动完成构建与部署
💡
谁该看这篇

未来的你自己。当你换了电脑、隔了半年、或者想加一份新文档时,从这里开始读,不用回忆任何细节。

架构与工作原理

目录结构

machinaix-docs/
├── build.py                 # 生成器:Markdown 渲染 + 模板注入 + 锚点校验 + 泄漏扫描
├── theme.html               # 文档页模板(全部 CSS/JS/组件,6 套品牌配色)
├── theme-index.html         # 文档中心首页模板
├── site.json                # 站点配置:文档顺序、卡片分组、扫描白名单、图标
├── placeholders.json        # 占位符标签表(可公开,只有标签没有真值)
├── wrangler.toml            # Cloudflare 部署配置(指明 dist/ 为静态资源目录)
├── README.md                # 仓库说明(GitHub 上展示)
│
├── src/                     # ← 唯一的内容源,日常只改这里
│   ├── machinaix-docs.md    # 本文
│   ├── andrewblog.md
│   ├── yunchuan.md
│   ├── proxy.md
│   ├── rustdesk.md
│   └── new-api.md
│
├── private/
│   ├── secrets.json         # 真实凭证(.gitignore,绝不提交)
│   └── secrets.example.json # 模板(会提交,只有占位值)
│
├── dist/                    # 公开产物(.gitignore,由 Cloudflare 构建时生成)
└── dist-private/            # 完整产物(.gitignore,仅本机)

一次构建发生了什么

  1. 读取配置

    site.json(站点级设置)和 placeholders.json(占位符标签),若存在 private/secrets.json 也一并读入。

  2. 逐份解析 Markdown

    每个 src/*.md 拆成两部分:顶部 --- 包裹的 JSON front-matter(标题、配色、导航分组、首页卡片信息),以及下方正文。

  3. 渲染正文

    正文按 ## 切分成 <section>,标题里的编号(一、 4.1)提取成徽章,{#id} 作为稳定锚点。表格、代码块、提示框、卡片、清单等逐一转成组件 HTML。

  4. 处理占位符

    %%KEY%% 在公开构建里渲染为脱敏块,在 --private 构建里替换为真实值。

  5. 注入模板

    把标题、导语、侧栏、正文塞进 theme.html 的占位标记,输出自包含单文件。

  6. 生成首页与 AI 入口

    汇总所有 front-matter 里的 card 字段生成 index.html;同时产出 llms.txt(文档清单)和 llms-full.txt / all.txt(全文合并,同一份内容两个名字)。

  7. 校验内部锚点

    收集全部页面的 id,检查每一个 #锚点xxx.html#锚点 是否真实存在。只报警告不中断构建 —— 死链不影响其他内容,不值得挡住部署。

  8. 泄漏扫描

    扫描 dist/ 里所有产物,命中真值或可疑模式则以非零码退出,构建失败。

关键设计:模板与内容分离

theme.html 里有一批 {{...}} 标记,build.py 用字符串替换填入内容:

标记填入内容
{{ACCENT}}品牌色名(写进 <html data-accent>,CSS 据此切换整套配色)
{{SIDEBAR}}根据 front-matter 的 nav 生成的分组侧栏
{{CONTENT}}渲染后的正文(一串 <section>
{{META_CHIPS}}首屏那排信息胶囊
{{BADGE}}顶栏右侧的状态徽章
正文末尾的「相关文档」卡片,由 front-matter 的 related 生成

好处是加一份文档不需要碰任何 CSS/JS,改一次样式全站同步。

在新机器上开始

假设你换了一台电脑,或者半年后回来接手,从零开始的完整步骤。

前置条件

需要版本检查命令
Python3.7 或更高python --version
Git任意git --version
浏览器任意现代浏览器

不需要 Node.js、不需要 pip install、不需要 npm install。整个生成器只用 Python 标准库。

步骤

  1. 克隆仓库
    git clone https://github.com/ANDREW-SVIP/machinaix-docs.git
    cd machinaix-docs

    仓库是私有的,克隆时需要 ANDREW-SVIP 账号的 Git 凭证。首次在新机器上操作会弹出浏览器登录(Git Credential Manager)或要求输入 Personal Access Token。

  2. 构建公开版
    python build.py

    macOS / Linux 上如果 python 指向 Python 2,改用 python3 build.py

    正常输出:

      [doc] machinaix-docs   11 sections  machinaix-docs 文档站维护手册
      [doc] andrewblog       15 sections  AndrewBlog 开发者文档
      [doc] yunchuan         12 sections  云传 · 开发者文档
      [doc] proxy            16 sections  代理服务器部署文档
      [doc] rustdesk         14 sections  RustDesk 自建服务器 · 运维与诊断文档
      [doc] new-api          11 sections  New API 运维手册
      [gen] index.html / llms.txt / all.txt / llms-full.txt
      [scan] 内部锚点校验通过
      [scan] 泄漏扫描通过(6 份文档)
    
      完成 -> .../machinaix-docs/dist
  3. 本地预览

    直接双击 dist/index.html,或者:

    # Windows
    start dist\index.html
    
    # macOS
    open dist/index.html

    产物是自包含单文件,不需要起本地服务器file:// 协议直接就能看。

  4. 恢复真实凭证(可选,仅本机需要完整版时)

    private/secrets.json 不在仓库里(这是设计使然)。新机器上有三种拿法:

    1. 从旧机器复制这个文件过来
    2. 从密码管理器里逐项恢复
    3. 照着模板重建:
    cp private/secrets.example.json private/secrets.json
    # 然后编辑 secrets.json 填入真实值

    填好后生成完整版:

    python build.py --private

    产物在 dist-private/,同样是 .gitignore 的,只在本机存在。

  5. 只做体检不写文件
    python build.py --check

    只跑解析和泄漏扫描,不产出文件。适合提交前快速验证。

  6. 新建一份文档
    python build.py --new proxy-v2

    生成带完整 front-matter 骨架的 src/proxy-v2.md,并自动把 "proxy-v2" 追加进 site.jsonorder。已存在则拒绝覆盖。

⚠️
没有 secrets.json 也能正常工作

缺少 private/secrets.json 时,公开构建完全正常(占位符渲染成脱敏块);只有 --private 会因为拿不到真值而退化成脱敏输出。所以在任何一台机器上 clone 下来就能直接构建和预览。

写作语法

标准 Markdown 的一个子集,加上几个专用扩展。

Front-matter(JSON)

每份 .md 顶部用 --- 包一段 JSON。这是唯一必须的结构:

{
  "title": "文档标题(首屏 H1 与浏览器标签)",
  "brand": "顶栏品牌名",
  "brandSub": "顶栏副标题",
  "accent": "teal",
  "eyebrow": "运维文档",
  "subtitle": "副标题(等宽字体,可省略)",
  "lede": "首屏导语,一两句话说清这份文档是什么",
  "badge": { "text": "🔒 脱敏版", "tone": "ok" },
  "meta": [["框架", "Flask 3.1"], ["许可证", "MIT"]],
  "footer": ["页脚左侧", "页脚右侧"],
  "related": ["proxy", "new-api"],
  "nav": [
    { "group": "开始", "items": ["s1", "s2"] },
    { "group": "进阶", "items": ["s3"] }
  ],
  "card": {
    "group": "基础设施文档",
    "initial": "R",
    "sub": "首页卡片的等宽副标题",
    "desc": "首页卡片正文描述",
    "role": "配色表里的定位说明",
    "tags": [["🔒 脱敏版", "ok"], ["14 章节", ""]],
    "meta": "卡片底部的小字"
  }
}
字段取值
accentblue / indigo / teal / violet / fuchsia / slate
tone、标签颜色ok(绿)/ warn(黄)/ danger(红)/ accent(品牌色)/ 空(灰)
nav.items章节 id 数组;写成 {"id":"s31","label":"3.1 节点","sub":true} 可自定义标签并缩进为子项
card.group决定首页归到哪一组,组的顺序在 site.jsoncardGroups
related其他文档的 slug 数组(可省略)。正文末尾自动渲染「相关文档」卡片,标题和描述从对方的 front-matter 取,不用手写。slug 写错会在构建时报 [warn] related 指向不存在的文档

章节与锚点

## 一、架构总览 {#s1}
### 4.1 UFW 防火墙
  • ## 自动切分成 <section>,是侧栏导航的单位
  • 标题开头的编号(一、 1. 4.1)会被提取成左侧的方块徽章
  • {#s1} 是稳定锚点,交叉引用写 [见第七节](#s7)
  • ### 进入右侧「本节内容」目录,跟随滚动高亮

没写 {#id} 时,## 用出现顺序编号(s1s2……),### 用标题内容的哈希(h-a1b2c3d4)。两者都只在你不改动文档结构时才稳定

不写 {#id}什么情况下锚点会变
## 二级标题增删任何一个二级标题 —— 后面所有编号整体移位
### 三级标题改动这个标题的文字(哪怕只改一个字)

所以凡是要被交叉引用或对外分享的章节,都写显式 {#id}。构建时会校验所有内部锚点,指向不存在的 id 会在输出里报 [warn] 内部锚点死链

提示框

> [!WARNING] 可选标题
> 正文,可以有多行、列表、代码块。
类型呈现
NOTE品牌色,💡
TIP / INFO灰底,💡 / ℹ️
WARNING / CAUTION黄色,⚠️
DANGER / IMPORTANT红色,⛔
SUCCESS绿色,✅
KEY / LOCK灰底,🔑 / 🔒

代码块

围栏后面跟语言名,支持这些:

语言效果
bash powershell json yaml nginx go env python语法高亮 + 语言标签 + 一键复制
text纯文本,无高亮
text wrap长链接自动换行(连接串这类超长内容用它)
tree目录树:目录名加粗,# 后的注释淡化
diagramASCII 图,支持 {good:文字}{bad:文字}{hl:文字} 上色
=html原样输出 HTML,用于流程图之类模板没覆盖的复杂组件

勾选清单

- [x] **①  已完成**
- [ ] **②  待办**
- [!] **③  阻塞**
- [~] **④  已并入别处**

四种状态分别渲染成绿勾、空框、琥珀感叹号、灰虚线(带删除线效果)。

卡片 / 步骤 / 折叠块

::: 包裹,内部用 --- 分隔条目:

::: cards
#### 卡片标题
*等宽副标题*
卡片正文
---
#### 第二张卡片
正文
:::

::: steps
#### 第一步的标题
正文、代码块、表格都可以放
---
#### [2.5] 自定义编号的步骤
方括号里的内容会替换默认序号
:::

::: expandable 展开完整日志
内容超过 600px 时自动折叠,底部出现展开按钮
:::

行内元素

支持 **粗体***斜体*、` 代码 链接`,以及原始 HTML 标签。

常用行内组件:

写法效果
<span class="pill green">已开启</span>绿色胶囊(还有 red / yellow / blue / gray / accent)
<span class="badge get">GET</span>方法徽章(get / post / delete)
<span class="stars">★★★★☆</span>星级
<span class="muted">补充说明</span>弱化小字
<br>表格单元格内换行

脱敏与占位符

整套系统里最重要的一条规则:源文件里永远不写真实凭证。

工作方式

在 Markdown 里写占位符:

| 密码 | %%HY2_PASSWORD%% |
| 服务器 | %%PROXY_HOST%% |

两种构建产出不同结果:

构建%%HY2_PASSWORD%% 渲染为产物目录用途
python build.py脱敏块「HY2 密码」dist/上云、给 AI 读、对外分享
python build.py --private真实值dist-private/本机查阅、实际运维

代码块里的占位符会渲染成 <HY2 密码> 这种尖括号形式,保持代码可读。

新增一个占位符

  1. 在 placeholders.json 加标签

    这个文件会提交,所以只放标签,不放真值:

    "NEW_SERVER_IP": { "label": "新服务器 IP", "note": "用途备注" }
  2. 在 private/secrets.json 加真值

    这个文件不会提交

    "NEW_SERVER_IP": "这里填真实 IP"
  3. 在 Markdown 里引用
    服务器地址:%%NEW_SERVER_IP%%
💡
想在文档里展示占位符语法本身

在前面加反斜杠:写 %%KEY%% 会原样输出 %%KEY%% 而不被替换。本文的所有语法示例用的都是这个转义。

顺序不能反

先加标签、后加真值、最后引用。如果直接在 md 里写真值再想着"回头替换",很可能忘记,而泄漏扫描只拦公开产物里的真值 —— 一旦真值进了 Git 历史,就得改密码而不只是改文件。

泄漏扫描

每次构建都会扫描 dist/ 里的所有产物:

触发条件处理
出现 private/secrets.json 里的任何真值(≥6 字符)构建失败
疑似 IPv4构建失败(除非在白名单里)
疑似 IPv6(5 段以上或含 ::构建失败
UUID 格式构建失败
sk- / cfut_ / ghp_ 开头的密钥构建失败

确认可以公开的字面量加进 site.jsonallowlist

"allowlist": ["127.0.0.1", "0.0.0.0", "66.249.68.32"]
💡
白名单里现在有什么

127.0.0.10.0.0.0(回环与通配,无意义)、两个 Googlebot 的 IP(博客文档里的日志示例,是 Google 的公开地址,不是你的服务器)。加任何新条目前先确认它不是你自己的资产。

新增与维护文档

加一份新文档

  1. 建 Markdown 文件
    python build.py --new xxx

    生成 src/xxx.md 骨架并自动登记进 site.json。也可以手工新建,照抄现有文件的 front-matter 改。

  2. 登记到 site.json

    --new 已经自动做完这步。手工新建时,在 order 数组里加上文件名(不含扩展名),位置决定它在首页和 llms.txt 里的排序:

    "order": ["machinaix-docs", "andrewblog", "yunchuan", "xxx"]

    如果用了新的卡片分组,还要在 cardGroups 里加上组名来控制分组顺序。

  3. 构建验证
    python build.py

    看输出里有没有你的新文档,以及锚点校验和泄漏扫描是否通过。

  4. 推送上线
    git add .
    git commit -m "docs: 新增 xxx 文档"
    git push

    Cloudflare 检测到 push 后自动构建部署,约 40 秒后生效。

首页卡片、侧栏分组、llms.txtllms-full.txt 全部自动生成,不需要手工维护任何索引。

日常修改流程

# 1. 改内容
#    编辑 src/xxx.md

# 2. 本地看效果
python build.py
start dist\xxx.html

# 3. 满意后推送
git add .
git commit -m "docs: 更新 xxx 的某某章节"
git push

# 4. 等约 40 秒,线上生效

验证线上是否已更新

curl -s -A "Mozilla/5.0" https://docs.machinaix.com/llms.txt | head -5

或者直接浏览器强制刷新(Ctrl + Shift + R)。

ℹ️
为什么要带 User-Agent

Cloudflare 的机器人防护会拦掉不带 User-Agent 的请求,返回 403。用 curl 测试时记得加 -A

改样式或交互

theme.html(文档页)或 theme-index.html(首页),重新构建即可,全站同步生效。这是模板化最大的收益 —— 不用逐个文件改。

给 AI 的入口

这是整套系统的核心目的之一:让 AI 用最低成本读懂你的全部技术上下文。

两个入口

地址内容什么时候用
https://docs.machinaix.com/llms.txt全部文档的 URL 清单 + 一句话描述让 AI 先看清单,再决定读哪一篇
https://docs.machinaix.com/llms-full.txt全部文档合并成一份纯文本让 AI 一次性拿到完整上下文

https://docs.machinaix.com/all.txt 是全文合并版的别名,内容与 llms-full.txt 逐字节相同。llms-full.txt 是社区正在形成的约定名,AI 更可能主动去猜这个地址;all.txt 是本站原有的名字,保留是为了不让已经发出去的链接失效。

怎么用

对话开始时直接把地址给 AI:

先读 https://docs.machinaix.com/llms.txt,
然后根据我的问题去读对应的文档,再回答。

或者需要全局上下文时:

读 https://docs.machinaix.com/llms-full.txt,这是我全部项目和服务器的技术文档。

注意事项

⚠️
线上全是脱敏版

AI 读到的服务器 IP、密码、UUID 全是占位符。这是刻意设计 —— 让 AI 理解架构和流程,但拿不到能实际访问你系统的凭证。需要 AI 帮你处理真实值时,用本机 dist-private/ 的内容,不要把线上地址当作真值来源。

ℹ️
机器人防护

Cloudflare 会拦掉不带 User-Agent 的请求。绝大多数 AI 抓取器都会带 UA,所以正常可用。万一某个 AI 读不到,去 Cloudflare 的 安全性 → WAF / 机器人管理docs.machinaix.com 放行。

AI 策略设置

在 Cloudflare 域名设置里配置的三项:

项目当前设置含义
搜索阻止搜索引擎爬虫拿不到内容,站点不会出现在搜索结果里
代理允许AI 回答问题时抓取网页走这条,必须开
训练阻止AI 厂商的训练爬虫拿不到内容
代理这项绝对不能关

关掉「代理」等于 AI 读不到你的文档,整套系统的核心价值就没了。

部署到 Cloudflare Workers

从零复现整套部署的完整步骤。

整体架构

GitHub 私有仓库 │ push 触发 ▼ Cloudflare Workers 构建环境 │ ① python build.py -> 生成 dist/(含泄漏扫描) │ ② npx wrangler deploy -> 读 wrangler.toml,上传 dist/ ▼ Cloudflare 边缘节点 ──► docs.machinaix.com

步骤

  1. 准备 GitHub 私有仓库

    你本机 Git 凭证所属的那个账号下创建仓库,设为 Private,然后:

    git remote add origin https://github.com/<账号>/machinaix-docs.git
    git branch -M main
    git push -u origin main
  2. 确认仓库里有 wrangler.toml

    这是 Workers 静态资源部署的必需文件:

    name = "machinaix-docs"
    compatibility_date = "2026-08-06"
    
    [assets]
    directory = "./dist"

    [assets] 段告诉 wrangler 把 dist/ 作为静态资源上传。没有这个文件,npx wrangler deploy 会失败。

  3. 创建 Cloudflare 应用

    Cloudflare Dashboard → Workers 和 Pages创建应用程序 → 找到「导入 Git 存储库」或「Pages」标签页。

    新版界面会走「创建 Worker」流程,这是正常的 —— Cloudflare 正在用 Workers 静态资源取代 Pages 新项目。

  4. 授权 GitHub

    选择账号 → Only select repositories → 勾选 machinaix-docs → Install & Authorize。

    必须勾选具体仓库,否则回到 Cloudflare 后在列表里找不到它(私有仓库尤其)。

    Only select repositories 而不是 All repositories,是最小权限原则 —— Cloudflare 只需要这一个仓库。

  5. 填写构建配置
    字段什么时候执行
    项目名称machinaix-docs——
    生产分支main——
    构建命令python build.py每次构建
    部署命令npx wrangler deploy只在生产分支
    版本命令npx wrangler versions upload只在非生产分支
    根目录留空 / /——
    构建命令最容易漏

    这一栏标着「可选」,很容易跳过。但漏了它整个部署就是错的 —— dist/ 不会生成,wrangler deploy 找不到资源目录直接失败。详见踩坑第 2 条。

    ⚠️
    版本命令别填成 deploy

    「版本命令」管的是非生产分支。填成 npx wrangler deploy 的话,推任何一个分支都会直接覆盖生产版本,而不是生成预览。必须是 npx wrangler versions upload。详见踩坑第 9 条。

  6. 部署并验证

    点「部署」,等约 40 秒。成功后会得到一个 <项目名>.<你的子域>.workers.dev 地址,打开能看到文档中心首页就说明通了。

  7. 绑定自定义域

    Worker 页面 → 标签 → 添加域名,填 docs.machinaix.com

    如果报「已有外部管理的 DNS 记录」,说明 DNS 里有一条同名的 A/CNAME 记录挡着,删掉它再加。如果提示「没有区域匹配」,不要点「查找相似项」(那是卖域名的页面),可以先用「添加路由」顶着。完整过程见踩坑第 4、5 条。

    加完回列表核对名称 —— 输入框可能自动补后缀,拼出 docs.machinaix.com.machinaix.com 这种东西。

  8. 回填 baseUrl

    域名生效后,把 site.jsonbaseUrl 填成正式地址:

    "baseUrl": "https://docs.machinaix.com"

    推送后 llms.txt 里的链接会从相对路径变成完整 URL,AI 抓取时才能正确跳转。

构建环境说明

情况
Python构建镜像自带,直接 python build.py 即可
依赖安装无 —— 没有 requirements.txt,日志里会显示 No dependencies detected to cache
wranglernpx 首次执行时自动下载(日志里会看到 will be installed: wrangler@4.x
构建耗时约 40 秒,其中大半是初始化环境和下载 wrangler

如果遇到 Python 版本问题,在 设置 → 变量和机密 加一条 PYTHON_VERSION = 3.11

自定义域 vs 路由

两种把域名接到 Worker 的方式,理解差异能少走弯路:

自定义域(Custom Domain)路由(Route)
DNS 记录Cloudflare 自动创建一条 Worker 类型记录并管理需要该主机名已能通过 Cloudflare 解析(泛解析也算)
配置形式填主机名 docs.machinaix.com填 URL 模式 docs.machinaix.com/*
前置条件该主机名不能已有你自己建的 A/CNAME 记录
适用场景该主机名专属于这个 Worker想按路径分流,或不方便动 DNS 记录
本站采用✅ 现用方案(2026-08-07 起)建站初期用它顶过一段时间
💡
路由模式末尾的 /* 不能漏

写成 docs.machinaix.com 只匹配根路径,/llms.txt/proxy.html 这些子路径全会 404。必须写 docs.machinaix.com/*

本站的迁移过程

建站时自定义域加不上(见踩坑第 4 条),先用路由顶着。后来查明真正的拦路石是一条手工建的 docs A 记录,删掉它之后自定义域正常添加,路由随即删除。

迁移顺序是关键:先加自定义域、确认站点正常,再删路由。反过来的话,中间那段时间 docs.machinaix.com 会落回泛解析,显示成博客首页。

部署踩坑实录

真实踩过的坑,按遇到顺序排列。每条都记了现象、原因、诊断方法和解决办法。最后一条是事前发现、还没踩上的,一并记在这里。

坑 1:git push 报 "Repository not found"

现象

remote: Repository not found.
fatal: repository 'https://github.com/xxx/machinaix-docs.git/' not found

仓库明明在网页上看得见,push 却说找不到。

原因

仓库建在了 A 账号下,而本机 Git 凭证是 B 账号的。GitHub 对无权访问的私有仓库统一返回 404 而不是 403 —— 这是防止通过错误码探测私有仓库是否存在的安全设计,但也让报错具有误导性。

诊断

# 看本机存的是哪个 GitHub 账号
cmdkey /list | Select-String "github" -Context 0,2

# 确认目标账号类型(User 还是 Organization)
Invoke-RestMethod "https://api.github.com/users/<账号名>"

解决

三选一:把仓库转移到凭证所属账号 / 把凭证账号加为协作者 / 用带用户名的 remote URL 让 Git 凭证管理器单独登录。本站采用的是第一种(在正确账号下重建)。

💡
注意显示名和登录名的区别

GitHub 的授权页面可能显示显示名(Name 字段)而不是登录名(login)。看到一个陌生名字先别慌,用 https://api.github.com/users/<登录名> 查一下 name 字段就能确认是不是同一个账号。

坑 2:首次构建失败,日志里没有构建步骤

现象

构建日志:

Cloning repository...
No build output detected to cache. Skipping.
Executing user deploy command: npx wrangler deploy

克隆完直接跳到部署,中间没有任何构建动作,然后部署失败。

原因

创建项目时「构建命令」那一栏是空的(它标着「可选」)。于是 dist/ 从未生成,wrangler deploywrangler.toml 去找 ./dist 却找不到。

诊断

在构建详情页展开「构建设置」,看 构建命令 是不是显示「无」。

解决

项目 → 设置构建 → 构建配置 → 填 python build.py → 保存 → 回到「部署」标签点「重试构建」。

坑 3:wrangler deploy 需要配置文件

现象

npx wrangler deploy 报错找不到入口或资源目录。

原因

Workers 部署需要 wrangler.toml(或 wrangler.json)声明要部署什么。纯静态站点属于「仅静态资源 Worker」,没有 main 入口脚本,必须用 [assets] 段指明目录。

解决

仓库根目录放 wrangler.toml

name = "machinaix-docs"
compatibility_date = "2026-08-06"

[assets]
directory = "./dist"

坑 4:添加自定义域报「没有区域匹配」

现象

Worker → 域 → 添加域名 → 填 docs.machinaix.com,弹窗提示:

没有区域匹配 docs.machinaix.com。
如果您拥有此域名并希望将其连接到您的 Worker,请先将该域名添加到 Cloudflare。

但域名明明就在 Cloudflare 上。

排查过程

  1. 确认域名确实托管在 Cloudflare
    # 查 NS 记录
    dig NS machinaix.com +short

    返回 xxx.ns.cloudflare.com 就说明在 Cloudflare 上。

  2. 确认域名和 Worker 在同一个账号

    Cloudflare 账号主页会同时列出 DomainsWorkers 两栏。如果域名和 Worker 都在里面,就排除了账号不一致的可能。

    💡

    Worker 只能绑定同账号下的域名,这是最常见的原因,但本例不是。

  3. 判定为界面异常

    域名在、账号对,弹窗却说没有区域匹配 —— 是这个弹窗自身的问题。

解决

改用 添加路由。路由走的是另一套接口,在它的域名选择列表里 machinaix.com 正常出现:

  1. Worker → + 添加路由
  2. 选择区域 machinaix.com
  3. 路由模式填 docs.machinaix.com/*
千万别点「查找相似项」和「接入域名」

「查找相似项」会跳到 Cloudflare 域名销售页,向你推销 docsmachinaix.com 这类新域名 —— 你根本不需要买。 「接入域名」是把一个新域名迁入 Cloudflare 的流程(要改 NS),你的域名早就在 Cloudflare 里了,点了只会把事情搞乱。

后续:2026-08-07 查明真正原因

再试一次时,弹窗不再说「没有区域匹配」,而是给出了真正有用的报错:

Hostname 'docs.machinaix.com' already has externally managed
DNS records (A, CNAME, etc). Delete them first or try a different hostname.

拦路石是一条手工建的 docs A 记录(建站早期留下的)。自定义域要求由 Cloudflare 自己创建并管理那条 Worker 类型记录,所以不接受同名的既有记录。

删掉那条 A 记录之后,自定义域一次就加上了,路由随即删除。建站时那个「没有区域匹配」的提示是误导 —— 它把「主机名已被占用」说成了「找不到区域」,白白多花了一小时。

⚠️
添加域名的输入框会自动补后缀

这次还顺手踩了个小坑:在输入框里填完整的 docs.machinaix.com,实际建出来的是 docs.machinaix.com.machinaix.com。加完一定回列表看一眼名称对不对,建错了就删掉重来。

坑 5:域名绑好了,打开却是另一个站点

现象

docs.machinaix.com 能打开、HTTPS 正常,但显示的是博客首页而不是文档站;/llms.txt 返回 404。

原因

主域名有一条 *.machinaix.com 泛解析记录指向博客服务器。在没有为 docs 建专属记录、也没有路由拦截时,docs.machinaix.com 命中泛解析 → 打到博客服务器的 Nginx → 返回默认站点。

诊断技巧

拿一个根本不存在的子域去解析:

dig zzz-does-not-exist-9421.machinaix.com +short

如果它也能解析出 IP,就证明存在泛解析记录。这个方法很好用,能立刻定位这类"看起来像成功但内容是错的"问题。

解决

加上 Worker 路由 docs.machinaix.com/* 之后自动解决 —— 路由在 Cloudflare 边缘节点就拦截请求,根本不会回源到博客服务器,优先级天然高于泛解析。

现在改用自定义域后同样安全:Cloudflare 为 docs 建了一条专属的 Worker 类型记录,专属记录的优先级高于泛解析。

⚠️
泛解析仍然在,别删

*.machinaix.com 是博客其他子域在用的,删掉会连带搞垮它们。它跟文档站现在已经没有关系 —— docs 有了自己的专属记录,不再走泛解析。

坑 6:.txt 文件中文可能乱码

现象

llms.txt / all.txt 的响应头是 Content-Type: text/plain不带 charset。浏览器可能按本地编码解析,中文变乱码。

HTML 不受影响,因为文件头部有 <meta charset="UTF-8">

解决

build.py 构建时生成 _headers 文件(Cloudflare 静态资源支持这个约定):

/*
  Cache-Control: public, max-age=300, must-revalidate

/*.txt
  Content-Type: text/plain; charset=utf-8

修复后响应头变成 text/plain; charset=utf-8

顺带声明了 5 分钟的浏览器缓存:改完推上去很快能看到新内容,同时挡住反复刷新时的回源。想让改动立刻可见就强制刷新(Ctrl + Shift + R)。

坑 7:不带 User-Agent 的请求被 403

现象

用脚本测试站点时返回 HTTP Error 403: Forbidden,浏览器里却完全正常。

原因

Cloudflare 默认的机器人防护会拦掉没有 User-Agent 的请求。

解决

测试时带上 UA:

curl -s -A "Mozilla/5.0" https://docs.machinaix.com/llms.txt

绝大多数 AI 抓取器都会带 UA,正常使用不受影响。万一某个 AI 读不到,去 Cloudflare 安全性 → WAF 给这个主机名加放行规则。

坑 8:PowerShell 取网页内容中文乱码(误报)

现象

Invoke-WebRequest 检查线上内容,中文关键词全都"找不到",ASCII 关键词却能找到。

原因

响应头没有 charset 时,PowerShell 的 .Content 会按 ISO-8859-1 解码,中文全乱。这是检查工具的问题,不是站点的问题。

解决

验证脚本改用 Python 显式按 UTF-8 解码:

import urllib.request as u
req = u.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
text = u.urlopen(req, timeout=25).read().decode('utf-8')
💡
这条坑的教训

排查问题时先怀疑自己的检查工具。当"部分检查失败、部分成功"且失败的都是非 ASCII 内容时,几乎一定是编码问题而不是业务问题。

坑 9:非生产分支会直接覆盖生产

现象

这条是事前发现的,还没踩上:建项目时「版本命令」被填成了 npx wrangler deploy,而「非生产分支构建」是开启的。

原因

Cloudflare Workers Builds 有两个部署命令:

字段触发分支应该是
部署命令生产分支(mainnpx wrangler deploy
版本命令其他所有分支npx wrangler versions upload

两个都填 deploy 的话,随便开个分支改点东西一推,线上立刻被这个半成品分支覆盖 —— 而你以为自己只是在做预览。

解决

项目 → 设置构建 → 构建配置 → 版本命令 改成 npx wrangler versions upload。改完非生产分支只生成带独立预览地址的版本,生产纹丝不动。

不需要分支预览的话,也可以直接在 分支控制 里关掉「非生产分支构建」。

💡
怎么验证改对了

建个临时分支推一次,看构建日志里执行的是 versions upload 还是 deploy,同时确认 docs.machinaix.com 的内容没变。验完删分支。

故障排查

现象可能原因处理
构建失败:front-matter JSON 解析失败md 顶部的 JSON 语法错(多逗号、缺引号)按报错里的位置改;JSON 不支持注释和尾逗号
构建失败:泄漏扫描不通过源文件里写了真值,或出现新的 IP/UUID改成 %%占位符%%,或把确认安全的值加进 site.jsonallowlist
构建警告:placeholders.json 中缺少标签md 里用了未登记的占位符placeholders.json 补一条标签
侧栏少了某个章节front-matter 的 nav.items 里没写这个 id补上 id;未列出的章节不会出现在侧栏
章节锚点失效没写 {#id}##s1/s2 会随增删移位,###h-xxxxxxxx 会随标题改字而变给需要交叉引用的章节写显式 {#id}
构建警告:内部锚点死链文档里的 [见第七节](#s7) 指向了不存在的 id按提示改链接或补 {#id}。只是警告,不会挡住构建
首页没有某份文档的卡片front-matter 缺 card 字段,或 site.jsonorder 里没登记card 并加进 order
线上没更新构建失败,或浏览器缓存看 Cloudflare 构建日志;Ctrl+Shift+R 强刷
线上 404路由模式漏了 /*改成 docs.machinaix.com/*
线上显示别的站点泛解析截胡见踩坑第 5 条
curl 返回 403没带 User-Agent-A "Mozilla/5.0"

本地快速自检

# 只跑解析和泄漏扫描,不写文件
python build.py --check

# 完整构建后核对产物
python build.py
ls dist

线上快速自检

curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0" https://docs.machinaix.com/
curl -s -A "Mozilla/5.0" https://docs.machinaix.com/llms.txt | head -8

十一设计决策

记录为什么这样设计,避免以后"优化"掉关键约束。

为什么产物是自包含单文件

每个 HTML 内嵌全部 CSS 和 JS,不引用任何外部资源。

代价:每份文档重复约 20 KB 的模板代码。 收益:可以离线打开、可以单独发给别人、可以塞进邮件附件、十年后没有 CDN 失效问题。脱敏版文档本来就是"要能单独发出去"的东西,这条约束不能破。

⚠️
不要改成共享 CSS 文件

抽出 docs.css 看起来更"专业",但会让单个 HTML 失去独立性。真正的去重方案是模板化(已经做了),而不是拆分产物。

为什么源是 Markdown 而不是 HTML

最初的 6 份文档是手写 HTML,每份都内嵌一整套 CSS/JS。改一次配色要改 6 个文件,到 10 份就彻底失控。

改成 Markdown + 模板后:写作成本降低、AI 改 md 的可靠性远高于改上千行 HTML、样式改一次全站生效。

为什么零第三方依赖

build.py 只用 Python 标准库,手写了一个 Markdown 子集渲染器。

代价:不支持完整 Markdown 语法(够用即可)。 收益:Cloudflare 构建环境不用装任何东西、构建快、没有供应链风险、几年后依然能跑。对一个文档站来说,这个权衡明显划算。

为什么用占位符而不是"两份文件"

最早的做法是维护「完整版」和「脱敏版」两份独立文档。问题是改一次密码要改两处,迟早不同步。

占位符方案让真值只存在于 private/secrets.json 一处,两个版本由同一份源生成,结构永远一致。

为什么泄漏扫描要让构建失败而不是只警告

警告会被忽略,尤其在 CI 日志里。让构建硬失败意味着含真值的产物永远不可能上线 —— 这条防线的价值就在于它不给人"下次再说"的机会。

为什么线上只放脱敏版

因为目标是让 AI 能读,而 AI 抓取需要公开无鉴权的 URL。公开 + 含真值 = 泄密,两者不可兼得。

所以选择:线上公开脱敏版供 AI 阅读,真值只在本机 dist-private/。这也是为什么「代理」这项 AI 策略必须保持允许 —— 它是整个设计的目的所在。