部署演示站点
演示模式(http.tokens 里的 scope: demo 令牌)已经删除(ADR 0049)。演示站点现在就是一台普通的 Zhin Host:网关 + 实例,开一个只读账号,把用户名和密码公开出来,访客用托管的 Console(console.zhin.dev,或 Cloudflare 上的 zhin-demo-console 项目)登录。访客看到的是真实的后端,权限由账号的角色决定,而不是一份写死的 RPC 白名单。
本页只描述部署方法,不包含任何现成的演示服务器。
1. 准备一个专用项目
演示站点用一个单独的项目,不要在生产 Bot 上开演示账号:只读账号能看到状态、日志、账号列表、会话与收件箱、功能配置(机密字段以掩码返回)、数据库内容与项目文件。
npm create zhin-app zhin-demo -- -y --adapter sandbox
cd zhin-demo只装演示需要的插件与适配器(Sandbox 不需要任何平台账号)。不要配置真实平台凭据、模型 Key 或任何私人数据。
zhin.config.yml里 Host 绑定回环地址,由反向代理对外提供 HTTPS:yamlhttp: host: 127.0.0.1 port: 8068 token: ${HTTP_TOKEN} # 初始化令牌,创建所有者后失效 corsOrigins: # console.zhin.dev 总是自动并入;用自己的 Console 域名时在这里列出 - "https://zhin-demo-console.pages.dev"不要打开终端:保持
console.terminal.enabled: false(默认值)。
2. 启动网关与实例
用进程管理器托管前台网关,命令与生产部署相同(模板见生产部署与运维):
pnpm install --frozen-lockfile
zhin start --foreground --mode production --no-watch # 即 pnpm start确认网关与实例都在运行:
zhin status
curl --fail http://127.0.0.1:8068/pub/health
curl --fail http://127.0.0.1:8068/pub/ready3. 创建所有者与演示账号
所有者只给维护者自己用。在服务器上、以运行服务的同一个系统用户创建(不需要经过浏览器,也不暴露初始化令牌)。网关在运行时这些命令经本地控制套接字交给网关,未运行时直接写 .zhin/gateway.db;在反向代理或公网入口开放之前完成这一步——所有者创建前,初始化令牌对能连上 Host 的任何人都是全权凭据:
# 所有者:初始化(第一次运行时)
printf '%s\n' "$OWNER_PASSWORD" | zhin console init --yes --owner maintainer --password-stdin
# 演示账号:默认角色就是 viewer
printf '%s\n' "$DEMO_PASSWORD" | zhin console user create demo --role viewer --password-stdin
zhin console user list密码至少 10 位。演示密码是要公开的,但仍然不要复用任何别处的密码。
用收窄的角色代替内置 viewer(推荐)
内置 viewer 含所有 *.view / *.read 权限(不含 ops.config.read、ops.env.read),其中包括 ops.database.read(数据库浏览)、ops.files.read(项目文件)、ops.schedule.read 与 plugin.perms.read。演示站点通常不需要让访客翻数据库和文件。用所有者登录 Console,在「访问控制 → 角色」复制 viewer,去掉不想公开的权限,例如:
| 保留 | 去掉 |
|---|---|
status.view、endpoint.view、feature.view、agent.view | ops.database.read、ops.files.read、ops.schedule.read |
演示插件需要的 plugin.perms.read 或具体的 <插件>:read | 其他插件的读权限 |
然后把演示账号绑到这个角色:zhin console user create demo --role demo-viewer --password-stdin,或在 Console「访问控制 → 用户」里改绑定。要让访客在 Sandbox 里发消息,再加上 endpoint.operate(/sandbox WebSocket 需要它);不加则只能看。
4. 让托管 Console 连接演示后端
Console 是纯静态站点,连接哪台 Host 由地址栏决定。公开的链接只带 API 地址,不带任何令牌:
https://console.zhin.dev/?apiBaseUrl=https%3A%2F%2Fdemo-api.example.orgapiBaseUrl不含/api,Console 自己拼接http.base。Console 读取参数后会从地址栏移除;第一次连接未知 Host 时会请访客确认。- 使用 Cloudflare 上的
zhin-demo-console项目(Console 仓库的同一份构建,npm run build:demo)时,链接换成它的域名,并把该域名加进http.corsOrigins。 - 在演示页面上公开:Console 链接、用户名
demo、演示密码,以及「这是只读演示,数据会定期重置」之类的说明。
访客打开链接 → 输入用户名和密码登录 → 看到的是演示账号的角色允许的分区与页面,其余分区整体隐藏,写操作返回 403。
5. 维护
- 重置演示数据:定期停止服务、恢复一份干净的数据库快照(SQLite 连同
-wal/-shm)再启动。账号与角色也存在这个数据库里,快照里要包含它们。 - 改演示密码:
zhin console user passwd demo --password-stdin,该账号的全部会话立即失效。 - 暂停演示:
zhin console user disable demo;恢复用enable。 - 看谁做了什么:所有者在「访问控制 → 审计日志」查看登录成功与失败、被拒绝的请求。
- 升级:网关、实例和 CLI 同批升级,见升级到管理网关。
加固清单
- [ ] 只开只读账号:演示账号绑定
viewer或更窄的自定义角色;不给operator及以上,不给任何*.write/*.manage/ops.restart/iam.*/plugin.manage。 - [ ] 不开终端:
console.terminal.enabled保持false。终端只对持有ops.terminal的所有者开放,但演示机上没有理由打开它。 - [ ] 专用项目、无真实数据:不配置真实平台凭据、模型 Key;
.env只有HTTP_TOKEN。 - [ ] 初始化之后再公开:先创建所有者再开放端口;初始化完成后
http.token在所有接口上失效。初始化令牌用随机值,不要写进公开的 README、镜像或演示页面。 - [ ] 反向代理:Host 绑定
127.0.0.1,由 nginx / Caddy / Cloudflare Tunnel 提供 HTTPS;保留Authorization头、SSE 流式响应(不要缓存/api/events)与 WebSocket Upgrade。 - [ ] 限速:网关自带登录限速(同一 IP 与用户名 15 分钟内 5 次失败后指数退避,返回
429)。默认不读取X-Forwarded-For,经代理进来的请求共用代理的地址——登录限速会对所有访客一起生效,审计里的 IP 也是代理地址。设置http.trustProxy(如[loopback],见生产部署)后网关按受信任代理的转发头取客户端地址;同时仍在反向代理上按客户端 IP 给/api/auth/login与/api/*加限速,日志里保留真实 IP。 - [ ] 反向 WebSocket 配
access_token:演示里用到 OneBot / NapCat / Milky 的反向 WebSocket 时必须配access_token。没配时网关只接受回环连接,而经同机反向代理进来的连接看起来正是回环。 - [ ] CORS 只列需要的源:
http.corsOrigins只写演示 Console 的域名(console.zhin.dev自动并入)。 - [ ] 只暴露需要的路由:在反向代理上只转发
/api/*、/pub/*与演示需要的页面 / WebSocket,其余返回 404。 - [ ] 定期重置与轮换:按计划恢复干净快照、轮换演示密码,并检查审计日志里的异常拒绝。