团队里共用一套账号密码这件事,几乎每个有点规模的技术团队都绕不开。有人用共享表格,有人直接甩在群里,有人用某个同事的个人网盘,等到人员流动或者要做审计的时候才发现根本理不清谁在什么时候看过哪条凭据。Passbolt 就是冲这个场景来的——开源自托管的团队密码管理器,端到端 OpenPGP 加密,服务端拿到的永远是密文。我在几个不同规模的团队里用它做过内部凭据集中管理,从最早的单机 Docker 部署,到后来多实例加反向代理,再到现在用脚本批量操作 Passbolt API 做凭据迁移和自动巡检,前后折腾了不短时间。这篇就把安装、配置,以及大多数人真正卡住的那部分——API 到底怎么调通——一次讲清楚。不管你只是想给团队搭一个能用的密码库,还是想把它接进自己的自动化流程,下面这些内容都能直接抄。
1. 先想清楚:为什么是 Passbolt,以及 API 到底能干什么
1.1 它的加密模型决定了它能解决什么问题
在动手装之前,得先搞明白 Passbolt 的加密模型,否则后面配置 GPG 密钥那一步你会完全不知道为什么。它采用的是典型的零知识架构:每条凭据在写入之前,就已经在浏览器端用非对称加密处理过了,服务端数据库里躺着的只有密文和一份"谁能访问这条记录"的权限表,没有任何明文。用户登录之后,私钥在本地解开,再由浏览器插件完成解密和再加密。
这个模型直接决定了它的能力边界。好处很明确:即使数据库被拖走,攻击者拿到的也是一堆没有私钥就解不开的密文;管理员可以重置某个用户的账号,但理论上无法窥探那个人曾经存过的内容。代价也很明显:一旦用户丢失私钥且没有恢复手段,数据就是真的没了,没有"超级管理员帮你找回"这条路。所以它适合的是内部技术团队共享基础设施凭据这种场景——数据库口令、部署账号、第三方服务密钥、服务器 SSH 之外的各类 Credential——而不是那种需要人找回密码的消费级产品。
理解了这一点,你就知道为什么安装过程里有一大半时间花在密钥上,也知道了为什么后面的 API 调用必须自己处理加解密,而不是简单地 POST 一个明文 JSON 过去。
1.2 API 的两层结构,很多人在这就栽了
Passbolt 的 API 并不是传统意义上"带个 token 就能读写数据"的 REST 接口,它是分两层的,这个结构必须掰开来看。
第一层是认证层,走的是 GPG 挑战应答。你要先告诉服务端自己是哪个公钥指纹,服务端返回一段用你的公钥加密的随机挑战串;你用私钥解密、签名、再提交回去,服务端验签通过后才发给你一个 JWT。第二层是数据层,你拿着 JWT 去访问/resources.json、/users.json这些端点,但所有敏感字段仍然是密文,需要你在本地用私钥解开,写入时也要先在本地加密再提交。
很多人第一次调 API 失败,就是因为只做了第二层——拿到一个 token 就去请求资源,结果要么 401,要么拿到一堆看起来像乱码的 JSON。这不是接口坏了,是设计如此。所以本文后面会花大篇幅讲认证握手的具体实现,这是绕不过去的一关。
1.3 社区版和专业版在 API 上的差异,得提前知道
这是一个官方文档里写得比较散、但我踩过实实在在坑的点。Passbolt 的社区版(CE)和专业版(Pro)在 API 能力上并不完全对等。随着资源类型体系演进(从早期的逐字段加密,到后来的 resource types 打包),新版本接口里的相当一部分能力是跟着付费版本走的。你在自建的社区版环境里,如果照着最新版官方 API 文档去调 v4、v5 的端点,很可能直接吃一个 404 或者 403。
我的经验是:在自建社区版环境下,优先确认服务端实际暴露了哪些端点和资源类型,再决定用哪套接口。具体做法是先访问/resource-types.json看看返回了哪些类型,再用GET /api/healthcheck.json确认服务活着。接口前缀在不同版本里可能是/api/也可能是/api/v2/,别凭记忆写死,先用 curl 探一下再动代码。这个习惯能帮你省掉大半天排查时间。
2. 环境准备与部署方式选型
2.1 三种部署路线的取舍
Passbolt 官方给了几条路:Docker 编排、从源码装到 LEMP 环境、以及各家云市场的镜像。我把它们的适用场景列出来,你可以对号入座。
| 部署方式 | 上手难度 | 可维护性 | 适用场景 | 主要坑点 |
|---|---|---|---|---|
| Docker Compose | 低 | 高 | 绝大多数自建场景,推荐 | 卷挂载路径、密钥持久化 |
| 源码 + Nginx + PHP-FPM | 高 | 中 | 已有 LEMP 体系、需要精细调优 | PHP 扩展、目录权限、SELinux |
| 云市场镜像 | 极低 | 低 | 快速验证、临时演示 | 版本滞后、升级路径不清晰 |
我自己现在一律走 Docker Compose。原因不复杂:Passbolt 依赖的 PHP 扩展比较多(gnupg、intl、mbstring、pdo_mysql、curl、openssl 这些),源码装的时候光扩展版本对齐就够喝一壶,而官方镜像已经把这些都调好了,升级也只是换个 tag 重启的事。源码装唯一的优势是你可以精细控制 PHP-FPM 的进程数和超时,但对一个内部工具来说,这点收益不值得。
2.2 服务器规格怎么估
官方给的最低配置是 1 核 2G,但这个数字在实际使用中偏紧。我的建议是按团队规模来:
- 20 人以内、日常只是查查密码:2 核 4G 完全够,磁盘 20G 起步。
- 50 人左右、有自动化脚本频繁调 API:4 核 8G,磁盘给到 50G。
- 再往上就要考虑把数据库拆出去,并且前面挂负载均衡做多实例。
磁盘容量的估算逻辑是这样的:Passbolt 数据量本身极小,一条凭据的密文也就几 KB,真正的空间消耗在数据库日志、容器镜像层和备份上。所以别抠磁盘,反倒是备份策略要提前想好,这个后面单独讲。
操作系统层面,Ubuntu LTS 或者 Debian 稳定版都行,内核版本不要太老,Docker 需要的东西它都有。装之前先把时区设对,timedatectl set-timezone一把梭,因为 JWT 的签发和校验都跟时间戳强相关,时区或者系统时间漂了,你会遇到那种"token 明明刚拿到就说过期"的诡异问题。
2.3 域名、TLS 和网络入口,这三件事必须在启动前定下来
这是最容易返工的地方。Passbolt 有一个环境变量叫APP_FULL_BASE_URL,它决定了系统生成的所有链接、邮件里的邀请地址、API 返回里的资源 URL。这个值一旦写错,后面改起来是要重启容器并且清理缓存的,非常烦。
正确的做法是:先确定最终对外访问的域名,配好 TLS 证书,再启动容器。哪怕你只是想在内网测试,也建议用一个正经域名(内网 DNS 解析即可),而不是用 IP 直接访问,因为后面 GPG 密钥的 user ID、JWT 的 audience 校验都可能跟这个 URL 绑上关系。
TLS 这块有两种常见做法。一种是把证书直接挂进容器,让 Passbolt 自己处理 HTTPS;另一种是在前面放一层反向代理(Nginx、Caddy 之类),容器内部只跑 HTTP。我更推荐第二种,原因是证书续期、访问日志、限流这些事在反向代理层做要顺手得多,容器只管业务。
如果用反向代理,有几个细节必须配对:
注意:反向代理必须正确传递
X-Forwarded-Proto、X-Forwarded-For、Host这三个头,同时把请求体大小限制放开(client_max_body_size建议 20M 以上),否则上传附件会直接 413。
还有一点,反向代理的超时不要太短。Passbolt 在做密钥相关的操作时偶尔会慢一点,proxy_read_timeout给到 60 秒比较稳妥。
3. 分步安装:从零到一个能登录的实例
3.1 拿到编排文件,别急着 up
先把工作目录建好,把官方的 compose 文件和环境变量模板拉下来。这里有个小习惯我一直在用:把数据卷的宿主机路径显式写死,而不是用命名卷。原因是命名卷虽然管理方便,但迁移和备份的时候你得先想起它的名字,显式路径直接tar一下就行。
目录结构我一般这么规划:
/opt/passbolt/ ├── docker-compose.yml ├── passbolt.env ├── gpg/ # 服务端 GPG 密钥,必须持久化 ├── jwt/ # JWT 签名密钥 ├── data/ # 数据库数据文件 └── backups/ # 备份输出gpg/和jwt/这两个目录是命根子。丢了gpg/,服务端认证会直接崩;丢了jwt/,所有已签发的 token 立刻失效,用户得重新登录(这个影响小一点,但也会让人措手不及)。
3.2 环境变量逐项过一遍
配置文件里字段不少,我只讲那些真正需要你动手改、改错了会出问题的。
APP_FULL_BASE_URL:填最终对外访问地址,带协议,不带尾斜杠。例如https://passbolt.example.com。这是全篇最重要的一个值。
DATABASE_*系列:主机名在 compose 网络里就是服务名;数据库名和用户名建议别用默认的,密码用openssl rand -base64 32生成一个。如果数据库是 MySQL 8,还有一个隐藏问题——MySQL 8 默认的认证插件是caching_sha2_password,某些 PHP 驱动版本对接不上,表现出来就是容器起来了但连不上库。解决办法要么是给这个用户单独指定mysql_native_password,要么直接用 MariaDB,省心。
EMAIL_*系列:这个是决定你团队能不能正常邀请成员的关键。必填的是发件地址、SMTP 主机、端口、用户名、密码,以及加密方式。如果用的是 587 端口配 STARTTLS,注意对应变量要设成 TLS 而非 SSL;用 465 端口才是 SSL。这一项配错的概率极高,因为配错了容器照样能启动,只有在你点"邀请用户"的时候才会报错,而且报错信息还不一定直白。
GPGA_SERVER_*相关:指定服务端密钥的位置。较新的镜像支持在目录为空时自动生成密钥对,老版本需要你手动先生成再挂进去。我倾向于手动生成,因为自动生成的话你不知道它的指纹和过期策略,后续排查时很被动。
3.3 服务端 GPG 密钥怎么生成
服务端这对密钥不是用来加解密凭据的(凭据加解密用的是用户自己的密钥),它的作用是给 JWT 签名、给服务端身份做背书。所以它只需要签名能力,不需要加密能力。
生成的时候,密钥类型选 RSA,长度 3072 是个比较合适的折中——2048 现在偏弱,4096 在小机器上做签名运算会明显慢一点点,3072 兼顾了安全性和性能,也让容器启动时的自检快一些。过期时间上,内部工具我一般设成不限期,但这意味着你要靠流程去定期轮换,所以我建议还是设个两三年,到期前做一次替换,有提醒总比没提醒好。
gpg --batch --gen-key <<EOF %no-protection Key-Type: RSA Key-Length: 3072 Key-Usage: sign Name-Real: passbolt server Name-Email: server@internal.example.com Expire-Date: 2y %commit EOF%no-protection这一行是必须的,因为容器里的服务进程没法交互式输密码。生成之后把公钥和私钥都导出成 ASCII 格式放进gpg/目录。
注意:导出文件之后,一定要把宿主机上的属主改成容器内运行用户对应的 UID(通常是 33 或者 www-data)。属主不对,容器内读不到密钥,健康检查会报 GPGA 相关项失败,而错误日志里往往只写一句"could not read key",特别容易误导人去查路径,其实是权限问题。
3.4 启动、初始化和健康检查
准备工作做完就可以起了。先只起数据库,等它初始化完成,再起应用容器。这个顺序不是迷信,是因为应用容器启动时会尝试连库并做数据库迁移,如果库还没 ready,它会失败退出,然后你再重启一次——多来几次你就烦了。
docker compose up -d db docker compose logs -f db # 看到 ready for connections 再往下 docker compose up -d起来之后第一件事是跑健康检查:
docker compose exec passbolt passbolt healthcheck这条命令会把所有关键项列出来:PHP 版本、扩展、GPG 密钥是否可读可用、JWT 密钥是否就位、数据库连接与 schema 版本、目录权限、SMTP 配置完整性。我的要求是除个别明确可忽略的项外,尽量做到全绿。特别是 schema 那一项,如果有 pending migration,说明数据库结构和你当前镜像版本不匹配,必须补跑迁移:
docker compose exec passbolt passbolt migrate迁移这件事在升级时尤其重要。我的习惯是每次拉新镜像之后,先备份,再up -d,然后立刻migrate,最后healthcheck确认,四步走完才算升级完成。
3.5 建第一个管理员账号
服务刚起来是没有用户的,第一个管理员得用命令行创建:
docker compose exec passbolt \ su -m -c "bin/cake passbolt register_user -u admin@example.com -f Admin -l User -r admin" \ -s /bin/sh www-data执行完会输出一个邀请链接。这个链接必须用浏览器打开,装好对应的浏览器扩展,在扩展里完成密钥生成——私钥是在浏览器里生成的,永远不会传给服务端。这一步做不完,账号就是未激活状态,API 也调不通。
顺带说一句,如果你们的 SMTP 已经配好了,后续加人可以直接在管理后台发邀请邮件,比命令行方便。但第一个管理员必须走命令行,因为那时候还没人能登录后台。
4. API 认证流程拆解与实操
4.1 认证走的是挑战应答,不是用户名密码
这是整篇最需要耐心看的部分。Passbolt 的 API 认证完全不涉及密码,流程是这样的:客户端告诉服务端"我要用指纹为 X 的这把钥匙登录",服务端查到这个指纹对应的用户,生成一段随机数据,用该用户的公钥加密后返回;客户端用私钥解开拿到挑战串,把它连同一些元数据序列化后签名,再提交给服务端;服务端用用户公钥验签,通过则签发 JWT。
整个流程的密码学意义在于:证明你持有与该账号绑定的私钥。服务端从头到尾不知道你的私钥是什么,也不需要知道。这就是为什么它敢把数据库托管给你自己——即使被拖库,攻击者没有私钥也模拟不出你的登录。
实现时有两个细节容易翻车。第一,签名的内容格式必须和服务端预期完全一致,通常是包含版本号、挑战串、密钥指纹的 JSON 字符串,字段名和顺序都不能随意改。第二,签名之后要做 ASCII armor 编码,直接提交二进制签名会验签失败。
4.2 拿到 JWT 之后怎么用
JWT 拿到手之后,后续请求放在Authorization: Bearer <token>头里就行。有效期默认不长(通常几十分钟量级),过期后需要重新走一遍认证流程。所以写自动化脚本的时候,别把 token 硬编码,而是在脚本里做一个"带缓存的登录函数":token 存在内存里,快过期了再重新握手,这样既避免了每次请求都做一遍 GPG 运算(这玩意儿是真的慢),也避免了 token 过期导致的中断。
另外,如果你们前面有反向代理或者负载均衡,注意所有实例背后的系统时间必须同步。JWT 校验对时间极其敏感,几台机器之间差个一两分钟,就会出现"这个 token 还没生效"或者"已经过期"的奇怪报错。
4.3 一段能跑通的认证脚本
下面这段 Python 是我实际在用的,依赖requests和PGPy。它做了三件事:请求挑战、解密并签名、换回 JWT。
import json, requests from pgpy import PGPKey, PGPMessage BASE = "https://passbolt.example.com" API = f"{BASE}/api" def load_key(path): key, _ = PGPKey.from_file(path) return key def login(key): # 指纹要去掉空格,服务端比对的是紧凑格式 fp = str(key.fingerprint).replace(" ", "") r = requests.post( f"{API}/auth/login.json", json={"gpg_auth": {"keyid": fp}}, timeout=15, ) r.raise_for_status() challenge_msg = PGPMessage.from_blob(r.json()["challenge"]) challenge = key.decrypt(challenge_msg).message # 这里的字段名要跟你服务端版本对应,先用一次成功的手工调用抓包确认 payload = { "version": "1.0", "challenge": str(challenge), "keyid": fp, } sig = key.sign(json.dumps(payload))[0] r2 = requests.post( f"{API}/auth/login.json", json={"gpg_auth": { "keyid": fp, "user_token_result": sig.armor(), }}, timeout=15, ) r2.raise_for_status() return r2.json()["body"]["token"]提示:
challenge解密出来的是字符串还是 JSON 对象,取决于服务端构造方式,第一次跑的时候直接把类型打印出来看一眼,别想当然。我第一次就因为多套了一层引号,白折腾了一个下午。
拿到 token 之后,把所有请求的 header 加上{"Authorization": f"Bearer {token}"},就可以开始读数据了。
5. 核心 API 资源操作实录
5.1 常用端点速查
先给一张表,方便你对照着写脚本。注意前缀在不同版本下可能是/api或/api/v2,拿不准就先探一下。
| 资源 | 方法 | 路径 | 用途 |
|---|---|---|---|
| 认证 | POST | /auth/login.json | 挑战请求与换取 JWT |
| 认证校验 | GET | /auth/verify.json | 验证当前 token 是否有效 |
| 资源列表 | GET | /resources.json | 拉取凭据列表 |
| 资源详情 | GET | /resources/{id}.json | 单条凭据 |
| 创建资源 | POST | /resources.json | 新建凭据 |
| 更新资源 | PUT | /resources/{id}.json | 修改凭据 |
| 用户列表 | GET | /users.json | 成员与公钥 |
| 用户详情 | GET | /users/{id}.json | 含该用户公钥 |
| 分组 | GET | /groups.json | 权限分组 |
| 文件夹 | GET | /folders.json | 目录结构 |
| 资源类型 | GET | /resource-types.json | 判断服务端能力 |
/users.json是个很关键但常被忽略的端点。因为你要写数据给某个用户,必须先用他的公钥加密,而公钥就在这个接口返回里。所以完整的新增凭据流程其实是:拉用户列表拿公钥 → 本地加密 → 提交。
5.2 读数据:解密才是真正的工作量
GET /resources.json返回的是一串资源对象,看着字段挺全,但 name、username、uri、description 这些字段的内容基本都是密文。你得逐条用自己的私钥解开,才能看到真实值。
这里有个性能上的现实问题:每条资源都要做一次 GPG 解密,几十条还行,几百条就是肉眼可见的慢。所以我在批量导出场景里的做法是:先只拉元数据(id、resource_type_id、权限信息),需要详情的再单独请求,避免一次性把几百条全解开。如果确实需要全量迁移,那就接受慢,或者做并发,但并发度别开太高,服务端也扛不住。
解密的时候还得注意一个版本差异:早期格式是每个字段单独加密,解出来是一个个独立的 JSON 值;较新的资源类型是把整个凭据打包成一个 secret,解出来是一个结构化对象。判断依据是resource_type_id,先查/resource-types.json对一下号,再决定用哪套解析逻辑。这块如果不做版本判断,代码换一个环境就跑不通,是那种"本地好好的、一上线就崩"的典型。
5.3 写数据:加密顺序和字段完整性
写入的时候顺序是这样的:先确定目标资源类型、确定要共享给哪些人和组、拿到这些人的公钥、在本地构造明文对象、分别用每个人的公钥加密一份(注意是每人一份,因为每个人用自己的私钥解)、最后把加密结果打包提交。
对,你没看错,同一条密码要加密 N 次,N 等于有权限访问它的人数。这听起来很浪费,但这是共享密码管理的必然结果——只有这样才能做到服务端零知识。数据量小的场景完全无感,但如果你们搞那种一次共享给几十上百人的操作,提交体积会明显变大,反向代理的 body 限制这时候就派上用场了。
另外一个实操细节:更新资源时,必须把完整的数据重新提交,而不是只提交改动的字段。因为加密是一次性的,你没法只改其中一个字段而不影响整体结构。所以正确的更新姿势是:读取 → 解密 → 修改 → 重新加密 → 全量提交。我见过有人以为 PUT 是增量的,结果把整条记录的描述给清空了。
5.4 分页、过滤和排序的实用组合
数据量上来之后,全量拉取是不现实的,必须用分页。几个常用参数:
# 分页 + 排序 GET /resources.json?limit=50&page=2&sort=Resources.name&direction=asc # 只看根目录下的资源(不带父文件夹的) GET /resources.json?filter[has-parent]=0 # 带上权限信息,方便判断这条记录共享给了谁 GET /resources.json?contain[permission]=1limit的上限我印象里是 100,超过会被截断。所以做全量同步的时候,标准写法是一个 while 循环,一直翻到返回的数组为空或者长度小于 limit 为止。顺手把meta.pagination里返回的count记下来,跟实际拉到的条数对一下,对不上就说明中间漏页了——这多半是因为你在翻页过程中有人改了数据,导致排序不稳定。稳妥点的话用 id 做二次校验。
6. 常见问题排查实录
6.1 启动阶段的问题
容器反复重启,日志只写"connection refused":九成是数据库没起来或者密码不对。先docker compose logs db看数据库的状态,再看应用容器里的连接参数有没有拼错。如果数据库是 MySQL 8,用工具有没有连过?附一个快速验证方法:进 db 容器用命令行连一下,能连上就说明问题在应用侧。
健康检查里 GPGA 相关项失败:按顺序排查三件事。文件在不在gpg/目录里?文件属主是不是容器内运行用户?公钥和私钥是不是配对的那一对?我遇到过一次是导出的时候只导出了公钥,私钥文件是空的,容器照样启动,但认证流程在签名那一步才崩,排查了好一会儿。
邮件发不出去:先看端口和加密方式的组合对不对(587 配 TLS、465 配 SSL)。再看发件地址的域名跟 SMTP 服务商要求的是否一致,很多服务商不允许随便填发件人。最后看容器能不能解析外网 DNS,有些内网环境 DNS 配得不全,容器里解析不到 SMTP 主机名。
6.2 认证阶段的坑
| 现象 | 最可能原因 | 处理方式 |
|---|---|---|
| 401 Unauthorized | token 过期或系统时间偏差 | 重新握手,同步 NTP |
| 403 Forbidden | 账号角色不够 | 确认是否 admin 角色 |
| 解密 challenge 失败 | 用错了私钥 | 核对指纹与私钥是否匹配 |
| 验签失败 | 签名内容格式不符 | 抓包比对字段名与序列化方式 |
| 登录成功但读数据 401 | token 未正确放进 header | 检查 Bearer 前缀与空格 |
验签失败这个问题特别值得多说两句,因为它几乎不会有清晰的错误信息。我的排查套路是:先用官方浏览器扩展成功登录一次,在开发者工具里把两次请求的 body 结构完整看一遍,然后照着这个结构去构造自己的请求。这比对着文档猜要快十倍。
6.3 数据操作阶段的问题
读回来的字段是乱码:那就是密文,你没解密。这是新手最常见的困惑,尤其是第一次看到响应体的时候会以为接口出毛病了。
能解密但解出来的内容不全:资源类型判断错了,用了老格式的解析逻辑去处理新格式的数据。回到/resource-types.json对一下,按类型分支处理。
更新之后其他成员看不到了:重新加密提交时漏了某些成员的公钥,导致他们的密钥副本缺失。共享操作是一个整体,任何一次写入都要把所有有权限的人的公钥都用一遍,漏一个那个人就解不开了。
删除凭据之后权限记录还在:软删除和权限级联的关系,清理逻辑要自己补。这个建议直接写成定时任务,定期扫一遍孤儿权限记录。
心得:调试 API 的最高效方式永远是"先用官方扩展成功做一遍,再抓包抄结构"。文档描述的是设计意图,抓包看到的是实际行为,两者不一致的时候以抓包为准。
7. 运维层面我踩过的几个真实坑
7.1 备份这件事,重点根本不在数据库
大多数人备份只备数据库,这是致命的。Passbolt 的备份必须包含四样东西:数据库、gpg/目录、jwt/目录、以及那个环境变量文件。少一样,恢复出来的实例就是残的。
最惨的一种情况是:数据库恢复了,但gpg/没了。这时候所有用户的认证都失效,理论上可以通过重置用户密钥来恢复访问,但已经存进去的凭据如果用户自己的私钥也丢了,那就是永久性丢失。所以我在做部署的时候会强制加一条规则:每天增量备份数据库,每次配置变更后手动导出一次密钥目录并异地存一份。密钥目录变化频率极低,一年也动不了几次,手动完全可行。
恢复演练也得做。我见过备份文件躺在那里三年,真出事的时候发现解压出来是空的,因为备份脚本里路径写错了但从来没报过错。建议每半年挑个测试环境真恢复一次。
7.2 升级要按顺序,跳版本容易出事
Passbolt 的版本升级有一条隐含要求:不要跨太多版本直接跳。数据库迁移脚本是逐版本累积的,跳太远可能出现迁移链断裂。安全做法是先查清楚目标版本和当前版本之间隔了几个大版本,如果超过两三个,就分步升,每次升完跑一次迁移和健康检查。
升级前必做的动作清单:备份数据库和密钥目录、记录当前版本号、把 compose 文件里的镜像 tag 从latest改成具体版本号(这个习惯强烈建议养成,latest是万恶之源)、然后在维护窗口里操作。
7.3 一份我自己在用的上线前自检清单
最后把这个清单分享出来,每次新建实例或者做大变更之前我都会过一遍:
- 域名和
APP_FULL_BASE_URL完全一致,且已配置有效 TLS gpg/、jwt/目录已持久化且属主正确- 数据库密码不是默认值,且未出现在任何代码仓库里
- SMTP 实测能发出邮件(真的点一次邀请,别只是配置完就算)
- 健康检查除明确可忽略项外全部通过
- 系统时间已同步,时区设置正确
- 反向代理的 body 限制和超时已调整
- 备份脚本已跑通一次,且验证过可恢复
- 至少两个管理员账号,避免单点
- 资源类型的实际支持情况已用
/resource-types.json确认,脚本里的接口版本与之匹配
这套流程跑顺之后,重建一个实例大概半小时以内就能搞定,剩下的时间都可以花在怎么把 API 接进自动化流程上,那才是真正省人力的部分。