外观
部署三个 Cloudflare Worker 站
控制台、对等门户、首页都是 Git 关联的 Cloudflare Worker:push 到默认分支即自动 构建部署,本地不跑 wrangler deploy。
授权界面不在此列,见 deploy-auth.md。
日常部署
bash
npm run check # i18n + 三个站的类型检查,必须全绿
npm run build # 四个站全建,确认无构建错误
git pushpush 之后按 watch paths 触发对应项目的构建。改了 ui/ 会同时触发三个——这是有意 的,共享库变更本来就该让三个站一起重建。
⚠️ 改了
ui/之后授权界面不会被触发,要手工重建投放。
三个项目的构建配置
三个 Worker 各自连同一个仓,靠构建配置区分(Cloudflare 官方支持 monorepo)。
| 项 | control | peering | home |
|---|---|---|---|
| Worker 名 | dn42-control-webui | dn42-peering-webui | natlan-io-home |
| Root directory | 仓根 | 仓根 | 仓根 |
| Build command | npm run build:control | npm run build:peering | npm run build:home |
| Deploy command | npx wrangler deploy -c apps/control/wrangler.jsonc | …apps/peering/wrangler.jsonc | …apps/home/wrangler.jsonc |
| Build watch paths | apps/control/* + ui/* | apps/peering/* + ui/* | apps/home/* + ui/* |
Root directory 必须是仓根——依赖装在根上。
wrangler.jsonc 是事实源
三份配置都必须保留在仓里。缺了它,wrangler deploy 会启动自动配置向导, 而那个向导在构建沙箱里把构建脚本改写成 wrangler types --check && vite build, 然后在一个从不存在的类型文件上失败(纯静态站没有 bindings)。
配置里几项容易被误删的:
| 配置 | 出现在 | 作用 |
|---|---|---|
workers_dev: false | 三份 | 关掉 *.workers.dev 子域。实测过不关时 dn42-control-webui.<sub>.workers.dev 会原样发出登录页(CSP 头都在,确实是本 Worker) |
assets.not_found_handling: "single-page-application" | control / peering | SPA 回退。不要改用 _redirects——Workers 资产会把它判成重定向死循环 |
assets.run_worker_first: ["/*", "!/_app/*"] | control | 文档过 Worker 拿 CSP nonce,内容哈希产物直供 |
main: ./worker/index.js | control | 盖 nonce 的薄壳 Worker |
routes: [{pattern: "peering.natlan.io", custom_domain: true}] | peering | 自定义域写在配置里,每次构建重新声明 |
rules | home | 把 html / svg / geojson / bundle.js 当文本模块、jpg 当数据模块打包 |
observability | 三份 | 这份文件是每次部署的事实源,面板上手动改的这几项会被 CI 覆盖 |
自定义域
三个域的绑定方式不统一:
peering.natlan.io写在wrangler.jsonc的routes里,随每次构建重新声明。console.natlan.io与natlan.io由账号侧的 Workers 自定义域承载, 不在配置文件里。
首页裸域的绑定调用:
PUT /accounts/{account}/workers/domains
{ "zone_id": "…", "hostname": "natlan.io",
"service": "natlan-io-home", "environment": "production" }给一个已有外部 DNS 记录的主机名挂 Worker 自定义域会被 Cloudflare 拒绝 (
Hostname … already has externally managed DNS records)。把「删 A 记录」与 「PUT domains」放进同一次 API 调用,可以把无 DNS 的空窗压到最小。
部署前必须确认的 zone 设置
控制台发 CSP,因此 natlan.io zone 上这两项必须是 off:
| 设置 | 为什么 |
|---|---|
| Rocket Loader | 它会改写页面上的 <script> 标签并注入一个没有 nonce 的脚本,CSP 必挂 |
| Auto Minify (HTML) | 它改写内联脚本字节。nonce 对此免疫,但历史上的哈希方案就是被它打掉的 |
详见 reference/csp.md。
部署后验收
bash
# ① 站点可达且是本 Worker(CSP 头存在)
curl -sI https://console.natlan.io/ | grep -iE 'content-security-policy|cache-control'
# ② 逐请求 nonce 互不相同
for i in 1 2 3; do curl -sI https://console.natlan.io/ | grep -io "nonce-[a-f0-9]*"; done
# ③ workers.dev 子域已关闭
curl -s -o /dev/null -w "%{http_code}\n" https://dn42-control-webui.<sub>.workers.dev/
# ④ SPA 回退生效(子路由直接访问返回 200 而非 404)
curl -s -o /dev/null -w "%{http_code}\n" https://console.natlan.io/nodes
curl -s -o /dev/null -w "%{http_code}\n" https://peering.natlan.io/peerings① 应看到 Cache-Control: no-store 与一条含 'nonce-<32 位十六进制>' 的 CSP。 ② 三行必须互不相同——相同说明响应被缓存了。④ 两条都必须是 200。
首页另外确认三条静态路径与重定向:
bash
curl -s -o /dev/null -w "%{http_code}\n" https://natlan.io/fleet.js
curl -s -o /dev/null -w "%{http_code}\n" https://natlan.io/world-land.geojson
curl -s -o /dev/null -w "%{http_code} %{redirect_url}\n" https://natlan.io/nodes/hkg1最后一条应当是 302 指向控制台。
手工部署(应急)
Git 关联失效时仍可本地部署:
bash
npm run build:control
npx wrangler deploy -c apps/control/wrangler.jsonc首页同理。这是应急手段,正常流程不要用——本地部署绕过了 CI 的检查。