warmblyctl

用于 Warmbly 实例的本地运维恢复、经远程批准的管理员诊断,以及使用 API 密钥调用公共接口的命令。

有两个 CLI,本页介绍管理员使用的那个

warmblyctl 用于运维实例,包括账户、健康状态、恢复和备份。本地运维命令使用后端容器内的数据库。远程管理员命令则从你自己的机器,使用单独批准的 MFA 会话,不需要数据库凭据。

如果想从终端使用产品功能,例如营销活动、联系人、邮箱和收件箱,请使用 warmbly:它是安装在自己机器上的已登录多主机客户端,使用 warmbly auth login,无需导出密钥。

warmblyctl 是 Warmbly 实例 CLI,有三种刻意分离的信任模型。

运维命令回答两个问题:当前安装处于什么状态,以及如何恢复访问。它们直接读写数据库,因此登录页面失效时仍可工作。其授权来自容器或主机访问权,与 Sentry 的 createuser、Gitea 的 admin user create 和 authentik 的 ak changepassword 采用相同信任模型;当故障恰恰出在身份系统时,这种方式才适用。

API 命令使用 API 密钥,通过运行中实例的公开 REST API 操作,因此可从任意机器访问你持有密钥的任何实例,包括托管服务。它们让脚本和 AI 智能体能够操作产品本身,包括营销活动、联系人、邮箱、收件箱和设置。所有能力都受密钥权限范围限制。

远程管理员命令使用在现有管理员网站批准的独立登录会话。它们要求实时平台权限、会话 MFA,以及服务器现有的新近身份验证和审计保护。平台管理访问权不意味着组织成员资格,不会选择工作区或签发 API 密钥。这些命令绝不回退到本地数据库权限或 WARMBLY_API_KEY。

CLI 从不提供 HTTP 服务。API 命令只是已有权限控制的公开 API 客户端,不会为实例增加新入口。

这些 API 命令早于 warmbly CLI,仍按下方文档正常工作。日常产品操作更适合使用 warmbly:它替你完成登录,为每个主机保存凭据,输出表格而非原始 JSON,并运行在笔记本上而不是容器内。

运行方式

后端镜像在 /usr/local/bin/warmblyctl 提供该二进制,因此每种运行环境都可从 PATH 找到。文档建议在后端内部运行,因为那里环境已正确配置。

docker compose -p warmbly exec backend warmblyctl status   # docker compose
docker exec -it warmbly-backend warmblyctl status          # plain docker
kubectl exec -it deploy/warmbly-backend -- warmblyctl status
warmblyctl status                                          # bare binary

本地运维示例使用 Compose 形式,远程管理员示例则在你自己的机器运行二进制。

Compose 安装中,make cli 等效且输入更少。参数通过 ARGS 传递,因为 Make 会把直接出现的 --email 当作自己的选项:

make cli status
make cli setup-link
make cli ARGS="user create --email [email protected] --admin"

终端与管道

这是 CLI 无法替你隐藏的一项 shell 机制。

docker compose exec 会分配 TTY,除非传入 -T。需要交互提示的命令需要 TTY,通过管道输入的命令则不能有。

目的命令
交互输入密码docker compose -p warmbly exec backend warmblyctl ...
通过管道输入密码printf '%s' 'your-password' | docker compose -p warmbly exec -T backend warmblyctl ... --password-stdin

设置密码的命令在非 TTY 环境会拒绝执行,除非提供 --password-stdin,避免创建无人能登录的账户。

从环境中读取哪些配置

变量使用者缺失时
PRIMARY_DB除 hash-password 外的本地数据库运维命令命令停止,并提示在后端容器内运行
REDISsetup-link 始终需要,reset-password 生成链接时需要setup-link 失败;其余情况降级为警告并继续
AUTH_SECRETreset-password 生成链接时无法用后端验证的密钥签署链接,因此拒绝
APP_URL所有打印链接和登录提示链接基于 https://app.warmbly.com 生成,指向托管服务而非你的实例
KMS_PROVIDER 及其密钥org export、org import命令停止;无法打开工作区密封值,归档将无法使用
CREDENTIALS_ENCRYPTION_KEYorg export、org import显示警告,不读取或写入邮箱凭据,其他内容仍迁移
CREDENTIALS_ENCRYPTION_KEY、KMS_LOCAL_MASTER_KEYbackup、restorebackup 警告并不将它们放入备份包;restore 在主机密钥与备份不同的时候拒绝
BLOB_FS_ROOTbackup、restore默认为 Compose 挂载位置 /data/blobs
WARMBLY_API_KEY所有 API 命令命令停止,并说明如何获取密钥
WARMBLY_API_URL所有 API 命令回退到实例自身 API_PUBLIC_URL,再回退到托管服务

backup 和 restore 还需要 pg_dump 和 psql,后端镜像正是为此提供它们。在后端容器内,除你自己的 API 密钥外,这些值都已设置。在容器外,应导出命令所需值,并确保 AUTH_SECRET 与后端完全一致。

远程管理员命令不需要上述任何实例秘密值,也不会通过 API_PUBLIC_URL、WARMBLY_API_URL 或 WARMBLY_API_KEY 选择实例或验证身份。其唯一配置目录依赖是操作系统的用户配置位置,Linux 上设置后使用 XDG_CONFIG_HOME。

远程管理员命令

在另一台主机上安装

后端镜像仍包含 warmblyctl,用于本地紧急恢复。要在笔记本或独立虚拟机使用远程命令,请从可信源码检出,用仓库要求的 Go 工具链仅编译这个二进制,再安装到 PATH:

go build -o ./warmblyctl ./cmd/warmblyctl
install -m 0755 ./warmblyctl "$HOME/.local/bin/warmblyctl"

若 $HOME/.local/bin 不存在,先创建。这不是 warmbly 安装器,后者提供独立的公开产品 CLI。远程凭据存储目前支持 Linux、macOS 和 BSD,要求文件系统能够验证 POSIX 所有权、权限和文件锁。Windows 及其他不支持的平台会拒绝远程存储,不会保存未受保护的凭据。

先部署后端设备登录路由和管理员网站的 /device 同意页面。旧后端可以拒绝新路由,不影响任何现有本地运维或 API 密钥命令。远程客户端不需要数据库迁移,也不需要访问 PRIMARY_DB。

登录、身份查询与退出登录

warmblyctl login --url https://api.example.com --server laptop --client-name 'work laptop'
warmblyctl whoami --server laptop
warmblyctl logout --server laptop

--url 是后端来源,不是管理员前端 URL。必须是纯 HTTPS 来源,不能包含凭据、路径、查询或片段。HTTP 只允许回环开发使用。所有重定向都会拒绝,包括同源重定向,因此应直接指向后端标准来源。没有不安全 TLS 参数,也没有由环境选择的默认服务器。

登录会将所选后端、请求者、查询代码和 /device 验证路径打印到 stderr。在任意机器打开已配置管理员网站的 /device 页面,通过原有 OAuth、通行密钥或密码流程登录并完成 MFA,输入查询代码,再明确批准指定请求者和实例。管理员网站可以位于不同前端来源,CLI 绝不会向那里发送凭据。仅查看请求不等于批准。

客户端比较返回的实例身份与所选后端,按提供的轮询间隔等待,只保存最终普通会话令牌对。私有设备秘密值只在进程内存中,绝不打印、保存、放入 URL 或交给浏览器。CLI 不打开浏览器,也不提供 HTTP 服务。批准五分钟后过期;认领响应丢失时必须重新登录,不能重放。如果来源会话的原身份验证证明已过期,应重新在浏览器验证并开始新请求。远程客户端不提供密码或绕过 MFA 的选项。

whoami 读取 /v1/auth/me,输出经过安全筛选的身份 UUID、实时管理权限和 MFA 状态。任意显示名称、地址和自由文本都会脱敏。登录成功只打印会话到期时间,不打印令牌值。

每个远程请求都要求 --url 或已登记的 --server。只用 URL 登录时,使用该精确来源的 default 配置,不会设置全局默认。命名配置绑定到 login --server NAME --url URL 登记的来源。冲突 URL 会被拒绝;不同实例或管理员会话应使用其他配置名称。退出登录会保留这个非秘密服务器映射。

刷新发生在到期前,或一次读取被拒绝后。轮换凭据先原子保存再使用,本地进程用文件锁串行刷新。刷新失败或结果不明确时,只丢弃受影响配置,因为旧刷新令牌可能已被消耗。读取最多重试一次,写入绝不自动重复。权限、MFA 和新近身份验证拒绝不会触发刷新。刷新不等于重新验证:遇到 reauth_required,应退出并开始新的浏览器明确批准登录,或使用新的命名配置。

普通退出会撤销独立 CLI 会话,只有确认成功后才删除本地凭据。传输失败会保留凭据,供之后明确重试;身份验证被拒绝则忘记凭据。logout --local-only 只明确删除本地记录,不撤销服务器会话;其 JSON 显示 revoked:false,服务器会话仍有效,直到另行撤销或过期。

只有确实需要时,才撤销此用户在该后端上的所有会话:

warmblyctl logout --server laptop --all --write \
  --confirm 'POST /v1/auth/logout-all'

服务器现有的退出全部会话行为适用,包括忘记可信设备。同一用户和实例的匹配本地配置会删除,不影响其他实例和用户。设备会话签发后,仅撤销批准它的浏览器会话,不会撤销独立 CLI 会话。请使用正常会话管理或全部撤销。

私密凭据文件

该二进制尚未使用支持的操作系统钥匙串库,因此回退为原子写入的 0600 凭据文件,保存在仅所有者可访问的 0700 目录中。Linux 路径为 $XDG_CONFIG_HOME/warmblyctl/credentials.json,或 $HOME/.config/warmblyctl/credentials.json;macOS 位于用户的 Library/Application Support/warmblyctl 下。令牌是由文件系统所有权和权限保护的明文秘密值,不使用应用密钥加密。

客户端检查所有者身份、普通文件类型、精确权限和硬链接。现有存储不安全时会拒绝,不会悄悄修正权限。写入使用私有临时文件、同步、原子重命名和目录同步。固定目录句柄限定文件访问范围。仅所有者可访问的锁文件,保护刷新令牌不被并发进程同时轮换。远程参数或保存配置绝不接受密码。不要将此目录复制到仓库、诊断归档或其他用户账户。

现有配置祖先目录,包括符号链接的主目录或配置目录,会在创建或打开存储前,先解析一次为标准路径。每个标准祖先必须由 root 或有效用户所有,且不允许组和其他用户写入,带粘滞位的 /tmp 等目录除外。最终固定的凭据目录仍必须由有效用户所有,且权限精确为 0700;最终目录、锁文件和凭据文件仍禁止符号链接。凭据文件要求精确 0600 和单个硬链接。不可信目标或悬空祖先会保守拒绝,不创建凭据存储。

只读运维辅助命令

warmblyctl admin --help
warmblyctl admin workers list --server laptop
warmblyctl admin workers stats --id <worker-uuid> --server laptop
warmblyctl admin fleet nodes --server laptop
warmblyctl admin fleet capacity --server laptop
warmblyctl admin fleet decisions --server laptop
warmblyctl admin logs --node-id <worker-uuid> --level warn --limit 100 --server laptop
warmblyctl admin broker --node-id <worker-uuid> --server laptop
warmblyctl admin mailbox list --q example.com --server laptop
warmblyctl admin sync list --state stalled --server laptop
warmblyctl admin sends in-flight --server laptop
warmblyctl admin recovery dead-letters --status pending --server laptop
warmblyctl admin recovery failures --server laptop
warmblyctl admin recovery webhooks --server laptop
warmblyctl admin jobs list --server laptop
warmblyctl admin health monitoring --server laptop

在辅助命令后运行 --help,查看类型化 UUID、时间、枚举和数量筛选。日志还接受 --after、--before,两者为包含边界的 RFC3339,以及 --mailbox-id。分页列表接受端点返回的精确 next_cursor,不是页码。辅助命令在访问凭据前拒绝无效筛选。其他现有筛选可通过下方目录请求命令使用。

AI 诊断应保持只读:在断定故障前,先检查可用性、覆盖、观察时间戳、丢弃事件、近期证据和范围。空或不可用证据不证明健康。节点日志是后端允许列出的部分事件历史,不是原始系统日志或服务商对话。没有与登记绑定的日志凭据的旧工作节点,需要升级并经过身份验证重新登记。使用相同配置目录和选项重新运行现有加入脚本;它会读取现有节点 UUID,保留本地覆盖,无需删除节点集群记录、移动邮箱或手动复制秘密值。服务会重启。仅升级无法安全地用集群共享代理凭据换取节点自身身份。在收到捕获数据之前,证据保持不可用,也不回填历史。参见升级流程。

消息代理观察将 commands,即此工作节点命令,与 results,即共享结果消费者,分开显示。延迟是已提交偏移量距离,不是投递成功、吞吐量或特定节点故障证明。权限、提交或依赖缺失都明确显示不可用。没有命令会重置偏移量。

在途发送行保留 has_message_id 和时长信息,但省略正文、邮件头和自由格式失败文本。不要因空日志或缺少邮件 ID,就推断未知结果从未发送。这些命令不能清除不确定发送暂停、禁用邮箱或悄悄重发。

需显式执行的恢复操作

每次管理修改都必须提供指定操作,并且提供 --write,还必须提供精确的 --confirm 'METHOD /admin/concrete-target-path',不包含查询参数。这是运维保护,不替代服务器权限、MFA、新近身份验证或审计日志。没有通用 --yes,写入也不自动重试。传输错误意味着操作可能已到达服务器;再次尝试前应检查状态。

服务商限流恢复和回填重启使用各自现有服务器流程,不用于清除未知发送:

warmblyctl admin recovery clear-throttle --id <mailbox-uuid> --server laptop \
  --write --confirm 'POST /admin/sync/<mailbox-uuid>/clear-throttle'
warmblyctl admin recovery restart-backfill --id <mailbox-uuid> --server laptop \
  --write --confirm 'POST /admin/sync/<mailbox-uuid>/restart-backfill'

两处应替换为同一个真实标准 UUID。清除服务商限流可能恢复服务商工作,重启回填会重置现有同步回填状态。两者都不建立发送授权。

死信重放从来不是诊断的一部分。只有不发送邮件的营销活动唤醒轮次可以重放;具备发送能力的死信保持待处理,直到通过正常恢复独立核对结果。先检查任务、邮箱、服务商回执和不确定发送保护。重放还要求单独确认风险:

warmblyctl admin recovery replay --id <dead-letter-uuid> --server laptop \
  --write --acknowledge-send-risk \
  --confirm 'POST /admin/tasks/dead-letters/<dead-letter-uuid>/replay'

确认风险不证明邮件未投递,也不覆盖服务器恢复保护。绝不能仅因 CLI 诊断没有证据就重放不确定发送。Webhook 租约恢复单独提供为 admin recovery reclaim-webhooks,需要对 POST /admin/webhooks/reclaim 精确确认。

已登记的管理 API 请求

warmblyctl admin catalog
warmblyctl admin api GET /admin/users --server laptop --query limit=25
warmblyctl admin api GET /admin/instance/settings --server laptop
warmblyctl admin api PUT /admin/workers/<worker-uuid>/tags --server laptop \
  --data @worker-tags.json --write \
  --confirm 'PUT /admin/workers/<worker-uuid>/tags'

离线、确定排序的目录列出每个已登记管理方法和路径模板、权限名称、新近身份验证要求、读写分类、重放风险门槛和不可用原因。参数模板应替换为标准 UUID;少数非 UUID 参数使用有长度边界的单路径段词。请求命令只匹配编译进来的清单,不接受任意 URL、/v1 路由、路径穿越、编码路径或虚构端点。即使 CLI 目录旧于服务器,服务器保护仍是最终依据。

每个有界筛选分别提供 --query key=value。修改操作的 JSON 输入必须是单个对象,来自 --data @file,或使用 --data - 从 stdin 读取,最多 1 MiB,不允许重复键或过深嵌套。内联 JSON 有意被拒绝,避免密码进入 argv。输入、请求头和请求体都不会回显。响应必须为不超过 8 MiB 的 JSON;没有原始输出或显示秘密值参数。

部分目录条目可发现,但有意不可用:浏览器设备同意端点、节点集群加入令牌和测试器凭据签发、二进制导出下载和 multipart 导入,以及任意后台作业执行。此类流程请使用现有浏览器或传输/运维工具。即使给出确认,不支持条目也会在网络或凭据访问前失败。

JSON、敏感信息脱敏与退出码

远程成功结果在 stdout 输出一个确定性的 JSON 值。进度、批准代码、帮助和机器可读错误都输出到 stderr。对象键由 JSON 编码器排序,后端列表顺序保留。脱敏只保留允许列出的诊断字段、标准 UUID、时间戳、类型化枚举、已知消息代理范围名、已验证游标、数字和布尔值。未知字段省略并计入 _redacted_fields;任意字符串替换为 [REDACTED],包括地址和显示名称。不会暴露服务商令牌、密码、正文、邮件头、载荷或自由格式错误子树。现有本地紧急恢复和公开 API 命令仍保持各自文档中的输出行为。

监控保留编译进来的来源和指标 ID、计数单位、严重级别、Schema 版本及有边界的覆盖原因,让 AI 客户端无需自由文本标签也能识别各观察。新的或无法识别的标识符仍保守拒绝;后端新增监控指标时应更新 CLI。标题、备注、邮箱地址和服务商错误文本仍会脱敏。

错误保留安全的服务器 code、HTTP status 和 request_id,但绝不保留原始后端消息、响应体、响应头或传输错误。速率限制可以附带数字 retry_after_seconds。

退出码含义
0命令或帮助成功
2参数无效、输入格式错误、服务器选择无效,或收到 HTTP 400 拒绝
3权限、MFA、新近身份验证或设备批准被拒绝
4需要登录、会话过期或被拒绝,或设备身份无效
5传输、服务、不安全存储或响应无效导致失败
6被限流
7操作取消
8缺少写入确认、缺少重放风险确认,或目录操作不可用

共享身份验证参考仍是后端约定。这些 CLI 扩展不会改变管理权限位或组织 API 权限范围。

运维命令

命令作用
status打印实例状态、平台管理员、健康检查及下一步命令
setup-link打印新的单次链接,认领没有账户的实例
user create创建账户、组织和试用,可选授予平台管理员
user list列出账户,并确认是否仍有平台管理员
user reset-password打印一次性重置链接,或从 stdin 设置密码
user grant-admin向账户授予平台管理员角色
user revoke-admin撤销账户的平台管理权限
user disable-2fa清除账户验证器登记
hash-password打印用于无人值守配置的 argon2 哈希
fleet join-token签发机器加入节点集群所需令牌
fleet list列出每个工作节点和消费者的角色、版本、存活与用量
fleet version显示或设置所有节点应运行的版本
fleet channel跟随 stable 或 dev 发布,或固定节点集群
fleet pin将单个节点固定在某版本,用于金丝雀或暂缓升级
fleet remove忘记节点,其邮箱会自行重新分配
backup将整个实例写入一个可恢复备份包
restore将备份包恢复到实例,替换全部现有内容
org list列出实例工作区及 ID、所有者和规模
org export将完整工作区写入可迁移归档文件
org import将归档应用到本实例工作区

warmblyctl --help 列出命令,warmblyctl <command> --help 打印某命令参数和示例。API 命令使用不同身份验证,因此放在后面单独介绍。

status

docker compose -p warmbly exec backend warmblyctl status
参数默认值作用
--json关闭以 JSON 而非文字输出状态,且始终以 0 退出
--quiet关闭只打印检查,不显示实例摘要;与 --json 同用时忽略

按顺序打印四部分:实例状态、平台管理员、如何进入和检查。示例输出见首次运行。

如何进入部分随状态变化。未认领实例会提示打印设置链接,已认领实例提供恢复命令,没有平台管理员的实例则提示提升某个账户权限。

检查部分与管理面板“设置与健康状态”显示相同发现,各项说明见实例健康状态。

Compose 安装中的 make doctor 就是 warmblyctl status。

退出状态与 JSON

status 在任意检查为错误级别时以非零状态退出,因此 make doctor 可以作为部署脚本最后一行。

--json 是例外,它始终以 0 退出,因为 make claim 用它判断后端是否有响应。应改为读取 .summary.error 获取结论。

键内容
accounts、claimed、setup_required账户数量,以及是否还能签发设置链接
admin_count、admins平台管理员数量及具体账户
registration、registration_source解析后的注册模式,以及来自显式设置还是默认值
mail_transport、mail_delivers、mail_transport_source传输方式、是否实际发信,以及配置来源
app_url、app_url_source所有打印链接的基础 URL
next_steps与文字版如何进入部分相同的命令
checks、summary发现项,以及错误、警告和说明数量

这些键构成稳定约定,只会追加,不会重命名或删除。

docker compose -p warmbly exec backend warmblyctl setup-link

打印用于认领未认领实例的一次性链接。24 小时后过期,替换所有尚有效的旧链接;系统只存储哈希,因此只有这次会打印完整链接。

实例已有账户时,命令有意拒绝:不能在没有现有账户授权的情况下签发第二个所有者。遇到这种拒绝,应改用 user create。

这是唯一无法降级的命令,因为令牌保存在 Redis。Redis 不可用时,请直接创建所有者。

Compose 安装中的 make claim 封装此命令,并会先查找日志中的现有链接,再生成新链接。

user create

docker compose -p warmbly exec backend warmblyctl user create --email [email protected] --admin
参数默认值作用
--email必填待创建账户地址
--admin关闭授予全部平台管理权限,与 --role super 相同
--org<name>'s Organization新组织名称
--no-org关闭只创建账户,不创建组织或试用
--password-stdin关闭从 stdin 读取密码,不交互提示

命令创建账户、组织和免费试用,带 --admin 时授予全部平台管理权限位。如果此人准备接受已有工作区邀请,可使用 --no-org。

地址已存在时会报错,并提示使用 reset-password。

密码就是你输入的内容

没有自动生成或默认密码。在终端中,命令会关闭回显并提示输入两次:

Password for [email protected]:
Repeat password for [email protected]:
Created account [email protected] (id ...), organization "Your Organization" (id ...),
a free trial, every platform admin permission (mask 4194303).

Next
  Sign in at http://localhost:5173 with the password you just set.

密码必须为 8 至 128 个字符,与控制台规则相同,因此脚本路径不会弱于交互路径。

脚本中应通过管道输入,避免进入 shell 历史或 ps 输出:

printf '%s' "$PASSWORD" | docker compose -p warmbly exec -T backend \
  warmblyctl user create --email [email protected] --admin --password-stdin

创建后登录

打开命令打印的 URL,即 APP_URL,使用该地址和密码登录。默认 Compose 服务栈为 http://localhost:5173。

标准自托管实例没有其他阻碍:

  • 没有邮件登录验证码。 自托管的 AUTH_LOGIN_CODE 默认关闭;无论设置如何,不实际投递的传输都不会阻挡登录。参见登录验证码。
  • 没有邮箱确认。 自托管的 REQUIRE_EMAIL_VERIFICATION 默认关闭,账户立即可用。
  • 没有验证码,除非设置了 TURNSTILE_SECRET。
  • 无需邀请,也不依赖注册模式。invite_only 和 true 控制的是注册表单,此命令不使用它。

控制台和管理面板是不同应用。--admin 开放的是 ADMIN_URL 上的面板,默认端口为 5174,不是 APP_URL。

user list

docker compose -p warmbly exec backend warmblyctl user list --admin
参数默认值作用
--admin关闭只列拥有平台管理权限的账户
--limit50打印账户数量,范围 1 至 100

打印地址、姓名、管理角色和创建日期,最早账户在前。--admin 专门回答是否还有管理员账户;没有时会打印两个修复命令。

user reset-password

docker compose -p warmbly exec backend warmblyctl user reset-password --email [email protected]
参数默认值作用
--email必填要重置的账户地址
--password-stdin关闭从 stdin 读取新密码并立即设置
--ttl1h打印链接的有效期,最长 24 小时

不带 --password-stdin 时,命令只打印一次性重置 URL,暂不更改任何内容。在浏览器打开并设置新密码,可避免密码进入 shell 历史、终端回滚记录和进程列表。它通过与产品邮件重置链接完全相同的路径兑换,打开后会撤销账户所有现有会话。

--ttl 最长 24 小时,因为重置链接是账户的持有者凭据。

自动化路径直接设置密码,并以相同方式撤销所有现有会话:

printf '%s' "$PASSWORD" | docker compose -p warmbly exec -T backend \
  warmblyctl user reset-password --email [email protected] --password-stdin

Redis 不可用时应使用这种方式,因为没有 Redis 无法生成链接。

user grant-admin

docker compose -p warmbly exec backend warmblyctl user grant-admin --email [email protected] --role super
参数默认值作用
--email必填要提升权限的账户地址
--role必填super、support、ops、analyst 之一
角色权限
super全部平台管理权限,可打开所有管理页面
support用户、营销活动、组织、预热池、预热封禁、申诉、企业咨询和审计日志
ops工作节点及其管理、速率限制、分析、组织和审计日志
analyst只读:用户、营销活动、组织、分析和审计日志

角色会替换现有权限掩码,不是追加;重复授予已有角色会报告没有变化。权限从会话读取,因此账户必须退出再登录,面板才体现新授权。

账户必须已存在。Compose 安装可用 make grant-admin EMAIL=... ROLE=... 封装调用。管理面板现在也在账户 > 管理员提供相同功能,可按预设或逐权限位设置,因此 CLI 不再是添加第二位管理员的唯一方式。

user revoke-admin

docker compose -p warmbly exec backend warmblyctl user revoke-admin --email [email protected]
参数默认值作用
--email必填要撤销管理员权限的账户邮箱地址
--force关闭允许移除最后一位平台管理员

移除全部平台管理员权限,保留账户的其他信息不变。

如果要撤销的是最后一位管理员,必须传入 --force,否则命令会拒绝执行。这是因为操作后所有人都将无法进入管理面板,产品内也没有任何方式可以重新授予访问权限。请先为其他人授予管理员权限。make revoke-admin EMAIL=... 是此命令的封装。

user disable-2fa

docker compose -p warmbly exec backend warmblyctl user disable-2fa --email [email protected]

清除身份验证器注册信息和恢复代码,避免手机丢失后还必须重置密码。原密码仍然有效,账户登录后可以在账户安全设置中注册新的身份验证器。

hash-password

warmblyctl hash-password
printf '%s' 'your password' | warmblyctl hash-password

为 WARMBLY_BOOTSTRAP_PASSWORD_HASH 生成并输出 argon2 哈希,用于在没有账户的实例首次启动前预先创建所有者。参阅无人值守配置。

连接了终端时,命令会提示输入;未连接终端时,则从管道读取。因此,同一条命令既能手动运行,也能用于实例配置脚本。stdout 只输出哈希,方便捕获或通过管道传递;说明文字输出到 stderr。这是唯一不需要数据库的命令。

用引号包裹哈希

argon2 PHC 字符串包含 $ 字符。Docker Compose 会将它们解释为插值语法,因此直接将未加引号的哈希写入 .env 会导致部分内容被悄悄移除。请在该文件中用单引号包裹哈希;若直接粘贴到 Compose 配置中,则需将每个 $ 写成两个,再放入 docker-compose.yml。

backup

docker compose -p warmbly exec backend warmblyctl backup --out /data/blobs/warmbly.tar.gz
docker compose -p warmbly cp backend:/data/blobs/warmbly.tar.gz ./warmbly.tar.gz \
  && docker compose -p warmbly exec -T backend rm -f /data/blobs/warmbly.tar.gz

/data/blobs 是容器和宿主机都能访问的路径,因此用它交接备份文件。请将备份包移出后删除原文件:backup 会从归档中排除本次生成的输出文件,但此前遗留的备份包会被收入下一次备份。删除操作通过 && 串联,确保复制失败时不会连唯一的副本也一起删除。

命令会生成一个备份包,其中包含必须一起恢复的三部分:数据库的 pg_dump、Blob 根目录(邮件正文、附件、头像),以及加密密钥。

将这三部分组合起来,正是提供此命令而不只在文档中列出操作步骤的原因。如果只恢复数据库转储,实例中的所有邮箱凭据都将无法解密,因为密文保存在数据库中,而解密密钥在 .env 中。如果只恢复 Blob 根目录,得到的只是没有任何记录引用的邮件正文。

参数作用
--out备份包的写入位置。默认为工作目录下的 warmbly-backup-<timestamp>.tar.gz
--no-keys不包含加密密钥。这样生成的备份包无法独立用于恢复
--no-blobs仅备份数据库
--force覆盖现有的输出文件

备份包以 0600 权限写入,包含实例中的所有邮箱凭据及其解密密钥。请像保护实例本身一样保护此文件:加密存储、存放在宿主机之外,并避免放入共享网盘。

只有使用文件系统存储提供程序时,Blob 才会随备份包一起迁移。如果实例将 Blob 存储在 S3 中,它们会留在存储桶内;备份包会明确说明这一点,而不会将自身描述为完整备份。

设置定时备份

install.sh --wizard 会生成 backup.sh 和一个 systemd 定时器,执行的正是上述备份操作,保留最近 N 个备份包,并且可以将每个备份包复制到宿主机之外。

restore

docker compose -p warmbly exec backend warmblyctl restore --file /data/blobs/warmbly.tar.gz
docker compose -p warmbly restart

清空数据库模式后,将备份包中的数据恢复进去。因此,此操作会替换实例现有的全部组织、用户、营销活动和邮箱。执行前会列出备份包的内容,并要求输入 restore 确认;脚本可使用 --yes 跳过此步骤。

写入任何数据之前,命令会将备份包中的 CREDENTIALS_ENCRYPTION_KEY 和 KMS_LOCAL_MASTER_KEY 与当前宿主机的值进行比较。如果不一致,命令会拒绝继续,并输出需要先写入 .env 的两行配置。这项检查是此命令的关键价值:没有它,恢复看似成功,所有邮箱却会在几天后认证失败,而且错误信息不会指出真正原因。

参数作用
--file要恢复的备份包。必填
--yes跳过输入文字确认的步骤
--no-blobs仅恢复数据库,不修改 Blob 根目录
--force即使密钥不一致也继续恢复,接受全部已存储邮箱凭据失效的后果

Blob 会以叠加方式解压到 Blob 根目录:覆盖备份包中包含的文件,其他文件保持不变。

转储加载完成后,恢复操作会运行 ANALYZE,因为转储不包含查询规划器的统计信息。如果此步骤失败,恢复仍会完成,并输出警告;之后请自行运行 ANALYZE,执行位置为 psql。

数据管理介绍了迁移到新宿主机的完整流程。

org list

docker compose -p warmbly exec backend warmblyctl org list

列出所有工作区及其 ID、名称、所有者邮箱,以及成员、邮箱和联系人数量。此命令为接下来的两个命令提供工作区标识:--org 接受 ID、slug 或所有者邮箱,可根据手边的信息任选一种。

org export

docker compose -p warmbly exec backend warmblyctl org export \
  --org [email protected] --out /tmp/workspace.warmbly.zip

将一个工作区写入单个归档文件,包括组织、成员及其角色、邮箱、营销活动、邮件序列、联系人、禁止发送数据、CRM、收件箱历史,以及发送状态。保留发送状态可以防止迁移后的邮箱在迁移当天发送两倍于每日限额的邮件。此归档与控制台的设置 > 数据生成的归档相同,因此迁移的任意一端都可以使用任一工具。管理面板现在也在实例 > 迁移中为任意组织提供相同操作。

参数作用
--org工作区的 ID、slug 或所有者邮箱。必填
--out归档的写入位置。- 表示写入 stdout,因此可以直接通过管道传给 ssh 或存储桶。必填
--groups要包含的数据组,以逗号分隔。省略时包含全部数据组
--with-credentials使用口令加密邮箱及集成凭据后,将其封装到归档中
--passphrase-stdin从 stdin 读取口令,而不提示输入两次

warmblyctl org export --help 会列出数据组。core 始终包含在内;inbox、sending、events 和 logs 会持续增长且没有上限,因此去掉这些组可以缩小归档体积,同时仍能重建可正常使用的工作区。

包含凭据的归档是本产品生成的最敏感文件

归档包含工作区中每个邮箱的密码和刷新令牌,仅由你输入的口令保护。Warmbly 不会在任何地方保存该口令,因此遗失后只能重新导出。任何同时持有归档文件和口令的人,都可以通过这些邮箱发送邮件。

不使用 --with-credentials 时,归档中的凭据字段为空,迁入目标实例后,每个邮箱都需要重新连接。无论是否包含凭据,其他数据都会迁移。

进度输出到 stderr,归档写入指定文件,因此 --out - 可以输出不夹杂其他内容的数据流:

warmblyctl org export --org [email protected] --out - | ssh newhost 'cat > /tmp/workspace.zip'

org import

docker compose -p warmbly exec backend warmblyctl org import \
  --org [email protected] --file /tmp/workspace.warmbly.zip --dry-run

将归档导入此实例中的一个工作区。写入任何数据前,命令始终会列出归档内容、目标中已存在的记录,以及尚无账户的成员。

参数作用
--org目标工作区的 ID、slug 或所有者邮箱。必填
--file要读取的归档。必填
--groups要导入的数据组,以逗号分隔。省略时导入归档中的全部数据组
--overwrite替换目标中已存在的记录,而不是保留原记录
--with-credentials提示输入导出时的口令,以便导入凭据
--passphrase-stdin从 stdin 读取该口令
--dry-run只输出报告,不写入任何数据

请先使用 --dry-run 运行一次。报告与控制台中的预检相同,不会产生任何代价。管理面板的实例 > 迁移也提供相同的导入操作,其中包括预检。

整个导入在单个事务中执行:任何部分失败,都不会写入任何数据,工作区保持不变。成员通过邮箱地址与此实例上的账户匹配;对于没有匹配账户的成员,其记录会重新归属工作区所有者,报告会在提交之前明确列出这些成员。归档不携带账户密码材料,因此无法在目标实例上创建账户。

org export 和 org import 是工作区级工具,backup 是实例级工具。它们不能互换:实例备份包无法应用到单个工作区,而工作区归档也无法恢复整个实例。

账单历史、套餐覆盖设置、工作节点分配、邮箱同步检查点和预热池成员资格会导出留作记录,但不会导入应用,因为这些信息都属于实例,而不属于工作区。导出和导入中提供了完整对照表。

API 命令

本节的所有命令都通过 API 密钥调用公共 REST API,因此可以在任何位置运行:笔记本电脑、CI 任务或智能体的沙箱。它们不需要数据库访问权限,既适用于托管服务,也适用于自托管实例。

export WARMBLY_API_KEY=wmbly_...                       # Settings > API keys
export WARMBLY_API_URL=https://api.your-instance.com  # omit for the hosted service

warmblyctl me                 # who the key is, and its scopes
warmblyctl campaign list
warmblyctl campaign start --id <uuid>

每条命令都会将 API 的 JSON 响应原样输出到 stdout。失败时以状态码 1 退出,并将 API 自身的 code 和 request_id 输出到 stderr。密钥能执行的操作严格受已授予的权限限制;warmblyctl me 可显示这些权限。

命令类别

命令族涵盖的操作
me身份和已授予的权限范围
campaign列表、获取、创建、更新、删除、邮件序列步骤、发件邮箱池、预检、启动、停止、测试邮件、发送日志
contact搜索、获取、按邮箱地址查找、创建、更新、删除、备注、时间线、导入、导出、自定义字段
mailbox列表、获取、更新、断开连接、认证检查、同步状态、发送行为、验证、发送,以及预热的启动、暂停、恢复、停止和状态查询
inbox列表、计数、邮件会话、标记已读、回复、撰写、智能体草稿、定时发送
analytics概览、邮件送达能力、预热、账户、营销活动、用量、审计日志
settings客户开发和禁止发送设置
webhook端点、密钥、投递记录、事件类型
apikey密钥自助管理:列表、创建、更新、撤销、权限范围目录
template回复模板
crm销售管道、交易、任务

warmblyctl <family> --help 列出某个命令族的子命令,warmblyctl <family> <subcommand> --help 则显示其参数及调用的端点。

原样透传

对于 API 已支持但尚无类型化命令的操作,只需调用一次 api 即可执行,因此所有 API 功能都能访问:

warmblyctl api get "/campaigns?limit=10"
warmblyctl api post /contacts --data '{"email":"[email protected]"}'
warmblyctl api patch "/campaigns/<id>" --data @changes.json
warmblyctl api delete "/webhooks/<id>"

路径相对于 /v1。--data 接受 JSON 字面量、表示 stdin 的 -,或 @file。

约定

  • 列表响应采用 {"data": [...], "pagination": {"next_cursor", "has_more"}} 格式。通过 --cursor 翻页,直到 has_more 为 false;游标应作为不透明值使用。
  • 写操作接受 --idempotency-key。以相同密钥重试命令,绝不会重复执行该操作。详情参阅身份验证。
  • 429 错误会给出 Retry-After;请等待指定时间后再重试。
  • contact list 执行的是搜索:--data 携带筛选请求体,省略时列出全部联系人。

六条命令会实际发送邮件

campaign start、campaign test-email、mailbox send、inbox reply、inbox compose 和 inbox approve-draft 会发送邮件。其他操作只读取数据或编辑草稿。请先运行 campaign preflight,再运行 campaign start;预检免费,可以发现缺少发件邮箱、受众为空和跟踪配置损坏等问题。

供 AI 智能体使用

仓库在 skills/ 下提供了技能:warmbly-cli 面向 warmbly CLI,warmbly-api 通过 warmblyctl 提供相同的产品操作能力,warmbly-ops 用于实例管理,warmbly-install 用于部署和迁移实例。这些技能会教编码智能体使用相关命令、上述约定及发送安全规则。请按照智能体所要求的方式安装,例如 Claude Code 可使用 cp -r skills/warmbly-api ~/.claude/skills/,也可以直接让智能体阅读 SKILL.md。获得限定权限的密钥及这些技能后,智能体无需接触数据库即可完成工作区的端到端操作。

Redis 不可用时

账户恢复不应依赖缓存,因此多数命令会将 Redis 无法访问视为警告,并继续执行。

命令Redis 不可用时的行为
setup-link失败。令牌保存在 Redis 中,因此无处写入
user reset-password 不带 --password-stdin失败。链接绑定到 Redis 中的 nonce。提示信息会引导你使用带 --password-stdin 的形式
user reset-password --password-stdin正常工作。密码会更改,但无法撤销现有会话
user create正常工作。新账户不会预加载到缓存中,这不会造成问题
status正常工作,并在检查结果中报告 redis_unreachable
org export 和 org import正常工作。解密后的密钥不会缓存,因此每个工作区会额外进行一次 KMS 往返调用
其他所有命令正常工作

Make 目标

以下每个目标都会在 Compose 部署的后端容器中运行 warmblyctl。

目标执行的操作
make claim查找或输出首次运行的实例认领链接;如果实例已被认领,则说明下一步应如何操作
make doctor运行 warmblyctl status;任何错误级别的检查失败时,返回非零状态码
make cli ARGS="..."执行任意命令,并连接 TTY,使交互式提示可以工作
make grant-admin EMAIL=... ROLE=...user grant-admin
make revoke-admin EMAIL=...user revoke-admin

另请参阅

fleet

管理运行 Warmbly 的机器。你所拥有机器上的每个进程都是一个节点:worker 发送和同步邮件,consumer 处理事件。两者都通过令牌注册、发送心跳,并拉取控制平面要求运行的版本。

添加一台机器只需两条命令:

warmblyctl fleet join-token

然后在该机器上运行:

curl -fsSL https://<your-instance>/join.sh | sh -s -- \
  --url https://<your-instance> --token <token> --role worker

令牌只显示一次。过期之前,它可以用于加入任意数量的机器:默认有效期为七天,也可以通过 --ttl 设置,最长为 720h。签发新令牌会撤销上一个令牌,但已经加入的机器不受影响。使用过期令牌的机器会被拒绝,因此请签发新令牌。

warmblyctl fleet list
ROLE      NAME      STATE  VERSION           REGION      MEM    SEEN     ID
worker    box-1     live   v1.4.2            eu-central  128MB  12s ago  de434ce4-...
consumer  events-1  live   v1.0.0 -> v1.4.2  -           96MB   30s ago  2b334317-...

STATE 为 live、unreachable(已注册,但没有发送心跳)或 stopped。版本显示为 a -> b 表示尚未切换到目标版本。--role worker 可缩小列表范围;--json 输出便于程序读取的格式。

更改节点集群运行的版本:

warmblyctl fleet version              # what the fleet should be on
warmblyctl fleet version v1.4.2       # move everything to a tag
warmblyctl fleet channel stable       # follow releases again
warmblyctl fleet pin <node-id> v1.4.1 # hold or canary one machine
warmblyctl fleet pin <node-id>        # clear that pin

设置版本标签还会固定更新通道,因此之后发布的新版本不会覆盖你主动执行的回滚。

warmblyctl fleet remove <node-id>

移除节点记录。此前分配给该节点的邮箱会在几分钟内重新分配。此操作不会停止进程:仍在运行的机器会在下一次心跳时重新加入,因此还需在该机器上停止服务。

本页目录