安装
通过一条命令,使用已发布的版本镜像安装 Warmbly;安装向导会询问数据存放位置、保留内容和期限,以及备份方式。
curl -fsSL https://warmbly.com/install.sh | sh无需克隆仓库、编译器或工具链。脚本检查 Docker,创建一个目录,生成所有秘密值,拉取固定版本的发布镜像,启动服务栈,并打印用于认领实例的链接。在已安装 Docker 的干净机器上,两分钟内即可完成。
加上 --wizard,安装向导就会询问那些配置参考无法替你决定的问题。
curl -fsSL https://warmbly.com/install.sh | sh -s -- --wizard不必记住这个参数。首次安装时,如果有可用终端且未指定主机名,脚本会在写入任何内容前询问是否进行配置。否则实例只能使用 localhost:控制台、跟踪主机和退订地址都只能在该机器上访问,却会出现在营销活动邮件中。选择否时,脚本会明确说明这一点并继续。--assume-yes、--host、WARMBLY_HOST,以及对已有安装重新运行,都会跳过此问题,因为这些情况已经给出了答案。
安装哪些组件
| 安装位置 | /opt/warmbly,或 --dir 指定的位置 |
| 安装内容 | docker-compose.yml、权限为 0600 的 .env、keys-backup.txt 和数据根目录 |
| 版本 | 解析一次最新发布,并固定在 .env 中,绝不使用 latest |
| 镜像 | ghcr.io/warmbly/warmbly/*,支持 amd64 和 arm64 多架构。镜像公开,无需登录镜像仓库或拥有 GitHub 账户 |
| 服务 | 后端、消费者、工作节点、控制台、管理端、Postgres、Redis、NATS;除非选择仅核心组件,还包括跟踪、实时服务和表单 |
机器上不会进行任何构建。克隆并构建的路径仍然存在且受支持;需要时参见自托管。
运行前的准备
对把脚本直接通过管道交给 shell 执行有所顾虑是合理的。脚本只有具备版本管理、可检查和校验和,才有依据回应这一顾虑。这里三者都具备:
curl -fsSLO https://warmbly.com/install.sh
curl -fsSLO https://warmbly.com/install.sh.sha256
sha256sum -c install.sh.sha256
less install.sh
sh install.sh脚本本身也按这一要求编写:使用 set -eu,所有代码行都放在函数内,最后一行才执行 main "$@",因此下载中途截断时不会执行任何操作。它不会要求你把脚本通过管道交给 sudo;只在确实需要提权的具体步骤请求权限,并说明步骤名称。
无需真正运行安装,也能查看它准备做什么:
sh install.sh --dry-run这会打印准备写入的完整 .env 和 Compose 文件,不修改任何内容。
运行完成后
每个发布都提供 images.json 资源,列出其中每个镜像的清单摘要。拉取完成后,安装器会将机器实际获得的镜像与该文件比较;不一致时,在启动任何服务前停止。你也可以随时自行检查:
docker image inspect ghcr.io/warmbly/warmbly/backend:v1.4.2 --format '{{index .RepoDigests 0}}'通过 --registry 指向自有镜像仓库的安装,没有可用于比较的清单;安装器会明确说明,不会假装已经验证。
环境要求
| 要求 | 说明 |
|---|---|
| Linux 或 macOS,amd64 或 arm64 | Windows 上请在 WSL 内运行 |
| Docker 20.10+ 和 Compose v2 | Linux 上脚本可协助安装 Docker,但绝不会悄悄安装 |
| 约 2 GB 磁盘空间 | 用于镜像,不是构建缓存;本流程不构建 |
| 4 GB 内存 | 整个服务栈空闲时约占 300 MB |
| 空闲端口 | 8080、5173、5174;完整组件还需 3000、4000、8090 |
最容易冲突的是端口 3000,许多自托管软件都默认使用它。安装器会在写入任何内容前检查所有端口,并告诉你哪个已被占用。
安装向导
--wizard 包含九个步骤。每个答案都有与快速安装一致的默认值,因此一路按回车,会得到与单行命令完全相同的安装。
菜单支持常用按键:
| 按键 | 操作 |
|---|---|
↑ ↓、j k、ctrl-n ctrl-p | 移动 |
ctrl-d ctrl-u | 移动半屏 |
gg、G | 跳到首项、末项 |
1 至 9 | 直接跳到对应选项 |
enter、space、l | 选择 |
q、esc、ctrl-c | 退出,不安装 |
最后的确认页面按 enter 安装,按 e 返回某个部分,按 q 退出。
各步骤会追加显示,而不是相互覆盖,因此整个过程可滚动回看,也不会影响安装器上方的终端内容。如果更喜欢每一步重绘屏幕,可使用 --clear。
安装位置
一个目录保存 Compose 文件、.env,以及实例写入的全部存储,除非在第三步将数据移到其他位置。默认目录为 /opt/warmbly。
访问方式
先确定所有链接使用的主机名,包括控制台 URL、营销活动邮件中的退订链接和跟踪像素,再选择提供服务的方式:
| 选项 | 行为 |
|---|---|
| 普通 HTTP | 原样发布端口,适用于 localhost 或私有网络 |
| 内置 Caddy | 添加自动 HTTPS 的 Caddy 服务,每个界面使用一个主机名,其他服务全部绑定到回环地址 |
| 使用自己的代理 | 将所有服务绑定到 127.0.0.1,并设置 TRUSTED_PROXIES;手动配置代理部署时,这个设置很容易在没有明显提示的情况下出错 |
TRUSTED_PROXIES 比看起来更重要。没有它,每个请求都像来自代理,按 IP 速率限制和点击去重就无法区分不同用户。
内置 Caddy 选项会在给定域名下提供六个主机名,安装结束时打印 DNS 记录:
app.example.com admin.example.com api.example.com
ws.example.com track.example.com forms.example.com它也能为尚不存在的主机名获取证书。实例中的工作区可能在安装几个月后,才将自己的跟踪域名或表单域名指向这里,而站点块无法预先列出尚未出现的主机。生成的 Caddyfile 包含 on_demand_tls,其 ask 指向后端,因此第一次请求此类名称时,Caddy 会在后端确认该实例已验证此域名后获取证书。没有任何工作区验证过的名称会被拒绝,仅把域名指向机器不足以从它获取证书。
使用自有代理时,需要自行安排:相同的自定义域名也需要证书,具体方式取决于代理。Caddy 可以使用相同的 on_demand_tls 块;nginx 和 Traefik 需要手动添加每个域名。根域名重定向也属于自定义域名:代理应将裸域名及其 www 请求转发到跟踪端口,并保留原始 Host 请求头。配置正确前,“发送域名”页面会显示访问者无法到达,并给出 Traefik、nginx、Caddy、Coolify 或 Dokploy 的修改方法。已连接 Warmbly Cloud 的实例也可改由 Cloud 提供重定向。
数据的存储位置
这是其他安装步骤无法替你决定的问题,也是向导存在的原因。
| 选项 | 结果 |
|---|---|
| 安装目录下 | /opt/warmbly/data 保存 Postgres、Blob、NATS 和工作节点状态 |
| 自选路径 | 使用外置磁盘或已有快照机制的挂载点 |
| Docker 命名卷 | 由 Docker 管理,只能通过 docker volume 迁移 |
默认使用真实路径,因为这样备份和迁移最直观:停止服务栈后,通过 rsync 将该路径及 .env 一起复制到另一主机,即可迁移实例。Postgres 运行时不要直接复制,运行中的数据目录不是一致快照。要不停机迁移,请使用 warmblyctl backup。Compose 将以 / 开头的卷源解释为绑定挂载,其他内容解释为命名卷,因此每个存储只需一个变量,无需第二份 Compose 文件。
随后通过一个提示配置外部 Postgres 或 Redis;自带数据库时,对应内置容器会完全移除。还会询问邮件正文、附件和头像的存放位置。
文件系统 Blob 仅属于一台机器
运行在另一台主机上的工作节点会写入自己的磁盘。扩展到多主机工作节点后,Blob 存储必须兼容 S3,否则正文会缺失。向导提供 MinIO、R2、B2 和 AWS 选项。
密钥与机密信息
系统生成七个秘密值:AUTH_SECRET、INTERNAL_API_TOKEN、NODE_BROKER_TOKEN、UPDATER_TOKEN、SECRET_KEY_BASE、KMS_LOCAL_MASTER_KEY 和 CREDENTIALS_ENCRYPTION_KEY。跟踪及表单服务使用 INTERNAL_API_TOKEN,工作节点和消费者使用 NODE_BROKER_TOKEN,后端访问更新器使用 UPDATER_TOKEN,三者不能互相打开对方保护的入口。重新运行会沿用 .env 中已有值,并补上缺失的 UPDATER_TOKEN;但绝不会为已有安装添加 NODE_BROKER_TOKEN,因为已加入的节点只持有共享令牌。文件以 0600 权限写入,APP_ENV 设置为 prod,这条安装路径不会使用任何公开默认秘密值。
最后两个值无法找回。所有邮箱凭据和按组织密钥都由它们密封,因此数据库备份若不附带它们,恢复后的实例将无法用任何有效凭据登录邮箱。向导会打印这两个值,将其写入安装目录旁的 keys-backup.txt,并等待你输入确认,说明已在别处保存副本。
保留哪些数据,以及保留多久
设置分为两组,之后都可以在实例 > 配置 > 设置中修改:
| 设置 | 默认值 | 控制范围 |
|---|---|---|
| 导入时间窗口 | 90 天 | 连接邮箱后,首次导入向前追溯多久 |
| 导入上限 | 5,000 | 每个邮箱首次导入最多存储多少封邮件 |
| 每邮箱每日限额 | 2,000 | 单个邮箱每个 UTC 日可存储的新邮件数 |
| 每组织每日限额 | 25,000 | 一个工作区每个 UTC 日的新邮件与导入邮件总数 |
| 打开和点击 | 365 天 | 逐事件日志,包含客户端、设备和大致位置 |
| 表单漏斗事件 | 180 天 | 浏览、开始填写和字段级流失 |
| 审计日志 | 90 天 | 谁从哪个 IP 和用户代理做了什么 |
| 邮箱中的预热邮件 | 30 天 | 超过期限后从每个邮箱删除预热邮件,不论放在哪个文件夹 |
| 预热记录 | 365 天 | 逐邮件预热令牌、回执、篡改事件和垃圾邮件报告 |
打开、表单和审计窗口也决定对应个人数据保留多久。“最少保留”预设将这三项设为 30 天,预热邮件设为 7 天,预热记录设为 30 天。
备份
配置定时 warmblyctl backup,包括目标目录、频率、保留份数,以及是否在备份包中包含加密密钥。它会在安装目录旁写入 backup.sh;systemd 主机上还会创建服务和定时器。
包含密钥后,备份包与实例本身同样敏感。不包含密钥时,必须在其他位置保存 keys-backup.txt,否则备份包根本无法恢复。
允许谁访问
创建首位所有者,可以打印认领链接,也可以通过 WARMBLY_BOOTSTRAP_EMAIL 和 warmblyctl hash-password 生成的哈希无人值守完成。然后选择注册策略和平台邮件:配置 SMTP 中继,或跳过并让密码重置及登录验证码打印到后端日志。
安装范围
可以安装全部组件,或仅核心组件,不含跟踪像素、WebSocket 和托管表单。仅核心模式会将 TRACKING_DOMAIN 和 FORMS_DOMAIN 留空,因为填写一个没有服务响应的主机,比不填写更糟:营销活动邮件会带上指向无法解析名称的链接和退订地址。发送仍可正常进行,只是没有打开及点击跟踪;所有退订链接都由 API_PUBLIC_URL 提供。
写入前会检查端口冲突。最后是更新检查,它向 GitHub Releases API 发起一次出站请求,也是实例自主发起的唯一出站调用。Warmbly 不包含遥测。
核对配置
一个页面显示所有答案。按回车安装,e 返回任意部分,q 退出且不写入任何内容。
先预览安装过程
curl -fsSL https://warmbly.com/install.sh | sh -s -- --demo展示真实向导和确认页面,但拉取、容器创建和健康等待只是演示,不实际执行。它不写文件、不拉镜像、不创建容器,也不需要 Docker,因此无需准备服务器,就能了解“实际会做什么”。
演示约半分钟,速度经过安排,方便阅读每个阶段;真实安装需要几分钟,主要花在拉取镜像和首次启动迁移。WARMBLY_DEMO_FAST=1 可缩短到十秒内。在源码检出目录中可运行 make installer-demo。
供智能体使用
智能体不应操作向导,因为它读取原始按键并需要终端。每个答案都有对应参数,因此使用 --yes 配合参数,可以安静、确定且幂等地执行;--print-env 和 --dry-run 可展示运行结果,而不真正生成它们。
仓库提供 warmbly-install 技能,位于 skills/ 下,专门覆盖这套流程,以及备份和实例迁移。按智能体要求安装即可,例如 cp -r skills/warmbly-install ~/.claude/skills/。
无人值守安装
每个答案都有对应参数和 WARMBLY_* 环境变量,因此同一脚本也可用于 Ansible 或 cloud-init 配置:
curl -fsSL https://warmbly.com/install.sh | sh -s -- --yes \
--dir /opt/warmbly \
--host warmbly.acme.com \
--tls caddy \
--data-root /mnt/data/warmbly \
--blobs s3 \
--retention-preset minimal \
--backup-dir /mnt/backups/warmbly \
--version v1.4.2| 参数 | 变量 | 默认值 |
|---|---|---|
--dir | WARMBLY_DIR | /opt/warmbly |
--host | WARMBLY_HOST | localhost |
--tls | WARMBLY_TLS | none(caddy、proxy) |
--data-root | WARMBLY_DATA_ROOT | <dir>/data(或 volumes) |
--blobs | WARMBLY_BLOBS | filesystem(s3) |
--database-url | WARMBLY_DATABASE_URL | 内置 Postgres |
--redis-url | WARMBLY_REDIS_URL | 内置 Redis |
--components | WARMBLY_COMPONENTS | full(core) |
--version | WARMBLY_VERSION | 最新发布 |
--channel | WARMBLY_CHANNEL | stable(dev) |
--registry | WARMBLY_IMAGE_PREFIX | ghcr.io/warmbly/warmbly |
--backup-dir | WARMBLY_BACKUP_DIR | 无 |
--backup-keep | WARMBLY_BACKUP_KEEP | 14 |
--retention-preset | default(minimal) | |
--no-update-check | WARMBLY_UPDATE_CHECK | 开启 |
--print-env 输出 .env 后退出。--dry-run 打印准备写入的所有文件后退出。--demo 演示完整流程,但不安装任何内容。
再次运行安装程序
安装器是幂等的。第二次运行会:
- 沿用现有
.env,绝不重新生成秘密值,因为新的AUTH_SECRET会让所有人退出登录,新的CREDENTIALS_ENCRYPTION_KEY则会造成永久数据丢失。 - 保留现有数据根目录,因为把安装指向新路径不是迁移,而是在有数据的实例旁边另建一个空实例。
- 保留你自行加入
.env的键,并放在末尾明确标注的区块中。 - 重新生成
docker-compose.yml,将旧文件保留为docker-compose.yml.bak。 - 拒绝使用不是由它创建的目录,除非传入
--force。
请将自己的 Compose 改动放在 docker-compose.override.yml 中;Compose 会叠加合并,安装器也不会触碰该文件。
更新
先升级后端,并确认数据库迁移完成,再升级消费者。消费者开始依赖数据库的工作前,最多等待两分钟,确认数据库达到其嵌入代码要求的干净迁移版本。如果后端仍旧或迁移失败,消费者会退出并给出可操作的 Schema 就绪错误,可由容器重启策略再次尝试。消费者不会执行迁移,也不会忽略缺失表。
统一收件箱转发功能的升级顺序
在所有处理提交、排队发送回调和工作节点/控制平面授权的后端升级完成、旧请求处理完毕、旧副本停用之前,不要使用向禁止发送收件人转发的功能。混合后端发布期间,应暂停统一收件箱提交和排队发送派发,因为旧后端仍会拒绝这些转发。现有客户端已经可以提交转发,因此仅安排前端顺序不够。应明确验证旧副本已停用,不要仅凭发布完成或某个后端健康判断。无需工作节点 Schema 变更或重新连接邮箱。参见发送安全。
邮箱同步功能的升级顺序
邮箱查找优化为归一化的发送 Message-ID 和精确 IMAP 文件夹代际添加并发索引,不改写现有记录。索引构建需要临时磁盘空间和 I/O,因此升级繁忙实例前,应确认数据库余量并等待迁移完成。新的批量邮件映射端点无需配置:旧工作节点仍使用逐邮件读取;旧后端不支持批量时,新工作节点也回退到原路径。先升级后端以获得性能收益,再升级工作节点。同步频率、草稿删除核对和持久来信要求保持不变。
要启用持久邮箱来信,应先升级后端并等待迁移完成,再升级并启动消费者,最后升级工作节点。后端在同一个数据库事务中接纳新的服务商映射和组织加密的来信载荷;消费者重启后继续处理待完成阶段。工作节点继续使用内部 HTTP 端点,无需数据库访问。应将实例的组织密钥和正文存储与数据库一起保留,详见数据控制。
旧工作节点配合新后端,仍只写入旧版映射记录。新工作节点配合旧后端,仅在持久内部端点不存在,即 HTTP 404 或 405 时回退。两种混合版本路径都不具备新的持久接纳保证。后端失败或缺少持久标记的确认,不会被视为成功。冲突映射没有保留的待处理载荷时,后端会保守拒绝,不会声称已持久接纳;这也包括待处理行已经投递并移除之后的重试。普通映射查找仍会对该邮件去重。
已完成导入和现有映射都保留。升级前映射没有重放载荷,因此不能仅凭映射重建旧的丢失来信。有界服务商恢复仅覆盖支持的近期历史,不提供无限历史修复。邮箱同步说明了各服务商限制。
降级前,应保持更新后的消费者运行,直到所有待处理来信完成。只要还存在任何待处理行,迁移就会拒绝移除来信表。不要删除队列行或强制迁移版本来绕过拒绝。旧消费者无法处理此队列。待处理来信仅属于当前实例,不随工作区导出迁移。
节点集群自动更新只选择不晚于当前后端的版本。响应心跳时,会再次检查现有集群和节点固定版本,因此不兼容的已存储目标不会发给节点;新设置的不兼容固定版本会被拒绝。新加入机器回退到后端的精确发布版本,而不是不断变化的 prod 标签。主分支构建使用其已发布的提交 SHA 镜像标签。没有版本标记的自定义构建,必须将 WARMBLY_VERSION 设置为其已发布镜像标签;未知版本无法安全排序。将节点集群迁移到新版本前,应先升级后端。这项保护只有安装兼容版本后才生效,无法改变正在运行的旧后端行为。
控制台在容器启动或静态构建时,从后端现有公开配置发现独立的实时服务来源。应先启动后端;如果发现过程警告不可用,在后端就绪后重启控制台,或重新构建静态部署。参见配置。
点击管理面板的版本标记,再选择更新并重启。这种安装方式下,更新器以镜像模式运行:将新标签写入 .env,拉取镜像,并重新创建镜像发生变化的容器,不进行构建。参见更新。
也可以在安装目录手动执行:
docker compose -p warmbly pull && docker compose -p warmbly up -d更新不会立即切断正在进行的工作。生成的服务栈为每个应用容器提供 60 秒停止时间,由 stop_grace_period 控制;期间后端会完成已开始的邮箱连接及正在连接的导入行,工作节点会完成当前发送。此后仍未完成的工作由新容器继续:工作节点在第一次心跳时重新接手原先加载中的邮箱,导入行则在两分钟租约到期后接手。参见更新时不丢失工作。
卸载
sh install.sh --uninstall这会停止并移除容器,所有数据原样保留。添加 --purge-data 还会删除数据根目录和安装目录,操作前要求输入确认短语,因为这也会毁掉唯一能够解开备份的密钥。
出现问题时
| 现象 | 原因 |
|---|---|
Docker is installed but not answering | Docker 守护进程未运行,或用户尚未加入 docker 组。执行 sudo systemctl start docker,然后退出并重新登录 |
Something already listens on 3000 | 其他自托管工具占用了端口。启动前修改 docker-compose.yml 中发布的端口 |
Could not pull the release images | 标签不存在,或主机无法访问 ghcr.io。检查发布列表 |
The registry refused to serve the release images | 镜像在未登录时不可读,而发布镜像本不应如此。请报告问题。如果传入了 --registry,先检查拼写;不存在的命名空间也会得到同样拒绝。可将 --registry 指向你能读取的镜像源,或通过 docker login ghcr.io 登录一个本来就有包访问权限的账户。登录本身不会授予权限 |
| API 始终没有响应 | 首次启动会应用全部迁移。使用 docker compose -p warmbly logs -f backend 查看 |
| 消费者等待后端迁移 | 消费者最多等待两分钟,确认数据库达到镜像要求的干净版本,再验证所需预热表可见。先更新后端,检查迁移日志,并确认两个服务使用相同数据库和搜索路径。dirty 迁移需要调查,不要为了启动消费者而强制修改版本 |
| 没有打印认领链接 | 数据库已有账户,无需再认领。参见首次运行 |
与邮件服务共用一台主机
自托管实例可以连接只存在于其所在机器上的邮箱。Proton Bridge 是典型场景:它在本地登录 Proton Mail,并通过 127.0.0.1:1143 的普通 IMAP 和 127.0.0.1:1025 的 SMTP 重新提供该账户。因为它从不监听网络接口,所以不提供客户端可验证的 TLS。
连接时,在表单中选择无安全模式。Warmbly 仅在主机是回环字面值,即 localhost、127.0.0.0/8 或 ::1,且实例为自托管时提供此模式。允许它的全部理由,就是凭据不会离开机器;工作节点也会检查实际连接的对端,避免后续修改已存储主机扩大范围。托管 Warmbly 的工作节点运行在我们的基础设施上,绝不提供此模式。
工作节点必须位于同一台机器。docker compose 安装默认如此;其他主机上的专用工作节点无法访问 Bridge。完整说明见邮箱指南。