Files
lislgosms/docs/production-deployment.md
T
hectorzhao 809175b544
CSS quality / css-quality (push) Has been cancelled
fix: enforce production React release builds
2026-09-08 22:39:32 +08:00

339 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CMPP 平台部署手册(当前实例为预发布环境)
## 当前预发布环境端口与域名
- 运营端、客户端页面:`https://sms.lisglo.com`Cloudflare 橙云代理,源站使用 Cloudflare Origin CA;旧 `12026` 仅在切换期临时保留。
- 客户 HTTP API`https://api.lisglo.com/api/openapi/v1`Cloudflare 灰云直连,源站必须使用公网信任的 Let’s Encrypt 证书;该虚拟主机不得暴露 admin/client 管理接口或前端页面。
- API:仅本机 `127.0.0.1:3000`,由 Nginx `/api/` 反向代理。
- Redis:仅本机 `127.0.0.1:6379`
- PostgreSQL:仅本机 `127.0.0.1:5432`
- MinIO:仅本机 `127.0.0.1:9000/9001`,页面上传下载通过 API 转发。
- Gateway 控制服务:仅本机 `127.0.0.1:8090`
- CMPP 入站端口:`17890`,由 Go Gateway 启动真实 CMPP 3.0 Server,接收企业应用下游 connect/login 和 submit。
## 预生产磁盘与目录规划
本节是预生产 `8.160.169.106` 的存储规范入口;实施证据见 [第一阶段记录](preprod-data-disk-stage1-20260831.md)和[第二阶段记录](preprod-data-disk-stage2-20260831.md),阶段完成状态见 [测试与运维进度](testing-progress.md)。旧版部署示例与本节冲突时,以本节为准。
### 当前状态与实施边界
- 以下已实施状态来自2026-08-31晚间第二阶段验证,不代表每次阅读时已重新核验服务器。
- 60GiB系统盘承载操作系统、运行程序、应用日志和监控历史数据;100GiB数据盘挂载 `/data`,已承接PostgreSQL、Redis、MinIO、版本恢复备份及原备份目录内的操作记录。
- 用户明确授权后已完成第二阶段三项存储迁移;应用日志、监控历史数据及其他表内待实施项不包含在本次迁移中,不得把规划当成实施事实。
- 本次迁移的系统盘原数据副本及一致性冷备暂作恢复资产保留,不属于新增旧代码版本。后续清理或进一步迁移需按明确指令执行,不能直接切回已落后于新写入的旧数据。
### 目录分工
| 内容 | 当前路径或位置 | 规划位置 | 实施状态与要求 |
|---|---|---|---|
| 操作系统、软件包、服务配置及密钥 | 系统盘;配置主要在 `/etc` | 继续留在系统盘 | 配置按最小权限保存,不因数据迁移改为公开可读 |
| 当前运行程序与依赖 | `/opt/cmpp-platform` | 继续留在系统盘 | 发布切换只处理程序,不携带持久化数据 |
| 上一个版本运行目录 | `/opt/cmpp-platform.previous-*` | 继续留在系统盘 | 稳态仅保留上一个有效版本;不得积累失败、incoming及更早版本目录 |
| 数据库、Redis、源码与配置的成套恢复备份 | `/opt/cmpp-platform-backups` | `/data/cmpp-platform-backups` | **已实施**;原入口是指向数据盘的符号链接,发布脚本不得替换成普通目录 |
| 备份目录内业务操作留痕 | `/opt/cmpp-platform-backups/operations` | `/data/cmpp-platform-backups/operations` | **已实施**;不纳入旧版本自动清理范围 |
| PostgreSQL业务数据及WAL | `/var/lib/pgsql/data` | `/data/postgresql/data` | **第二阶段已实施**`/data/postgresql`绑定挂载到`/var/lib/pgsql`,无外置表空间或WAL链接,完整冷复制及110张表行数核对通过 |
| Redis持久化文件 | `/var/lib/redis` | `/data/redis` | **第二阶段已实施**;绑定挂载保留旧路径,保持RDB开启、AOF关闭,停写SAVE后冷复制,三条Stream状态一致 |
| MinIO对象及存储元数据 | `/var/lib/minio` | `/data/minio` | **第二阶段已实施**;绑定挂载保留旧路径,完整目录及62个业务对象清单核对通过,独立探针读写删除通过 |
| 本地对象存储备用驱动的上传文件 | 配置项 `OBJECT_STORAGE_LOCAL_ROOT` | 如启用则规划为 `/data/cmpp-platform/object-storage` | 条件规划;第一阶段未启用或切换存储驱动,禁止擅自改用local替代MinIO |
| API、各Worker、Gateway应用日志 | `/opt/cmpp-platform/logs/<service>` | `/data/log/cmpp-platform/<service>` | **待单独实施**;需同步日志输出路径、权限和轮转,不能因迁盘取消保留上限 |
| Prometheus历史指标 | 仓库安装脚本使用 `/var/lib/prometheus/metrics2` | `/data/prometheus/metrics2` | **待核验并单独实施**;当前值以实际服务参数为准,保留时间及容量双重限制 |
| 系统journal、系统及安全日志 | `/var/log` 等系统路径 | 继续留在系统盘 | 设置轮转及容量上限;不能将安全审计日志当旧发布文件删除 |
待实施目标子目录应在相应迁移时创建,不提前启动服务写入。目前备份入口使用符号链接,三项存储使用绑定挂载;其他规划路径不假设已有链接、挂载或服务配置支持。
### 挂载、权限与缺盘保护
- 使用磁盘UUID持久化挂载,不把 `/dev/nvme1n1` 的枚举顺序作为永久身份。当前预生产UUID与fstab记录见第一阶段文档;其他主机不得照抄该UUID或格式化命令。
- 第一阶段 `/data` 采用 `nofail`,仅保证缺盘时系统仍可启动;这不意味着未来依赖数据盘的数据库可以在缺盘时启动。
- 现有备份保护由 `/usr/local/sbin/cmpp-backup-storage-check` 和未挂载时底层空 `/data` 的immutable属性共同提供。不得移除保护、重建系统盘备份目录或在检查失败后改用其他路径继续备份。
- 第二阶段已为`postgresql``redis``cmpp-minio`配置`50-cmpp-data-disk.conf`,通过`RequiresMountsFor``After``BindsTo`关联`data.mount`及各自绑定挂载;`ExecStartPre`调用`/usr/local/sbin/cmpp-data-storage-check`核验UUID、挂载源目录、读写属性和原有存储标记。后续服务迁入也必须在启动前建立同等保护。
- 挂载失败、UUID不匹配或数据盘只读时,备份及相应迁入服务应明确失败并告警,禁止初始化新数据库、空Redis或空MinIO来掩盖故障。
- 各数据目录归对应服务的实际运行用户,保留原权限、ACL、扩展属性与适用的SELinux上下文;核验systemd沙箱的 `ReadWritePaths` 等限制。禁止对整个 `/data` 执行统一业务用户授权或 `chmod 777`
- 三项原路径通过fstab中的绑定挂载兼容,底层空挂载目录保持immutable,缺盘时不能写回系统盘。部署与安装脚本检测`/etc/cmpp-platform/data-disk.uuid`或PostgreSQL存储drop-in后,先执行存储检查;不得删除标记或drop-in绕过检查。尚未以整机重启验证开机过程。
### 备份、保留与容量规则
- 所有后续发布备份统一使用固定入口 `/opt/cmpp-platform-backups`,先执行存储检查,失败即中止;禁止使用随发布切换的 `/opt/cmpp-platform/backups`
- 发布前生成数据库、运行代码和配置的成套恢复点;涉及Redis队列或对象存储的变更,还须包含相应一致性恢复资产。SHA-256、数据库备份目录和压缩包可读性检查通过不等于已完成实际恢复演练。
- 版本保留目标是“当前运行版本 + 上一个有效运行版本 + 上一个版本的完整恢复备份”。发布期间允许暂时保留原有效恢复点与新候选恢复点;新恢复点校验、发布验收和版本对应关系确认前,不能先删唯一有效备份。
- 失败发布、临时传输包、incoming及更早恢复点不长期保留;先确认不被进程引用、无待诊断事故或未完成回退,并按当次明确授权清理。文档中的保留目标不等于已安装自动清理任务,也不授权定时删除业务操作记录。
- 日志和监控数据需要轮转、压缩及容量上限。应用日志保留期不得替代数据库中操作日志的业务保留规则,短信、账务和审计数据不得因磁盘告警直接删除。
- 系统盘与数据盘分别监控使用率、可用字节、inode、读写异常和增长速度。规划告警分级为70%预警、80%严重、90%紧急;属于**待配置并验证的运维目标**,本次文档更新不表示告警已上线。
- 发布或迁移前按实际数据量核算源/目标临时副本、恢复备份、数据库WAL与Redis持久化重写所需空间;余量不足则停止该次操作,不以删除唯一恢复点腾空间。
- 数据和备份同在这块100GiB盘后会共用容量及故障风险。同机备份不等于异机灾备,异机或对象存储副本须另行实施并验证恢复能力;当前不声称已经配置。
### 第二阶段与后续发布验收
1. 收到明确指令后,重新核对实际磁盘、服务配置、运行用户、数据量及业务流量;为每个服务记录源路径、目标路径、复制方式、切换条件和回退点。
2. 先建立可验证的一致性恢复资产。不得把普通在线目录复制视为PostgreSQL或Redis的一致备份;停写窗口或复制同步方式另定。
3. 短信切换前确认入口接收策略、在途提交、供应商回执、持久化Inbox/Outbox和Redis Streams;不得清队列、手工ACK/XDEL或重投真实短信来缩短迁移窗口。
4. 切换后验证实际文件落盘位置、挂载依赖、用户权限、数据库和对象读写、队列积压、供应商连接及API健康;真实短信测试需单独授权。所有检查通过前保留源数据副本,不能只凭HTTP 200删除它。
5. 若新位置已接受业务写入,回退必须包含新增数据的一致性处理,不能直接切回已经落后的旧目录。
6. 同步部署脚本、systemd配置及安装器,防止下次发布把数据目录、日志路径或挂载依赖恢复成旧默认值。当前仓库部署脚本仍向 `$APP_DIR/logs` 输出日志,不能把本文目标路径当作已经实现的脚本行为。
7. 完成状态、配置证据和未验证项写入阶段记录及 `testing-progress.md`;本节维护长期规范,不将一次实施记录当成永久实时状态。
## 首次部署
1. 本机提交并推送 `main`
2. 使用 root 登录服务器,或先放置免密 SSH key。
3. 在服务器执行:
```bash
curl -fsSL http://175.27.255.91:3000/hectorzhao/lislgosms/raw/branch/main/tools/deploy/production-bootstrap.sh -o /root/production-bootstrap.sh
bash /root/production-bootstrap.sh
```
可覆盖的环境变量:
```bash
APP_DIR=/opt/cmpp-platform
REPO_URL=http://175.27.255.91:3000/hectorzhao/lislgosms.git
BRANCH=main
PUBLIC_HTTP_PORT=12026
API_PORT=3000
API_HOST=127.0.0.1
API_METRICS_HOST=127.0.0.1
API_METRICS_PORT=9464
API_DB_POOL_MAX=32
API_WORKER_DB_POOL_MAX=8
HTTP_API_MASTER_KEY=<至少32位随机值,用于AES-256-GCM加密HTTP访问凭据和Webhook密钥>
HTTP_API_PUBLIC_ORIGIN=https://api.lisglo.com
API_ENABLE_SEND_WORKER=true
API_SEND_WORKER_CONCURRENCY=50
ADMIN_SESSION_IDLE_TIMEOUT_MS=3600000
CLIENT_SESSION_IDLE_TIMEOUT_MS=7200000
SESSION_LOCK_RECOVERY_MS=14400000
SESSION_ABSOLUTE_TIMEOUT_MS=43200000
SESSION_RECENT_AUTH_MS=1800000
SESSION_COOKIE_SECURE=true
OPERATION_LOG_ARCHIVE_ENABLED=true
OPERATION_LOG_RETENTION_DAYS=180
OPERATION_LOG_ARCHIVE_BATCH_SIZE=1000
OPERATION_LOG_ARCHIVE_MAX_BATCHES=20
OPERATION_LOG_ARCHIVE_INTERVAL_MS=86400000
SMS_RECEIPT_TIMEOUT_SCAN_ENABLED=true
SMS_RECEIPT_TIMEOUT_HOURS=72
SMS_RECEIPT_TIMEOUT_SCAN_INTERVAL_MS=300000
PROMETHEUS_URL=http://127.0.0.1:9090
PROMETHEUS_QUERY_TIMEOUT_MS=5000
REPORT_DAILY_REFRESH_ENABLED=true
REPORT_REFRESH_INTERVAL_MS=3600000
CMPP_DOWNSTREAM_ACK_TIMEOUT_SECONDS=30
GATEWAY_CMPP_ADDR=0.0.0.0:17890
CMPP_PUBLIC_HOST=8.160.169.106
CMPP_PUBLIC_PORT=17890
GATEWAY_STARTUP_RECONNECT_DELAY_MS=1000
GATEWAY_SUBMIT_WORKER_CONCURRENCY=64
GATEWAY_SUBMIT_RESULT_STREAM=gateway.submit.results
GATEWAY_SUBMIT_RESULT_GROUP=cmpp-api-callback
GATEWAY_SUBMIT_RESULT_CONSUMER=gateway-1
GATEWAY_SUBMIT_RESULT_WORKER_CONCURRENCY=8
GATEWAY_CMPP_INBOUND_MAX_CONCURRENCY=64
OBJECT_STORAGE_DRIVER=minio
OBJECT_STORAGE_LOCAL_ROOT=/var/lib/cmpp-platform/object-storage
PROD_ADMIN_EMAIL=admin@example.com
PROD_ADMIN_USERNAME=prod_admin
PROD_ADMIN_PASSWORD='change-me'
```
系统监控需要在发布前单独安装,配置和规则位于`tools/monitoring/`。在 Debian/Ubuntuapt)或 Alibaba Cloud Linux 4 ARM64 服务器依次执行`bash tools/monitoring/install-prometheus-monitoring.sh``bash tools/monitoring/install-service-exporters.sh`apt 环境使用系统包,Alibaba Cloud Linux 4 ARM64 使用脚本内锁定版本与 SHA-256 的官方 release 二进制。前者安装 Prometheus/Node Exporter,后者安装 PostgreSQL/Redis/Nginx Exporter 并开启 MinIO 回环原生指标。脚本必须先备份已有配置并运行`promtool`校验;9090、9100、9187、9121、9113、9464和API/Gateway控制端口必须只监听`127.0.0.1`,不得加入Nginx公网反向代理或安全组放行。安装完成且确认全部 target 为 up 后,才可完成发布;详细口径见`docs/prometheus-system-monitoring-design-20260814.md`
安全会话使用 HttpOnly Cookie,正式生产必须先为页面和管理 API 配置 HTTPS,并保持 `SESSION_COOKIE_SECURE=true`;纯 HTTP 的 `IP:12026` 不作为受支持的登录入口,即使切换期仍保留其监听,也只允许用于非登录的兼容检查并应尽快下线。`sms.lisglo.com` 只允许 Cloudflare 回源,`api.lisglo.com` 通过独立 Nginx SNI 虚拟主机只开放客户接口、客户 Swagger 和健康检查;Lets Encrypt 使用 DNS-01 自动续期,不依赖开放 80 端口。
系统操作日志默认在线保留 180 天。API 每日以最多 20 个、每批 1000 条的小事务将过期记录搬入 `OperationLogArchive`,并用 `archiveMonth=YYYY-MM` 标记归档月份;归档记录不会自动删除。调整保留期或批量参数前,应先评估数据库、备份窗口和审计要求。归档表达到千万级或清理窗口不能满足要求时,再实施按 `createdAt` 的月度 PostgreSQL 分区,不在当前数据规模下提前改造主表分区。
脚本会安装 Node.js、Go、PostgreSQL、Redis、MinIO、Nginx,创建 systemd 服务,执行 Prisma migrate,构建前端/API/Gateway,并创建平台管理员。Node.js、Go 和 MinIO 下载会按服务器架构自动选择 x64/amd64 或 arm64。
`API_ENABLE_SEND_WORKER=true` 是生产发送链路必填项。后续发布脚本会在构建和迁移前校验该开关以及正整数 `API_SEND_WORKER_CONCURRENCY`;缺失时直接终止发布,防止 API/Gateway 健康但 BullMQ 短信队列无人消费。
HTTP容量边界固定为:NestJS普通JSON/URL-encoded请求体`2 MiB`,仅`/api/client/send/imports/*`使用`25 MiB` JSON解析上限,原始CSV/TSV正文继续由业务层限制为`20 MiB`Gateway读取NestJS API响应最多`4 MiB`且超限必须明确报错。客户文件导入和运营端报备资料导入走`sms.lisglo.com`私有API;报备XLSX允许最大100MiB并另设500MiB解压后总量限制,因此该虚拟主机的`client_max_body_size`必须不低于`110m`(包含multipart开销),标准bootstrap配置为`110m``api.lisglo.com`只承载单条公网HTTP API、Swagger和健康检查,不承载文件导入;不要为导入需求开放私有路由或把NestJS所有JSON接口统一放宽。发布前使用`nginx -T`确认最终生效值,不能只检查仓库模板。
Gateway 的最终 TPS 防线依赖与 API 相同的 Redis。通道连接时会写入 `rate:gateway:channel:config:<channelId>` 权威上限,实际预约使用 `rate:gateway:channel:<channelId>`;这些 key 不应在正常发布时清理。多 Gateway 实例必须指向同一 Redis,才能共享单通道额度。超速的 `gateway.submit.commands` 消息会保持在 consumer group pending 中等待,不应通过手工 `XACK` 或删除 Stream 处理积压;先检查通道配置、Redis key、consumer group 和 Gateway 日志。V2起Submit Worker使用持续补位有界池并逐条ACK,`GATEWAY_SUBMIT_WORKER_CONCURRENCY`缺省64、最大1024;调整前必须同时核对供应商连接数、窗口、TPS限制、Gateway RSS和`cmpp_gateway_submit_worker_slots`,不能用放大并发绕过通道限速。
V3起客户CMPP入站Submit按应用`cmppWindowSize`在单连接内受限并发,全局上限`GATEWAY_CMPP_INBOUND_MAX_CONCURRENCY`缺省64、最大1024。发布后必须先以已认证测试连接确认`cmpp_gateway_inbound_submit_slots{state="configured"}`等于应用有效窗口,再执行阶梯压测;不得通过调高全局值绕过应用窗口,也不得在未验证Sequence_Id关联、心跳/ACK活性和断线清理时直接提高生产并发。
V4起供应商分片和聚合结果写入`gateway.submit.results`幂等Outbox,由默认8槽、最大1024的独立回调Worker上报API。聚合结果XADD与原命令XACK由Redis Lua原子执行;回调成功后结果事件XACK+XDEL,失败事件留在PEL。发布时必须保留同一Redis数据和AOF,不得清理`gateway.submit.results`、其consumer group或`gateway.submit.results:dedupe:*`;调整`GATEWAY_SUBMIT_RESULT_WORKER_CONCURRENCY`前须核对API/PostgreSQL承载能力。第91条向前兼容migration增加`SmsSubmitRecord.resultEventId/resultProcessedAt`,用于回调跨重启幂等,不删除历史字段或数据。
服务重启顺序必须是 Gateway 在前、API 在后。API 启动后等待 `GATEWAY_STARTUP_RECONNECT_DELAY_MS`(默认 1 秒),从 PostgreSQL 读取全部 active 通道并重新下发真实连接命令,同时恢复 Gateway 内存连接池和 Redis 权威 TPS key;禁止沿用数据库中重启前的 connected 状态冒充当前连接。
日报任务默认启用,并由 `REPORT_REFRESH_INTERVAL_MS` 每小时检查一次北京时间业务日是否变化;每个业务日只执行一次 T-4 至 T-1 重算。服务重启后也会自动补跑最近四个完整自然日,确保 72 小时回执更新反映到对账和利润报表。
如预发布服务器临时无法稳定下载 MinIO,可显式传入 `OBJECT_STORAGE_DRIVER=local`,文件会通过真实 API 保存到服务器本地目录 `OBJECT_STORAGE_LOCAL_ROOT``cmpp-minio` 服务会跳过安装和启动。该模式只建议用于验证环境;正式生产建议恢复 `OBJECT_STORAGE_DRIVER=minio`
## 后续发布
```bash
cd /opt/cmpp-platform
git fetch origin main
git reset --hard origin/main
bash tools/deploy/production-deploy.sh
```
### Fail2ban 安全检测发布前置条件
本功能包含新增 PostgreSQL migration、非 root API 身份、安全代理、Fail2ban、Nginx include 和 nftables 表,不能按普通前端热发布处理。发布前除平台标准 PostgreSQL、运行源码和环境文件恢复资产外,必须额外备份 `/etc/systemd/system/cmpp-api.service*``/etc/systemd/system/cmpp-security-agent.service``/etc/fail2ban``/etc/nginx``/etc/nftables.conf``/etc/nftables.d``/var/lib/cmpp-security-agent`,并逐项生成、复核 SHA-256。
发布脚本会构建 `cmpp-security-agent` 并运行 `tools/security/install-security-agent.sh`。安装器创建专用 `cmpp-api` 用户和 `cmpp-security` 组、写入 systemd 加固 drop-in、安装固定 Fail2ban filter/action、校验 Nginx/Fail2ban/nftables,但不会执行任何人工封禁。环境文件至少明确:
```bash
SECURITY_AGENT_SOCKET=/run/cmpp-security-agent/agent.sock
SECURITY_AGENT_TIMEOUT_MS=3000
SECURITY_EVENT_TOKEN=<至少32字节随机值,仅供Gateway和安全代理上报固定事件>
TRUSTED_PROXY_IPS=127.0.0.1,::1
SECURITY_BUILTIN_PROTECTED_NETWORKS=<运维出口CIDR,健康检查CIDR,源站公网IP>
```
正式 Nginx 的 `sms.lisglo.com` 运营端/客户端 server 块必须 `include /etc/nginx/snippets/cmpp-security-deny.conf;`;API 专用域名继续只开放客户接口。Cloudflare `real_ip_header` 及可信网段必须按官方来源单独维护和验证,禁止信任任意客户端 `CF-Connecting-IP``X-Forwarded-For`。发布后需证明 `cmpp-api` 进程用户不是 root、无 sudo 权限且无法写 `/etc/fail2ban`,安全代理 Socket 不监听 TCP,九类规则版本一致,Fail2ban 为 report-onlynftables/Nginx 回读与数据库状态一致。任何一项失败均不得开放人工封禁按钮。
部署脚本重启 Gateway、API 和 Nginx 后,会分别对 Gateway、API 健康接口执行最多 60 秒的逐秒就绪检查。Nest 初始化、活动通道恢复或生产数据量增加可能使 API 启动超过固定数秒;发布流程不得用单次固定延时把正常慢启动误判为失败。超过 60 秒仍不健康时才终止发布,并结合 systemd journal 和发布前数据库、源码、环境备份判断回滚方式。
标准发布会先清空自身管理的`/etc/nginx/conf.d/cmpp-compression.conf`,再排除该文件检查Nginx现有配置。发行版或既有虚拟主机已经启用`gzip on`时直接复用;完全未启用时才写入平台级压缩配置。不得无条件叠加第二个HTTP级`gzip on`,每次重启前必须以`nginx -t`为准。
## 账号和密钥
- 生产管理员账号写入 `/root/cmpp-platform-admin.txt`
- 首次部署汇总凭据写入 `/root/cmpp-platform-credentials.txt`
- 这两个文件权限为 `600`,不要提交到 Git。
- root 密码后续可修改;SSH 私钥放入 `/root/.ssh/authorized_keys` 后即可免密登录。
## 日志
```bash
journalctl -u cmpp-api -f
journalctl -u cmpp-gateway -f
journalctl -u cmpp-minio -f
tail -f /opt/cmpp-platform/logs/api/stderr.log
tail -f /opt/cmpp-platform/logs/gateway/stderr.log
```
## 健康检查
```bash
curl http://127.0.0.1:3000/api/health
curl http://127.0.0.1:8090/health
curl http://127.0.0.1:12026/
redis-cli -h 127.0.0.1 -p 6379 ping
pg_isready -d "$(grep '^DATABASE_URL=' /etc/cmpp-platform/cmpp-platform.env | cut -d= -f2-)"
grep -E '^(API_ENABLE_SEND_WORKER|API_SEND_WORKER_CONCURRENCY|GATEWAY_SUBMIT_WORKER_CONCURRENCY|GATEWAY_SUBMIT_RESULT_WORKER_CONCURRENCY|GATEWAY_CMPP_INBOUND_MAX_CONCURRENCY)=' /etc/cmpp-platform/cmpp-platform.env
grep -E '^(API_HTTP_KEEP_ALIVE_TIMEOUT_MS|API_HTTP_HEADERS_TIMEOUT_MS|CMPP_INBOUND_FAST_PATH_ENABLED|CMPP_INBOUND_WORKFLOW_WORKER_ENABLED|API_INBOUND_WORKFLOW_CONCURRENCY|API_INBOUND_WORKFLOW_BATCH_ENABLED|API_INBOUND_WORKFLOW_BATCH_SIZE|API_INBOUND_WORKFLOW_BATCH_WAIT_MS|API_INBOUND_WORKFLOW_TARGET_BATCH_SIZE|API_INBOUND_WORKFLOW_POLL_INTERVAL_MS|API_INBOUND_WORKFLOW_STALE_SECONDS|API_DB_POOL_MAX|API_WORKER_DB_POOL_MAX|API_WORKER_METRICS_PORT)=' /etc/cmpp-platform/cmpp-platform.env
systemctl is-active cmpp-api cmpp-send-worker cmpp-gateway
curl -fsS http://127.0.0.1:9465/metrics | grep '^cmpp_worker_inbound_workflow_'
redis-cli --scan --pattern 'rate:gateway:channel:*'
redis-cli XINFO GROUPS gateway.submit.commands
redis-cli XINFO GROUPS gateway.submit.results
```
## 回滚
1. 数据库备份:
预生产 `8.160.169.106` 已于 2026-08-31 完成数据盘第一阶段:`/opt/cmpp-platform-backups` 指向 `/data/cmpp-platform-backups`。后续备份先执行存储检查,再写入该固定入口;不得重新写入随运行目录切换的 `/opt/cmpp-platform/backups`。其他环境需先完成相应存储配置,不可直接套用预生产的磁盘 UUID。配置、验证及第二阶段边界见 [数据盘第一阶段记录](preprod-data-disk-stage1-20260831.md)。
```bash
set -e
/usr/local/sbin/cmpp-backup-storage-check
set -a
. /etc/cmpp-platform/cmpp-platform.env
set +a
pg_dump "${DATABASE_URL%%\?*}" > /opt/cmpp-platform-backups/cmpp-$(date +%F-%H%M%S).sql
```
2. 回滚代码:
```bash
cd /opt/cmpp-platform
git reset --hard <上一版提交>
bash tools/deploy/production-deploy.sh
```
3. 如迁移造成不可兼容故障,先停服务,再恢复数据库备份。
## CMPP耐久Inbox与独立Worker发布门禁(2026-08-20
- 发布前恢复资产除PostgreSQL、运行源码和环境文件外,必须包含`cmpp-api.service``cmpp-send-worker.service`及其drop-in;逐项校验`pg_restore --list`、tar可读性和SHA-256。数据库回滚与运行代码必须成套执行,禁止只回退代码后让旧Prisma Client访问新状态机。
- 环境必须显式启用`CMPP_INBOUND_FAST_PATH_ENABLED=true``CMPP_INBOUND_WORKFLOW_WORKER_ENABLED=true`,并给出正整数`API_INBOUND_WORKFLOW_CONCURRENCY``API_DB_POOL_MAX``API_WORKER_DB_POOL_MAX`;推荐初始值分别为32槽、API池32、Worker池8、轮询100ms、租约300秒。API systemd角色必须是`api`Worker角色必须是`worker`Worker可通过`API_WORKER_DATABASE_URL`使用独立地址,未配置时仍使用同一数据库地址但保持独立进程和有界连接池。发布前必须核对两池之和、其他服务连接与运维余量不超过PostgreSQL`max_connections`
- Worker日志目录归`cmpp-api:cmpp-security`且仅服务可写,9465只监听回环并加入Prometheus `cmpp-send-worker` target。发布后必须验证两进程均为非root、API/Gateway health、Worker metrics、PostgreSQL/Redis,以及Inbox pending/processing/最老等待可观测。
- 回滚前先停止Gateway、API和Worker,保留故障现场Inbox及日志;如恢复旧数据库备份,必须同时恢复对应源码和环境/systemd资产。不得在回滚时删除pending Inbox或重投真实短信。
- 第三阶段要求环境显式设置`API_INBOUND_WORKFLOW_BATCH_ENABLED=true`和正整数`API_INBOUND_WORKFLOW_BATCH_SIZE`,初始建议64且不得大于Worker业务槽的可解释倍数。批次增大前必须核对PostgreSQL参数数量、单事务持续时间、Worker RSS和租约时长;付费短信仍走逐条账务锁,不能用零计费批量结果替代付费链路验收。
- 单企业微批阶段要求显式设置`API_INBOUND_WORKFLOW_BATCH_WAIT_MS=40``API_INBOUND_WORKFLOW_TARGET_BATCH_SIZE=32`,目标不得超过批次上限;发布脚本拒绝负等待、超过250ms或目标越界。API还必须设置`API_HTTP_KEEP_ALIVE_TIMEOUT_MS=120000`和更大的`API_HTTP_HEADERS_TIMEOUT_MS=125000`,确保服务端keep-alive长于Gateway 90秒空闲池;发布后用响应头回读实际timeout并检查Gateway日志无loopback reset。
- `GATEWAY_CALLBACK_BATCH_ENABLED`默认关闭,只有独立Gateway callback进程已启用、`GATEWAY_CALLBACK_API_BASE_URL`指向其回环端口且批量路由健康检查通过时才允许显式设为`true`。主API回调模式必须保持`false`;发布后同时检查`gateway.submit.results``pending/lag`,任何持续增长均视为发布失败,禁止手工ACK掩盖回调未持久化。
- 服务重启顺序必须为“独立callback(如启用)→ Gateway → API → Send Worker”。API必须在Gateway已健康后重启,利用启动同步重新下发活动供应商通道;发布后`cmpp_gateway_upstream_connections{state="desired"}``connected`必须恢复到发布前活动连接数,不能只凭Gateway `/health` 判定发布成功。
## 测试环境持久验收管理员(2026-09-06)
用户明确授权创建并保留后续复用的专用测试管理员。2026-09-06在测试环境100.93.204.60创建`codex_qa_admin`,显示名“自动化验收管理员”,用户ID`cmtphgp340000yrle7v11jemc`,角色`platform_admin`、状态`active`;正常验证码登录和退出已验证。该账号仅在测试环境创建,预生产不适用。
安全认证入口:工作站`C:\Users\hectorzhao\.config\cmpp-qa\test-admin.json`,仅当前Windows用户及SYSTEM可访问;测试机备份入口`/home/hector/.config/cmpp-qa/test-admin.json`,目录0700、文件0600、所有者hector。文件保存随机生成密码和登录地址,禁止打印内容、提交Git或将密码写进命令、截图和报告。后续通过既有SSH安全入口读取到内存或使用本机受限文件完成正常登录;账号和凭据按用户要求保留,测试结束退出会话即可。使用前重新核验账号有效性,不覆盖已有账号、不因认证失败重置其他管理员;账号存在不构成发送短信、改余额或通道/客户配置的授权。
## 发送质量监控与报备消息发布(2026-09-06)
本轮仅授权测试环境。候选版本新增20260906170000_report_readiness_notifications及20260906171000_sending_monitor两项兼容迁移;新增表/索引/报备触发器、Gateway可选时间元数据,以及独立cmpp-sending-monitor服务。默认规则和行业纳管均为空,不能套用原型阈值。
cmpp-sending-monitor以cmpp-api:cmpp-security运行,TZ=UTCPostgreSQL池上限2、单实例排他锁、5秒采集;读取已有持久事实,不发送短信、不新增发送链路同步依赖。工作目录api,入口dist/sending-monitor-worker.js,日志logs/sending-monitor,目录0750。标准发布脚本已安装并启用该服务;存量部署按本轮范围建立相同单元,保留已有存储及安全drop-in,不为本功能运行初始化/账号重置/存储配置脚本。
发布前分别核对迁移、存储、DB连接余量、近72小时尝试量和磁盘预算,备份新触发器上线前的数据库。事实72小时、分钟7天、快照30天、关闭告警90天;当前测试机零近期发送、24GB可用空间适用于此次测试准入,不证明500TPS或预生产容量达标。后续高峰上线须按方案容量和CPU/IO/延迟预算实测。
发布顺序:独立候选归档构建→迁移→切换受保护的原运行目录→callback(已启用时)→Gateway健康→API健康→既有Worker→新监控Worker。日志、队列、对象存储和环境沿用;不重启无关存储、不清理历史恢复点、不手工ACK/补发。验证健康检查点更新、规则为空、真实分页API、双端消息页、三尺寸、匿名/越权拒绝及队列基线。应用回退时先停止新Worker,旧代码忽略新增字段;兼容新增表/触发器可保留,数据回退另行评估,不直接覆盖库。
## 2026-09-06 本轮测试发布实绩与归档核验经验
- 测试100.93.204.60从442dda7发布457319e,再于19:49:52更新前端至247fee6d6b4c4aa5abdfb86d34b379552a3a2fa3;本轮预生产未访问。两项新增迁移完成,8项应用服务最终active/NRestarts=0;监控默认规则及纳管为空,不配置用户未指定的阈值。详细真实双端验收和数据保护证据见testing-progress.md同日最终节。
- 独立恢复点/var/backups/cmpp-platform/20260906-sending-monitor-191302含postgres.dump/list、application.tgz、config.tgz、redis.rdb、旧标记及SHA256SUMS;目录0700pg_restore列表、tar可读性和摘要通过,未实际恢复。旧运行目录保留/opt/cmpp-platform-previous-20260906-monitor-442dda7,现有日志继续引用它,不能当作无用目录删除。前端二次发布单独保存frontend-247fee6/previous-dist及previous-source.tgz;这些是本次资产,后续仍需重新建立独立恢复点。
- 本机Git归档受换行配置影响:本次git archive中的文本为CRLF,而git show HEAD:path为LF。首次用git show摘要核对已部署归档文件时6项均不一致,写入前中止;逐一对照原457319e归档成员后确认是换行差异,所有文件与原归档完全一致,再按归档实际字节核验后更新。后续优先以实际发布包摘要及成员字节为依据;不能无证据把换行差异当作线上改动,也不能忽略真实漂移。Shell脚本执行前单独规范LF,不能对运行目录盲目全量转码。
- 真实HTTP主JS/CSS与运行dist摘要一致。最终仅追加验收文档时,可从明确的文档提交归档提取精确文件,备份原文档/标记并验证功能代码无差异,再同步部署标记;不因此重启服务或重跑迁移。不能把整份未提交工作区文档复制到服务器。
- 本轮Git认证:首次推送457319e返回Failed to authenticate user,核对既有helper后在同一调用进程设置GCM_INTERACTIVE=never、GIT_TERMINAL_PROMPT=0,仅重试一次成功;247fee6随后推送首试成功。未修改全局配置/凭据/Deploy Key。仅说明当次标准流程可用,首次失败内部原因仍未查明,不能把临时环境变量当作永久认证修复。标准流程见工作区专项文档git-authentication.md;其既有未跟踪状态受保护,不夹带提交。
## 2026-09-06 监控配置修复局部发布
4c210723cdb133b62462079f14491e37c1b87ed0于20:28:51发布测试环境,修复纳管/规则事务锁void反序列化并移除运行概况三个最近列表及聚合明细;无迁移、无Gateway/发送逻辑变更。恢复点为/var/backups/cmpp-platform/20260906-monitor-config-fix-2025,数据库、应用、配置、Redis和摘要已验证;仅API重启,Gateway及其他Worker/存储进程未重启。精确源包、静态资源与真实API/PG/三尺寸验收详见testing-progress.md同日最终节。
PostgreSQL备份不能直接把含Prisma专用schema参数的DATABASE_URL交给libpq工具;本次以进程内URL解析取得连接项,经PG环境变量调用pg_dump,既不输出连接秘密,也不修改应用连接配置。已有备份失败文件不算有效资产,必须取得成功dump、pg_restore目录和摘要后再发布。
测试环境保留6条【测试演示】监控历史告警及36个初始快照,前缀qa-monitor-demo-20260906,规则表保持空;这些是人工验收数据,不能迁到预生产或当作供应商质量事实。生成脚本留在本机%TEMP%/cmpp-monitor-fix-20260906和测试机/tmp/cmpp-seed-monitor-alerts-20260906.cjs,未加入生产启动流程。工作区原有认证/部署规范改动仍单独保护,本轮功能提交GCM推送首试成功;验收文档c51255d首次推送返回Failed to authenticate user,核对helper后在同样进程级GCM_INTERACTIVE=never/GIT_TERMINAL_PROMPT=0下仅重试一次成功,远端回读确认。未修改认证配置,首次失败内部原因仍未查明。
## 2026-09-06 整体兜底规则管理局部发布
功能版本e4f93f7193496a204efe4e753018a1c1ceb35742已推送,测试22:27:37、预生产22:34:24完成应用发布。发布包来自精确提交,907份源码逐项验证;本次前端/API变更,无依赖、schema、Gateway或Worker契约变更,故仅重启cmpp-api。保留dist下Gateway、安全代理二进制以及旧哈希前端资源,避免重启无关进程或中断旧浏览器会话。
测试恢复资产/var/backups/cmpp-platform/rule-manager-20260906T222440;预生产/opt/cmpp-platform-backups/rule-manager-20260906T222419。PG/Redis/应用/配置及SHA256已验证,数据盘与备份入口保护通过,未演练恢复。预生产备份位于数据盘、运行目录位于系统盘,不能直接rename跨设备目录;本次失败发生在旧dist尚未移动时,校验已复制源码后改在/opt/cmpp-rule-manager-previous-e4f93f7保留previous-dist/previous-api-dist并切换。后续发布应在运行目录同文件系统准备原子切换及应用回退目录,独立恢复包仍写受保护数据盘,不改备份链接。现有logs仍依赖历史运行目录,不清理。
Windows内置tar解压含中文路径的完整归档曾报Invalid empty pathname;本轮用Python tarfile直接读取提交归档计算907文件摘要,并在Linux解压。归档生成、读取和校验分别检查退出状态,失败时生成的残缺压缩包不得发布。Prisma URL通过进程内解析传给PG工具;Redis备份使用已核验REDIS_HOST/PORT及需要时的受限认证环境,不能假定一定存在REDIS_URL。
发布后两环境服务/API/真实PG/三个业务Stream/HTTP资源通过,监控检查complete=true、pendingReceipts0API重启之外既有PID保持。预生产上游6/6;测试6/0是发布前既有状态。用户提供的现有预生产管理员已完成三尺寸登录后只读验收,取代前一发布记录中的“缺少有效管理员入口”遗留项;保留原有2条规则和3条历史版本,不复制测试规则或演示告警。临时公网浏览器连接关闭经连通性复验后通过,不修改网络/认证配置。完整证据及测试规则v4恢复继承记录见testing-progress.md本轮发布验收节。
## 2026-09-07 夜间累计审核发布补充
功能版本633ba597754c1b89943ea3a819f45042f41b019a于测试23:08:23、预生产23:11:59部署完成;包含WPS修复,新增夜间计数/幂等表及审核续发租约字段,迁移20260907143000_night_sending_risk。仅重启API、发送Worker、报备解析Worker,保留dist内Gateway/安全代理与旧哈希静态文件;恢复点和逐项验收见testing-progress.md本次发布节。
跨发行版保留旧资源不要依赖cp -n的成功退出码,本轮预生产跳过已有文件返回非零导致中断。按已核验的切换位置续接,禁止盲目重跑已迁移/移动资产的整份脚本;用逐项存在判断及最终哈希保证不覆盖新index、保留旧资源和运行二进制。PG工具连接使用内存解析后的标准PG环境变量,排除Prisma专用查询参数,不打印密码。
本次独立备份为测试/var/backups/cmpp-platform/night-risk-20260907T230742和预生产/opt/cmpp-platform-backups/night-risk-20260907T231021;前一轮失败备份目录不算恢复资产。旧API依赖及dist仍在各自/opt/cmpp-night-previous-时间戳,历史logs依赖继续保留。新审核记录和预留状态需结合业务恢复,不能把应用回退当作数据回退。
验收需同时读journald与应用append日志;本轮预生产journal无warning不能掩盖API文件内既有日报事务超时。在线回执可在备份与验收之间自动退款,遇到余额摘要变化应核对实际业务流水、消息和服务时间,不回写余额以强行制造摘要一致。测试安全代理缺少运行目录、预生产日报刷新超时作为独立遗留问题登记。
## 2026-09-08 前端候选安全门禁依赖修复
标准 prepare 的 security:verify 使用 createRequire(api/package.json) 读取 API 本地依赖及 brace-expansion 兼容适配器。仅安装根 node_modules 的前端候选可能向上解析到根依赖,导致安全门禁阻断。现已调整为:impact.frontend 或 impact.api 任一成立时,在独立候选依次执行 npm ci --include=dev、npm --prefix api ci --include=dev,再进入 security:verify 与 deploy:verify;不跳过安全检查,不在运行目录手工安装依赖。
npm run prisma:generate、API 生产构建,以及线上 api/dist、api/node_modules 的切换仍仅由 impact.api 控制。前端变更为安全检查安装的候选 API 依赖不进入线上 API;不因此停止或重启 API/Worker。依赖安装或门禁失败不生成可部署的 candidateHash,后续 deploy 拒绝进入备份和切换。
原测试发布编号 20260908T134432-6816f5617819-b92bbb04 已由标准 status 核验停在 preparebackup=null,尚未备份、停服或切换。工具修复改变 toolHash,须重新生成绑定新摘要的计划并执行 fresh preflight,不修改旧计划或复用失效现场检查;原失败候选和日志保留。
本次补充 4 项发布工具隔离回归:Windows 共 35 项,33 通过、2 项因符号链接权限与 Linux flock 平台限制跳过;Linux 35/35 通过且临时测试夹具残留 0。Linux 日志 SHA-256 为 77106f006cf22ea33c3b744c9c7cbf43d3885b32f4c6d71d8d00b49b1578e900;日志和补丁见本机 %TEMP%/cmpp-starttime-pagination-20260908。隔离测试使用真实临时归档、文件摘要和目录切换,外部命令受控替身仅用于测试,不替代真实发布验收。
授权变更记录:用户随后明确授权先完成测试环境验收,再推送本轮提交并发布预生产;此前“仅测试部署、不推送、不预生产”的表述保留为历史阶段边界。本节记录时业务提交 6816f5617819546850b1188508eda37866f4ca3c 已本地提交、尚未推送;修复工具仍属受保护的未跟踪文件,不据此纳入整套既有发布工具或其他脏文件。后续两环境执行与验收分别追加。
## 2026-09-08 22:35 前端正式构建模式修复
测试环境实际下载的 /assets/index-TLGANFcU.jsSHA-256a8db74bfdb24ef0892ca77fee891297d492d07b979c8653d5fd7c86c186e080c)含 react_stack_bottom_frame 与 jsx-dev-runtime。标准 prepare 把全部命令 NODE_ENV 设为 testVite 以 NODE_ENV===production 判断 isProductionReact 插件沿用该值,因此此前名为 build 的发布产物实际包含开发版 React。必须核验最终产物,不能只凭执行 vite build 推断生产运行时。
本次工具最小修复仅为前端 npm run build 复制独立命令环境并显式设置 NODE_ENV=production;安装、安全/部署门禁、API 的生成/构建仍保持原环境,既有依赖前置与组件切换范围不变。vite.config.ts 同时增加 apply: build 的 configResolved 守卫:isProduction 为假立即拒绝构建,防止其他构建入口再次产出开发版发布包;serve 与 Vitest 不应用该 build 守卫。
真实观测的 18 次通知信号取消发生于可见、在线页面,elapsed 约 3.114ms,均为 AbortError 且栈落在 225:176295 的通知 effect cleanup,不是 15 秒 TimeoutError。146:26427 对应 React 的 commitPassiveUnmountEffectsInsideOfDeletedTree_begin146:25089 对应递归 passive unmount;这是删除树清理路径,不能直接认定所有取消都是 StrictMode 双 effect,也不是另一台电脑 ERR_CONNECTION_CLOSED 的已证实根因。修正正式包后须重新验证资源模式和真实刷新/切页/通知行为。
截至本节,NODE_ENV=test、development 两个真实负例均被新守卫拒绝;默认构建和 lint 正在执行,尚未记为通过。工具 Windows 共 37 项,35 通过、2 项平台限制跳过;新增版本 Linux 37 项结果待返回。新工具摘要和新增 Vite 配置要求重新生成绑定精确提交的计划、执行对应证据与 fresh preflight;本节不表示新版本已部署或业务已验收。证据位于 %TEMP%/cmpp-starttime-pagination-20260908 的 live-react-mode-evidence.json、release-build-mode-fix.patch、build-mode-test.log、build-mode-development.log 及第三轮 browser 目录。
最终检查结果补记(2026-09-08 22:36):默认 npm run build 成功,npm run lint 退出码 0;两个真实 test/development 构建负例准确拒绝。Linux 新版工具 37/37 通过、0 失败/0 跳过,耗时 0.192s,日志 SHA-256 为 305e7df43ff9d6506a755fd8d5e0adb6926e5ee89eb719d8baa015c231a4060b,位于主 TEMP/release-linux-unit-buildmode-2026-09-08T14-34-37-205Z/linux-unittest.log。本结果补齐上文执行中的本地检查;正式候选及真实部署页面仍待验收。