HeyForm 3.0.6 常用 CLI 命令与工具详解
HeyForm 3.0.6 三容器栈 CLI 运维实测:compose 生命周期与改参重建口径、版本取证(端口 9157/changelog 404)、mongosh/mongodump+restore dryRun 备份闭环、KeyDB 持久化、CSV 导出链路(x-device-id 一致性)、官方内置 reencrypt-integration-secrets 凭据轮换 CLI、EAI_AGAIN 故障案例与升级回滚流程(实测日期 2026-11-01/02)。
HeyForm 3.0.6 常用 CLI 命令与工具详解
目标:把 HeyForm v3.0.6 三容器栈(server + MongoDB + KeyDB)日常运维用到的 CLI 命令与内置工具讲透——compose 生命周期、版本与镜像取证、mongosh/mongodump 数据操作、KeyDB 持久化、CSV 导出链路、官方唯一的内置 CLI(
reencrypt-integration-secrets)凭据轮换,以及升级流程与真实故障案例。全部命令与逐字回显来自实测环境(compose 项目目录/opt/data/quzuo/tmp/heyform,宿主 192.168.1.172,实测日期 2026-11-01/02)。部署方法见《HeyForm 3.0.6 Docker Compose 部署指南》,参数语义见《HeyForm 3.0.6 配置参数详解》。
一、运维对象速览
| 容器 | 镜像 | 角色 | 端口口径(实测) |
|---|---|---|---|
heyform-306-server | heyform/community-edition:v3.0.6 | NestJS 应用(Node v22.23.4) | 容器内 9157,宿主映射 0.0.0.0:8150->9157/tcp |
heyform-306-mongo | mongo:8.2 | 主数据库(26 个集合) | 仅 compose 内网 27017/tcp |
heyform-306-keydb | eqalpha/keydb:x86_64_v6.3.3 | 队列/缓存(redis_version 6.3.3,AOF 开) | 仅 compose 内网 6379/tcp |
$ docker compose ps
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
heyform-306-keydb eqalpha/keydb:x86_64_v6.3.3 "docker-entrypoint.s…" keydb 2 hours ago Up 2 hours 6379/tcp
heyform-306-mongo mongo:8.2 "docker-entrypoint.s…" mongo 2 hours ago Up 2 hours 27017/tcp
heyform-306-server heyform/community-edition:v3.0.6 "docker-entrypoint.s…" heyform 32 seconds ago Up 21 seconds 0.0.0.0:8150->9157/tcp, [::]:8150->9157/tcp
两个数据库容器不发布宿主端口,一切数据操作走 docker exec——这本身就是最小权限的正确姿势。
二、compose 生命周期命令
docker compose ps # 状态与端口口径
docker compose up -d # 改 env/镜像后重建(仅重建定义有变化的服务)
docker compose up -d --force heyform # 定义没变但想强制重建
docker compose restart heyform # 只重启不重建(env 改动不会重新注入,别用它改参)
docker compose logs -f --tail=100 heyform
docker compose down # 删容器保卷(数据在 ./mongodb_data 等 bind mount,安全)
docker stats --no-stream # 资源基线
实测要点:
- 改参必须
up -d,不能restart。环境变量在容器创建时注入,restart沿用旧 Config.Env;up -d检测到 env 定义变化会重建容器(第六节闭环实测回显Container heyform-306-server Started)。 - 资源基线(2026-11-02 实测,空闲态):server
159.2MiB、mongo278.3MiB、keydb15.72MiB(宿主总内存 15.57GiB,CPU 均 ~0%)。三容器合计 <450MiB,1 核 2G 的机器即可承载小规模使用。 docker compose logs会带出历史崩溃的堆栈(如 helmet 中间件栈),看日志用时间戳分段,勿把旧事故当新故障。
三、版本与镜像取证命令
$ docker exec heyform-306-server node -v
v22.23.4
$ docker exec heyform-306-server cat /app/packages/server/package.json | head
{
"name": "server",
"version": "3.0.6",
"homepage": "https://heyform.net",
"author": "Heyooo, Inc.",
$ docker exec heyform-306-server sh -c "netstat -ltn || ss -ltn"
tcp 0 0 127.0.0.11:37231 0.0.0.0:* LISTEN # Docker 内嵌 DNS
tcp 0 0 0.0.0.0:9157 0.0.0.0:* LISTEN # 应用真实监听
$ docker exec heyform-306-server sh -c "wget -q -O- http://127.0.0.1:8000/health"
wget: can't connect to remote host (127.0.0.1): Connection refused # 官方文档样例端口 8000 已失效
- 镜像内保留完整源码(
/app/packages/server/src)与编译产物(dist/),取证友好:默认值直接读dist/environments/index.js(APP_LISTEN_PORT行原文const APP_LISTEN_PORT = +process.env.APP_LISTEN_PORT || 9157;)。 - 进程模型:PID 1 为
node --enable-source-maps ./dist/main.js,cwd/app/packages/server;入口docker-entrypoint.sh对dist/main.js|dist/src/main.js|dist/packages/server/main.js三选一探测。 /api/changelog路由存在但返回 404({"message":"Cannot GET /api/changelog","statusCode":404}),别依赖它做升级差异核对;版本信息以 package.json 与镜像 tag 为准。- 环境注入核对(排查"改参没生效"):
docker inspect heyform-306-server --format '{{json .Config.Env}}' | grep 参数名。
四、MongoDB 数据操作
# 集合清单(26 个)与计数
$ docker exec heyform-306-mongo mongosh --quiet heyform --eval 'db.getCollectionNames().length'
26
$ docker exec heyform-306-mongo mongosh --quiet heyform --eval '
["usermodels","formmodels","submissionmodels","integrationmodels","integrationrecordmodels"]
.forEach(c => print(c, db[c].countDocuments({})))'
usermodels 2
formmodels 8
submissionmodels 3
integrationmodels 0
integrationrecordmodels 0
26 个集合按用途分组:账号(usermodels/usersocialaccountmodels/team*/project*)、表单(formmodels/templatemodels/brandkitmodels)、提交与分析(submissionmodels/formanalyticmodels/formopenhistorymodels/formreportmodels/submissioniplimitmodels)、集成(integrationmodels/integrationconnectionmodels/integrationrecordmodels/thirdpartyoauthmodels)、杂项(apptokenmodels/appcodemodels/appmodels/emailtemplatemodels/useractivitymodels)。
常用查询(实测可用):
docker exec heyform-306-mongo mongosh --quiet heyform --eval 'db.formmodels.find({},{id:1,name:1,status:1}).limit(10)'
docker exec heyform-306-mongo mongosh --quiet heyform --eval 'db.submissionmodels.findOne({},{category:1})' # {"_id":"...","category":"inbox"}
docker exec heyform-306-mongo mongosh --quiet heyform --eval 'db.usermodels.deleteMany({email:/@heyform\.local$/})' # 清测试账号:deletedCount: 2
备份与校验(2026-11-02 实测闭环):
$ docker exec heyform-306-mongo sh -c 'mongodump --db heyform --archive --gzip' > /tmp/hf-backup-t3.archive.gz
2026-11-01T19:46:51.322+0000 writing `heyform.useractivitymodels` to `archive on stdout`
2026-11-01T19:46:51.323+0000 writing `heyform.submissionmodels` to `archive on stdout`
...
-rw-r--r-- 1 root root 6109 Nov 2 03:46 /tmp/hf-backup-t3.archive.gz # 全库归档仅 6KB
$ cat /tmp/hf-backup-t3.archive.gz | docker exec -i heyform-306-mongo \
mongorestore --db heyform --archive --gzip --dryRun
2026-11-01T19:46:51.450+0000 preparing collections to restore from
2026-11-01T19:46:51.450+0000 dry run completed
2026-11-01T19:46:51.450+0000 0 document(s) restored successfully. 0 document(s) failed to restore.
--dryRun 校验归档可读、命名空间可映射,不写数据;生产恢复去掉 --dryRun 并加 --drop(先删同名集合再灌入)。备份节奏建议:cron 每日 mongodump --archive --gzip 落盘 + 异机拷贝;表单提交量大的实例注意归档体积随 submissionmodels 增长。
五、KeyDB(Redis 兼容)操作
$ docker exec heyform-306-keydb redis-cli INFO server | head -6
# Server
redis_version:6.3.3
redis_git_sha1:86ea5c7e
redis_git_dirty:1
redis_build_id:942bbf4eda83edde
$ docker exec heyform-306-keydb redis-cli CONFIG GET appendonly
appendonly
yes
KeyDB 完全兼容 redis-cli 语法。HeyForm 用它跑 Bull 队列(webhook 投递、邮件、清理任务)与限流计数:
docker exec heyform-306-keydb redis-cli KEYS 'bull*' # 队列键
docker exec heyform-306-keydb redis-cli LLEN bull:webhook:wait # 积压长度(按实际队列名)
docker exec heyform-306-keydb redis-cli DBSIZE
docker exec heyform-306-keydb redis-cli BGSAVE # 落盘:dump.rdb + appendonly.aof
实测 BGSAVE 后数据目录产物:dump.rdb(约 6KB)+ appendonly.aof(约 87KB),均落在 bind mount ./keydb_data,容器删除重建不丢。队列是可再生状态:极端情况下清空 Redis 只丢未执行的作业与限流窗口,不丢业务数据(业务全在 Mongo)。
六、改参 → 重建 → 验证 → 回滚闭环(CLI 版)
以 APP_DISABLE_REGISTRATION 为例(2026-11-02 实测逐字回显):
# 1. 基线:注册开放(GraphQL signUp 成功)
{"data":{"signUp":true}}
# 2. compose 的 environment 段加入 APP_DISABLE_REGISTRATION: 'true' → 重建
$ docker compose up -d heyform
Container heyform-306-server Started
# 3. 公开探针验证生效(无需鉴权,最快判据)
$ curl -s http://localhost:8150/api/config | jq .appDisableRegistration
appDisableRegistration = True
# 4. 行为验证:注册被拒
{"errors":[{"code":"BAD_REQUEST","message":"Error: Registration is disabled"}],"data":null}
# 5. 回滚:删该行 → 重建 → 探针 False → signUp 恢复 true
appDisableRegistration = False
{"data":{"signUp":true}}
两个实用细节:/api/config 是无鉴权探针,改参生效与否先看它,再做行为验证;GraphQL 写操作必须带 x-device-id 头且与 session 内 deviceId 一致,否则 403 Forbidden request error(实测 DID 不匹配时 CSV 导出直接 400 空响应,匹配后 200)。
CSV 导出链路(CLI 版):
# 登录拿 cookie(-c 存 jar)
$ curl -s -c /tmp/hf_cookie.txt -X POST http://localhost:8150/graphql \
-H 'Content-Type: application/json' -H "x-device-id: $DID" \
-d '{"query":"mutation { login(input: {email:\"admin@heyform.local\",password:\"Q1w2e3r4\"}) }"}'
{"data":{"login":true}}
# 导出(REST 控制器 GET /api/export/submissions)
$ curl -s -b /tmp/hf_cookie.txt -H "x-device-id: $DID" \
'http://localhost:8150/api/export/submissions?formId=VUnqcZMz'
"#","Your Name","Plan","Score","Start Date (UTC)","Submit Date (UTC)"
"6ae77bfcedc7952478a66c44","Fail Case","Free",2,"2026-11-01T17:50:52.000Z","2026-11-01T17:50:52.000Z"
HTTP:200
无 session 直接 401 {"message":"Unauthorized"};archive=true 变体在 v3.0.6 实测返回的仍是 CSV 文本(171B ASCII)而非 gzip 包,脚本里别按 gzip 解压它。
七、官方内置 CLI:reencrypt-integration-secrets
镜像内 dist/cli/ 只有一个编译好的运维 CLI(2026-11-02 实测):
$ docker exec heyform-306-server ls /app/packages/server/dist/cli
reencrypt-integration-secrets.js
reencrypt-integration-secrets.js.map
源码头部注释逐字(即官方用法说明):
Re-encrypts stored integration credentials (connection tokens, API keys)
that still use INTEGRATION_ENCRYPTION_KEY_PREVIOUS with the current
INTEGRATION_ENCRYPTION_KEY, so the previous key can be removed. Run it with
the same environment as the server, after restarting it with both keys:
node dist/src/cli/reencrypt-integration-secrets.js (built server, e.g. Docker)
pnpm integrations:reencrypt (source checkout)
守卫行为实测——不带 INTEGRATION_ENCRYPTION_KEY 试跑,立即报错退出、不碰数据库:
$ docker exec -w /app/packages/server heyform-306-server \
node dist/cli/reencrypt-integration-secrets.js
INTEGRATION_ENCRYPTION_KEY is not set
凭据轮换三步(与《配置参数详解》5.6 呼应):
- compose 同时配
INTEGRATION_ENCRYPTION_KEY(新)+INTEGRATION_ENCRYPTION_KEY_PREVIOUS(旧)→docker compose up -d; - 容器内跑上述 CLI,把用旧密钥加密的
integrationconnectionmodels凭据重灌为新密钥; - 删除
..._PREVIOUS行 → 再up -d。
注意区分:SESSION_KEY 泄露只需换值重启(旧会话失效);FORM_ENCRYPTION_KEY 不可换(已分发链接全废);只有集成凭据的轮换需要这个 CLI。另外 package.json 里的 migrate:seed、start 脚本依赖 scripts/ 目录,生产镜像未打包该目录,仅源码 checkout 可用。
八、真实故障案例与升级流程
案例 1:启动期 EAI_AGAIN(compose 依赖顺序)。冷启动时 server 先于 mongo 就绪,日志刷 MongooseServerSelectionError: getaddrinfo EAI_AGAIN mongo 若干次后自愈。教训:给 mongo/keydb 加 healthcheck + server 侧 depends_on: {condition: service_healthy},否则升级窗口内会看到一段吓人的报错日志(服务本身能自愈,无需干预)。
案例 2:/api/changelog 404。controller 在编译产物里存在但路由不通(见第三节),升级核对不要走这条路,直接对比 GitHub Releases。
升级流程(发布节奏实测:v3.0.4 10-15、v3.0.5 10-24、v3.0.6 11-01,约 2-4 周一版;latest 与 v3.0.6 同 digest,2026-11-01):
docker exec heyform-306-mongo sh -c 'mongodump --db heyform --archive --gzip' > backup-$(date +%F).gz
sed -i 's|community-edition:v3.0.5|community-edition:v3.0.6|' docker-compose.yml
docker compose pull heyform && docker compose up -d heyform
curl -s http://localhost:8150/health # {"status":"ok",...}
docker compose logs --since 5m heyform | grep -i error # 无新错误即成
回滚 = 反向改 tag + up -d。小版本升级无 schema 迁移脚本(生产镜像不含 migrate 工具),Mongo 数据向后兼容;若跨大版本(如 2.x→3.x)先读 Release Notes 再动。
九、命令速查表
| 场景 | 命令 |
|---|---|
| 状态/端口 | docker compose ps |
| 改参生效 | docker compose up -d(restart 无效) |
| 健康 | curl :8150/health |
| 配置探针 | curl :8150/api/config(无鉴权) |
| 版本 | docker exec heyform-306-server node -v + package.json(changelog 路由 404 勿用) |
| 集合计数 | mongosh --quiet heyform --eval ... |
| 备份 | mongodump --db heyform --archive --gzip > f.gz |
| 校验 | mongorestore --archive --gzip --dryRun < f.gz |
| 队列/落盘 | redis-cli BGSAVE / KEYS bull* |
| 导出 | GET /api/export/submissions?formId=xx(session + x-device-id 一致) |
| 凭据轮换 | node dist/cli/reencrypt-integration-secrets.js(双键并存重启后跑) |
关联文章:部署与端口口径见《HeyForm 3.0.6 Docker Compose 部署指南》;参数全表与生效方式见《HeyForm 3.0.6 配置参数详解》;选型与社区数据见《HeyForm 3.0.6 介绍与选型指南》。