Negentropy Wiki 独立部署与内容同步指引

适用对象:负责把 negentropy-wiki 部署到远程环境、并把主站 Catalog 内容发布上去的工程师 / 运营。

一句话:wiki 是纯静态站点output: export),运行时无 Node/后端/数据库;内容来自仓库内 content/ 静态内容包,任何已发布内容都必须先「导出」到 content/、再「重建」才能上线

相关文档Wiki 运维指引 · Wiki 知识发布(UI 操作) · Docker 发布流水线 · Wiki README


#1. 架构前提

negentropy-wiki#931 完成纯静态解耦:

  • 运行时零依赖:无 Node 服务端、无后端 API 调用、无数据库访问。产物是纯静态 HTML/CSS/JS(apps/negentropy-wiki/out/),可由任意静态托管(nginx / static-web-server / CDN / GitHub Pages)提供服务。
  • 内容来自「内容根」:构建期 next build 读取内容根的静态内容包烘焙为静态 HTML;运行时不再回源任何后端。内容根三级解析(content-source.tsresolveContentDir):
    • WIKI_CONTENT_DIR(显式覆盖)> content/(真实导出落点,整体 gitignored,存在 index.json 时采用)> content.fixture/(仓库内开发种子 fixture,入 git,构建兜底)。
    • 真实导出内容环境相关、不入 git;fixture 与真实物理隔离——导出工具覆盖式 _reset 只动 content/,不波及 content.fixture/。schema 见 content.fixture/README.md
  • 关键不变量内容更新 = 重建。ISR 已退役——不存在「运行时 5 分钟自动刷新」。要让新发布的内容上线,必须重新导出 content/ 并重建站点(Docker 镜像或静态产物)。

#1.1 数据流(发布 → 上线)

边界:导出动作由主站职责承担(合法持有 DB 访问),产出静态文件;wiki 端构建/运行时不直接或间接依赖主站数据库。内容包即「发布边界」。


#2. 独立部署 negentropy-wiki

#2.1 前置:构建产物形态

产物路径说明
静态产物apps/negentropy-wiki/out/next buildoutput: export)输出,含全部预渲染 HTML
搜索索引out/pagefind/postbuildpagefind --site out)生成,必须随 out/ 一起部署,否则全文搜索失效
真实内容(构建输入)apps/negentropy-wiki/content/真实导出落点,整体 gitignored;由 sync-wiki-content.sh / CI 写入
fixture(构建兜底)apps/negentropy-wiki/content.fixture/开发种子,入 gitcontent/ 缺失时构建自动回退(见 §1 内容根解析

内容根三级解析content-source.tsresolveContentDir):WIKI_CONTENT_DIR > content/(存在 index.json 时)> content.fixture/。即有真实导出用真实、否则回退 fixture, 二者物理隔离——导出工具覆盖式 _reset 只动 content/,不波及 fixture。

构建命令(仓库根目录):

hljs bash
pnpm install --frozen-lockfile
pnpm --filter negentropy-wiki build      # next build(export)+ postbuild pagefind

#2.2 方式 A:Docker 全链路(推荐)

独立 Dockerfile:docker/wiki/Dockerfile(多阶段:deps → builder 产出 out/ → runtime 用 static-web-server 托管,无 Node 运行时)。

① 本地构建镜像

hljs bash
# 仓库根目录;<tag> 例如 latest / 日期 / commit
docker build -f docker/wiki/Dockerfile -t threefishai/negentropy-wiki:<tag> .

② 运行(单容器)

hljs bash
docker run -d --name negentropy-wiki \
  -p 8080:80 --restart unless-stopped \
  threefishai/negentropy-wiki:<tag>
# 访问 http://<host>:8080/

③ 独立 compose(验证独立性,无 backend/ui/postgres)

hljs bash
docker compose -f docker-compose.wiki.yml up -d --build
# 见 docker-compose.wiki.yml:仅 wiki 服务,端口 3092:80

④ 完整栈:根 docker-compose.ymlwiki 服务已depends_on,可与 backend/ui 同栈部署,也可独立拉起。

配置要点

容器端口80(static-web-server),宿主机任意(如 8080/3092
环境变量无需任何 env(纯静态)
健康检查HTTP GET / 返回 200
重启策略unless-stopped

镜像发布通道(CI)reusable-negentropy-docker.yml 在 release 流水线构建并推送多架构镜像到 docker.io/threefishai/negentropy-wiki。详见 Docker 发布流水线。发布后任意远程主机可直接 docker pull threefishai/negentropy-wiki:<tag>

#2.3 方式 B:通用静态托管(直接托管 out/

不想用 Docker 时,把 out/ 作为纯静态资源托管到任意静态服务器/CDN。

① 构建产物pnpm --filter negentropy-wiki buildapps/negentropy-wiki/out/

② nginx 示例trailingSlash:true 已生成目录式 HTML,无需 SPA fallback):

hljs nginx
server {
  listen 80;
  server_name wiki.example.com;
  root /var/www/negentropy-wiki/out;   # 指向 out/
  index index.html;                    # 显式声明:使 $uri/ 能解析到目录 index.html

  # 目录式 HTML:/pub/entry/ → /pub/entry/index.html。
  # try_files 的 $uri/ 分支使「无尾斜杠」请求(如 /pub/entry)也能内部命中目录 index,
  # 故无论链接是否带尾斜杠均可访问(静态导出不产出 .html,无需 $uri.html)。
  location / {
    try_files $uri $uri/ =404;

    # 可选 SEO 规范化:把无尾斜杠的目录型 URL 301 到尾斜杠(单一规范 URL)。
    # 启用后上述 try_files 的无尾斜杠兜底可移除。
    # rewrite ^([^.]*[^/])$ $1/ permanent;
  }

  # pagefind 搜索索引(随 out/ 一起部署)
  location /pagefind/ { add_header Cache-Control "public, max-age=3600"; }

  # 静态资源长缓存
  location /_next/static/ { add_header Cache-Control "public, max-age=31536000, immutable"; }

  gzip on;
  gzip_types text/css application/javascript application/json image/svg+xml;
}

③ CDN / 对象存储(Cloudflare / CloudFront / OSS):上传 out/ 全量(务必包含 out/pagefind/)。设置默认根对象为 index.html

④ GitHub Pages:把 out/ 推到 gh-pages 分支(或用 peaceiris/actions-gh-pages)。注意 base path:若部署在子路径(user.github.io/repo/),需在 next.config.tsbasePath 后重新构建。

#2.4 反向代理 / HTTPS(可选)

前置 Caddy / Traefik / nginx 终止 TLS,反代到 wiki 容器的 80

hljs nginx
server {
  listen 443 ssl http2;
  server_name wiki.example.com;
  # ... ssl 证书 ...
  location / { proxy_pass http://negentropy-wiki:80; }
}

#3. 内容同步与发布

#3.1 原理

wiki 只读 content/任何已发布内容必须经两步才能上线

  1. 导出:从主站 DB 把已发布 publication 序列化为 content/ 静态内容包(WikiExportService + export_wiki_content.py)。
  2. 重建next buildcontent/ 烘焙进 out/(并据此构建 Docker 镜像或上传静态产物)。

这两步分别由 sync-wiki-content.sh(本地)/ wiki-content-export.yml(CI)+ 构建/部署流水线承担。

#3.2 路径选型矩阵

场景推荐路径触发详见
本地主站 → 远程 wiki(一次性/快照)手动快照部署人工§3.3
主站 DB 在 CI 可达 + publish 自动化CI 自动publish webhook§3.4
本地开发联调(本地 wiki)本地刷新cli.sh restart§3.5 / README

#3.3 路径 A(重点):手动快照部署 —— 本地主站 → 远程 wiki

最适合「我在本地主站编排好 Catalog 并发布,想把这份内容部署到一台远程 wiki」的场景。本质:把本地 DB 的已发布内容烘焙进 Docker 镜像,推到 registry,远程拉取运行

前置:本地已能跑通主站(postgres + backend),且至少有一个 status=published 的 Wiki publication。

Step 1 — 本地发布:浏览器打开主站 /knowledge/wiki,编排 Catalog 树后点「同步并发布」(UI 操作详见 Wiki 知识发布)。发布成功后内容写入本地 DB。

Step 2 — 导出本地 DB → content/

hljs bash
# 仓库根目录;自动连本地 DB(NE_DB_URL 或默认 localhost:5432/negentropy),
# 把已发布内容导出到 apps/negentropy-wiki/content/
./scripts/sync-wiki-content.sh

等价手动命令(便于排错):

hljs bash
cd apps/negentropy
NE_SVC_ARTIFACT_BACKEND=inmemory uv run python scripts/export_wiki_content.py \
  --out ../negentropy-wiki/content

校验导出结果:

hljs bash
cat apps/negentropy-wiki/content/publications.json | python3 -m json.tool
# 期望:items 含你刚发布的 publication,entries_count > 0

sync-wiki-content.sh 自动把 NE_SVC_ARTIFACT_BACKENDinmemory,以容忍用户级 ~/.negentropy/config.yaml 中可能与本分支枚举(inmemory|gcs)不符的取值。本地生成的 content/ 是环境相关数据,请勿提交(仓库保留 fixture 种子)。

Step 3 — 构建含内容的镜像content/ 在构建期烘焙进 out/):

hljs bash
docker build -f docker/wiki/Dockerfile -t threefishai/negentropy-wiki:<tag> .

Step 4 — 推送 registry

hljs bash
docker login                           # 首次需登录 Docker Hub(或私有 registry)
docker push threefishai/negentropy-wiki:<tag>

Step 5 — 远程拉取运行(在远程主机):

hljs bash
docker pull threefishai/negentropy-wiki:<tag>
docker run -d --name negentropy-wiki -p 80:80 --restart unless-stopped \
  threefishai/negentropy-wiki:<tag>
# 或:docker compose -f docker-compose.wiki.yml up -d   (需把 NEGENTROPY_IMAGE_TAG 设为 <tag>)

Step 6 — 验证

hljs bash
curl -s https://<remote>/ | grep -o "<publication 名称>"
curl -s -o /dev/null -w "%{http_code}\n" https://<remote>/<pubSlug>/   # 期望 200

变体:静态托管(不走 Docker):Step 3 换成 pnpm --filter negentropy-wiki build,把 apps/negentropy-wiki/out/ 上传到远程 nginx/CDN(见 §2.3)。

#3.4 路径 B:CI 自动(publish 触发)

主站 DB 在 CI 可达时,可配置「publish 自动导出 + 提交 + 重建」闭环。

① 配置后端WikiRedeploySettings env):

环境变量
NE_KNOWLEDGE_WIKI_REDEPLOY__URLhttps://api.github.com/repos/<owner>/<repo>/dispatches
NE_KNOWLEDGE_WIKI_REDEPLOY__TOKENPAT(需 repo + workflow 权限)
NE_KNOWLEDGE_WIKI_REDEPLOY__EVENT_TYPEwiki_content_export

② 配置仓库 Secrets(GitHub Settings → Secrets):NE_DB_URL(主站 DB 只读连接串)、WIKI_CONTENT_BOT_TOKEN(有 push 权限的 PAT)。

③ 流程:主站 publish → 后端 trigger_wiki_redeploy(WARN-only,不阻塞发布)→ GitHub repository_dispatchwiki-content-export.yml 导出 content/ 并提交 → push 触发 wiki 重建校验。

⚠️ 重要限制(如实说明)content/ 提交后,wiki 镜像的重建当前并非全自动——需要 release 流水线发版,或手动 dispatch reusable-negentropy-docker.yml 重建 negentropy-wiki 镜像(workflow 注释已标注「按需扩展」)。即:自动闭环目前覆盖到「内容进仓库」,镜像重发布仍需一次手动/发版动作。

#3.5 路径 C:本地开发同步

本地开发联调(本地 wiki + 本地主站),cli.sh restart 已内置「导出 → 重建」:

hljs bash
./scripts/cli.sh restart   # 自动 sync-wiki-content.sh + pnpm build

/knowledge/wiki 选择**「测试环境」**目标「发布」时,后端会 fire-and-forget spawn scripts/build-wiki-local.sh(复用 sync-wiki-content.sh + next build), 等价于上述本地刷新的「导出 + 重建」步骤——无需手动 cli.sh restart 即可在 :3092 看到新内容。

本路径(sync-wiki-content.sh)默认 bake_assets=true:图片烘焙为 content/assets/ 自包含静态文件(与 §3.5b Pages 路径一致),经 wiki prebuild/predev 钩子(sync-assets.mjs)同步进 public/assets/out/assets/pnpm dev / serve out 均可直接显示,无需主站运行时供图。

详见 Wiki README · 本地刷新

#3.5b 路径 D:发布到 GitHub Pages(本地主站 → threefish-ai.github.io)

适用:主站纯本地(DB 不公网暴露),希望在 /knowledge/wiki 选择**「生产环境」目标「发布」后自动**把站点上线到独立的 GitHub Pages 仓库(如 threefish-ai.github.io)。

为什么不是云端 CIwiki-content-export.yml(§3.4)在 GitHub 云端 runner 跑,需 NE_DB_URL 连主站 DB——本地 DB 云端连不上。故由后端在 publish 后后台 spawn 本地脚本完成「导出 → 构建 → 推 Pages」,全程在本地。

入口:publish(target=production) → spawn publish-wiki-pages.sh(与「测试环境」目标的 build-wiki-local.sh 共享「导出 + 构建」前两步,生产目标额外 rsync + git push)。 显式目标即授权——不再依赖 ENABLED 开关(该开关保留为遗留自动发布兼容)。

图片自包含:本路径用 bake_assets=true 把图片字节烘焙进 content/assets/,经 wiki prebuild 钩子(sync-assets.mjs)同步到 public/assets/next build 复制进 out/assets/(见 §4.4),站点零主站运行时依赖——即使主站不公网可达,公网 Pages 上图片照常显示。

一次性配置

  1. 目标仓库 Pagesthreefish-ai.github.io → Settings → Pages → Source 选 Deploy from a branch,分支 master / root(user/org pages 根域默认分支即 master,无需 basePath)。

  2. 推送凭证:本地对该仓库有 push 权限。脚本支持三种凭证(优先级从高到低):① WIKI_PAGES_TOKEN 环境变量(GitHub PAT);② gh auth token(装了 gh CLI 且已登录则自动取);③ SSH key(WIKI_PAGES_REPOgit@ URL 时)。零配置最快gh auth login 后直接选「生产环境」发布即可。

    仅当需覆盖默认仓库/分支或禁用 gh auth 回退时,才需下列 env:

    hljs bash
    NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__REPO=https://github.com/ThreeFish-AI/threefish-ai.github.io.git
    NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__BRANCH=master
    # 可选:HTTPS 推送 token(缺省则脚本回退 gh auth token / SSH)
    NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__TOKEN=ghp_xxx
    # 遗留:True 时任意 publish 即推 Pages(新流程已由显式 target 触发,通常无需开启)
    # NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__ENABLED=true
    

之后:在主站点选择**「生产环境」**目标 → 点 「发布」(destructive 二次确认)→ 后端后台跑 scripts/publish-wiki-pages.sh →(导出含图片 → 构建 → 备份目标分支 → rsync out/ + push)→ 数十秒后 https://threefish-ai.github.io/ 更新。spawn 为 fire-and-forget,不阻塞 publish 接口(失败仅后端 WARN 日志)。

手动 fallback / 首次验证

hljs bash
# 手动跑(用 gh CLI 的 token 推送 master)
WIKI_PAGES_TOKEN=$(gh auth token) ./scripts/publish-wiki-pages.sh

# 演练(导出+构建+同步到 .temp 工作副本,不 commit/push)
WIKI_PAGES_DRY_RUN=1 ./scripts/publish-wiki-pages.sh

目标仓库已被占用时(重要实操):若 Pages 目标分支原本是另一个站点(如 Docusaurus),全量覆盖会清掉它。脚本默认 WIKI_PAGES_BACKUP=1覆盖前自动把目标分支当前内容推到 <branch>-archive-<时间戳> 分支兜底(可随时恢复)。确认无需保留旧站后再发布;如需保留双站,改用 §2.3 的子路径/独立仓库方案。

关键细节

  • 脚本自动写 .nojekyll(GitHub Pages 即使 build_type: legacy(Jekyll)也读它;否则 _next/ 等下划线目录被忽略,致 JS/CSS 404)。
  • out/rsync --delete 全量覆盖目标分支(保留 .gitCNAME),旧站点文件一并清理。
  • 幂等:buildId 绑定内容版本(见 §4.5),内容未变则跳过 commit,避免 noise 历史。
  • 并发安全:多次「同步并发布」触发的 spawn 经 .temp/wiki-pages-publish.lock(mkdir 原子锁 + PID 陈旧检测,portable,macOS/Linux 通用)串行化,避免目标 Pages 仓库 push 竞争(后写覆盖 / 分支冲突 / 备份分支污染);锁被持有时跳过本次,下次 publish 会带上最新内容。
  • 全流程日志:导出 / build / push 各步与失败原因落盘 .temp/wiki-pages-publish.log(后端 spawn 与手动运行同一入口;手动运行同时输出终端)。发布未生效时首选查此日志
  • token 安全:token 仅注入 git remote URL(工作副本在 gitignored .temp/),上述日志不记录 token。

#3.6 内容刷新语义

  • 纯静态 = 必须重建才更新:不存在运行时 ISR。主站 publish 后,内容要上线必须走 §3.3 / §3.4 的「导出 + 重建」。
  • 延迟:手动快照 = 人工触发(即时);CI 自动 = 一次 CI 构建(数分钟)。
  • 回退:重新部署旧 tag 镜像 / 旧 out/ 快照即可(静态产物天然可回滚)。

#4. 配置参考

#4.1 后端 WikiRedeploySettings(仅 CI 自动路径需要)

Env默认说明
NE_KNOWLEDGE_WIKI_REDEPLOY__URLNone触发端点;未配置则跳过(等价被动,等下一次手动/定时重建)
NE_KNOWLEDGE_WIKI_REDEPLOY__TOKENNoneGitHub dispatch 鉴权 PAT(Bearer)
NE_KNOWLEDGE_WIKI_REDEPLOY__EVENT_TYPEwiki_content_exportdispatch 事件类型
NE_KNOWLEDGE_WIKI_REDEPLOY__SECRETNone可选 HMAC 签名(自建 webhook 鉴权)

#4.2 CI Secrets(仅 CI 自动路径需要)

Secret用途
NE_DB_URL导出时只读访问主站 DB
WIKI_CONTENT_BOT_TOKEN提交 content/ 的 push 权限 PAT
NE_KNOWLEDGE_WIKI_EXPORT_ASSET_BASE_URL可选;主站可达前缀,把图片重写为 {base}/knowledge/wiki/documents/{doc}/assets/{file} 绝对 URL(wiki 与主站分域部署时配置;同源反代可省略)

#4.3 通用注意

  • artifact_backend:#932(GCS 退役)后枚举仅 inmemory|postgres,默认 postgres(制品持久化到 adk_artifacts 表)。若 ~/.negentropy/config.yaml 仍保留 services.artifact_backend: gcs 等失效值,请改为合法值,否则主站启动校验报错。
  • 资产/图片:#932 起 GCS 全量退役、资产转 PostgreSQL bytea。两种处理见 §4.4。

#4.4 图片烘焙(bake_assets

markdown 内的图片(/api/documents/{doc}/assets/{file})有两条互斥处理路径,由 NE_KNOWLEDGE_WIKI_EXPORT__BAKE_ASSETS 控制:

模式配置行为适用
烘焙(自包含)BAKE_ASSETS=true导出期下载图片字节写入 content/assets/{doc}/{file},markdown 改相对路径 /assets/{doc}/{file};由 wiki prebuild 钩子 sync-assets.mjs 同步到 public/assets/next build 再复制进 out/assets/公网 Pages / 本地主站——零主站运行时依赖,主站不可达也能显示图片
URL 重写BAKE_ASSETS=false(默认)+ ASSET_BASE_URL重写为 {base}/knowledge/wiki/documents/{doc}/assets/{file},运行期由主站 bytea 端点供图分域反代部署——要求主站对访客可达

publish-wiki-pages.sh(§3.5b)与 sync-wiki-content.sh(§3.5 路径 C)均默认置 BAKE_ASSETS=true(本地自包含烘焙;可显式置 false 走 URL 重写)。烘焙图片进静态产物由 wiki 端 prebuild/predev 钩子(sync-assets.mjs)统一完成,无需改动 publish/sync 脚本。烘焙失败的单张图仅 WARN 并保留原引用,不阻断正文导出。

#4.5 发布幂等(buildId)

next.config.tsgenerateBuildId 把 Next buildId 绑定到内容包各 publication 的 (slug,id,version) 哈希(剔除导出时间戳)。内容未变(version 不递增)则 buildId 不变publish-wiki-pages.sh 的 rsync 无差异 → 跳过 commit,避免每次构建因随机 buildId 产生 noise 历史。

#4.6 本地 Pages 自动发布(WikiPagesPublishSettings

Env默认说明
NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__ENABLEDfalseTrue 时 publish 后后台 spawn 脚本自动发布到 Pages
NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__SCRIPTscripts/publish-wiki-pages.sh发布脚本(相对仓库根)
NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__REPONone目标 Pages 仓库(透传脚本 WIKI_PAGES_REPO),SSH 或 HTTPS URL
NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__BRANCHmaster目标分支(透传脚本 WIKI_PAGES_BRANCH;user/org pages 默认 master)
NE_KNOWLEDGE_WIKI_PAGES_PUBLISH__TOKENNone可选 GitHub PAT(透传脚本 WIKI_PAGES_TOKEN);HTTPS 推送免 SSH key,缺省回退 gh auth token/SSH

WikiRedeploySettings(webhook → 云端 CI)正交:本地纯本地主站用本块、分域/云端用 webhook。默认关闭,不影响现有流程。 脚本另支持 WIKI_PAGES_BACKUP=1(默认)——覆盖前把目标分支备份为 <branch>-archive-<ts>


#5. 验证与故障排除

#5.1 独立性铁证

hljs bash
# 完全断网运行,仍能渲染内容(证明零运行时后端/DB 依赖)
docker run --rm -p 8080:80 --network none threefishai/negentropy-wiki:<tag> &
curl -s http://localhost:8080/            # 200 + 含真实文章

#5.2 内容为空排查

现象排查
首页「暂无已发布的 Wiki」content/publications.json 是否有 status=published 项;未导出则跑 §3.3 Step 2
详情页 404镜像内是否烘焙:docker run --rm <img> ls /public/<pubSlug>/ 应有 index.html
搜索框无效out/pagefind/ 是否一起部署(Docker 镜像已含;静态托管需手动上传)

#5.3 其他

  • 端口/反代 404trailingSlash:true 生成目录式 HTML(/pub/entry/),静态层 try_files $uri $uri/ =404 + index index.html 即可,无尾斜杠请求亦能命中目录 index,无需 SPA fallback。
  • 图片不显示:markdown 图片重写为主站资产端点 /knowledge/wiki/documents/{doc}/assets/{file}(PostgreSQL bytea 流式提供,#935)。wiki 与主站分域部署时须配置 NE_KNOWLEDGE_WIKI_EXPORT__ASSET_BASE_URL 为主站可达前缀并确保网络可达;同源反代可省略。
  • 导出报 artifact_backend 校验错:用 sync-wiki-content.sh(已置 inmemory)或手动 NE_SVC_ARTIFACT_BACKEND=inmemory

编辑本文档时,同步检查 ops.md §5/§6 与 publishing.md §8 的指针是否一致(本文档为部署 + 内容同步的单一事实源)。