主题
XHIM Server 发布审计门禁
本 Runbook 适用于服务端源码、Migration、Compose 或公开接口变化。一次服务端 任务只有由发布人员或受控编排系统实际执行发布,并取得以下证据后,才可标记为 “已部署且审计通过”:
- 现网 Runtime Observation;
- 加密 Secret/数据库备份、恢复演练和 Migration Readiness;
- 受保护 CI 生成的不可变镜像、SBOM 与 Image Provenance;
- Clean Commit + Secret 连续性 Preflight;
- 受控
server-only或server-and-config发布; - 发布后的 Runtime Observation;
- Revision/Image Digest、健康、公开配置、账号链路与已认证 WSS Upgrade Postflight;
- 目标端真机 SDK 消息回归。
CI 中的 server-release-audit-gate 只测试门禁代码和负向契约,不会自动登录、 修改、重启或部署客户服务器。preflight / postflight 的 passed 只表示提供 给脚本的证据通过审计,不表示发布动作已经发生。没有目标主机生成的 Runtime Observation、实际发布记录和 Postflight 证据时,状态必须写成“代码完成,服务端 未部署”。
安全边界
server_release_gate.py:
observe-runtime只读取部署文件、Docker Compose/Inspect 的受限字段,并在 PostgreSQL 容器中执行固定的只读SELECT version FROM xhim_schema_migrations ORDER BY version;bootstrap-continuity只创建一次不可覆盖的连续性锚点;preflight不修改服务器;postflight只读健康/配置并真实执行携带 Bearer Token 的 WebSocket Upgrade; 当服务端公开声明development_phone_auth_enabled=true时,会自动创建一个合成 Development 账号、强制验证register → password-login,再使用该登录 Token 验证 Upgrade;rollback-preflight只生成一小时有效的应用回滚授权。
脚本不会 SSH、执行 Docker 更新、恢复数据库、执行 Migration 或轮换应用 Secret。它也不会把 .env、手机号、密码、Token、数据库 URL 或明文 Secret 写入证据。
1. 先采集现网,禁止套用示例路径
server-release.example.json 不是可直接部署的配置。先在目标主机用只读命令 确认真实状态:
bash
pwd -P
docker compose ls
docker volume ls
docker ps \
--filter label=com.docker.compose.project \
--format '{{.ID}} {{.Names}} {{.Image}}'随后确认:
- Deployment Root、Shared Directory、Releases Directory;
- 当前
.env、.env.release、Compose、deploy/Caddyfile的真实绝对路径; - Compose Project 与
server、postgres、gatewayService; - PostgreSQL、Media、Caddy 的实际 Docker Volume Name。
当前某个环境即使采用 /www/wwwroot/xhim-server/shared/.env + releases/,也必须现场验证,不能把 该路径当成其他客户环境的默认值。将观测值写入客户专属的 Deployment Manifest; 该文件放受控配置库,不提交客户域名或路径到 SDK 源码。
部署布局支持 Release Directory:
text
<observed-deployment-root>/
├── shared/
│ ├── .server-continuity-anchor.json
│ ├── current -> config-sets/<active-release-id>
│ └── config-sets/
│ └── <release-id>/
│ ├── .env
│ ├── .env.release
│ ├── compose.yaml
│ └── deploy/
│ └── Caddyfile
└── releases/
└── <version>-<full-commit>/Manifest 中 .env、.env.release、compose.yaml 必须位于同一个 Config Set 目录,gateway_config_file 必须是该目录下的 deploy/Caddyfile。四个文件 任一跨目录都会被门禁拒绝。Manifest 还必须显式声明:
continuity_anchor_file:位于 Shared Directory、文件名固定为.server-continuity-anchor.json的一次性连续性锚点;development_phone_auth_required:Development 演示环境必须为true; Production 必须为false。为true时,环境配置、公开能力和真实register → password-login任一步缺失都会拒绝通过。
.env.release 固定只含:
dotenv
XHIM_SERVER_IMAGE=registry.example/xhim/server@sha256:<64-hex-digest>
XHIM_SERVER_VERSION=VERSION
XHIM_SERVER_COMMIT=FULL_COMMIT若既有环境没有 .env.release、OCI Version/Revision Label 或不可追溯的旧镜像, 先完成一次受审批的 Baseline Bootstrap;在基线建立前不能声称支持受控应用 回滚。Production 的 XHIM_SERVER_IMAGE 必须是 Registry image@sha256:<digest>,Runtime Observation 的 RepoDigests 必须非空并包含该 摘要。Tag、空 RepoDigests 或只有本机 Image ID 均不构成可销售环境的发布证据。 Production 的 .env 还必须把 XHIM_POSTGRES_IMAGE 与 XHIM_GATEWAY_IMAGE 配置为不可变 image@sha256;门禁会分别读取三个运行镜像 的 RepoDigests,并要求 Container Image Reference 命中对应摘要。
POSTGRES_PASSWORD=xhim-local-only 只允许本地 Development。Production 必须由 Secret Manager 注入至少 20 字符的独立密码,而且 Server 的 XHIM_DATABASE_URL 中用户、Host、Database 与解码后的密码必须和 PostgreSQL 容器一致。给 Compose 写一个“看起来很强”的新密码但未轮换数据库角色,或 Server 仍使用旧密码时都会失败;既有数据库凭证轮换必须走独立受控流程。
2. Secret 指纹 Key
门禁需要一个独立、持久的 Fingerprint Key。它应由 Secret Manager/KMS 注入为 权限 0600 的临时文件,至少 32 字节。该 Key 只用于:
- HMAC-SHA256 签署发布证据;
- 计算持久 Secret 指纹;
- 比较发布前后 Secret 连续性。
它不是 XHIM Token、Cursor、数据库或 Push 凭证,不得每次发布重新生成。 证据只保存 HMAC 指纹。尤其 XHIM_CURSOR_KEY_BASE64 指纹变化会立即阻断发布, 防止“新账号注册成功”掩盖旧手机号摘要、Cursor 或旧会话全部失效。
3. 发布前 Runtime Observation
bash
XHIM_DEPLOYMENT_MANIFEST=/secure/xhim/deployment.json
XHIM_FINGERPRINT_KEY=/run/secrets/xhim-release-fingerprint-key
XHIM_EVIDENCE_DIR=/secure/xhim/release-evidence
install -d -m 0700 "$XHIM_EVIDENCE_DIR"
scripts/deploy/server_release_gate.py observe-runtime \
--deployment-manifest "$XHIM_DEPLOYMENT_MANIFEST" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--output "$XHIM_EVIDENCE_DIR/runtime-before.json"Observation 会绑定:
- Deployment/Shared/Releases 和四个部署文件;
- 四个部署文件经过父目录 Symlink 解析后的真实 Release 路径;
- Compose Project、Service、逻辑 Volume 与实际 Runtime Volume Name;
- 当前
.env.release声明; server、postgres、gateway三个 Service 的 Container ID、Image ID、 Image Reference、RepoDigests、唯一 Compose Network 和完整 Mount Type/Source/Destination/RW;Server 额外 Host Bind 或外部 Network 会直接失败;- 三个 Container 使用的 Image ID Content Digest 与非空 RepoDigests;
- 实际 OCI Version/Revision Label;
- 在线
xhim_schema_migrations的完整有序版本集合及其 SHA-256; current_database()与 PostgreSQL Clustersystem_identifier组成的数据库 身份 SHA-256;备份 Metadata 必须与该身份一致,避免拿同版本/同 Schema 的 另一客户数据库冒充现网备份;.env文件 HMAC 与持久 Secret HMAC 指纹。
它不会执行不受限的 docker inspect,避免把 Container Environment 输出到日志; 数据库观测也只执行上述固定 SELECT,不会输出数据库 URL 或业务数据。
3.1 仅一次的 Continuity Bootstrap
新环境在第一次普通 Preflight 前,必须用已审批、已签名的 Runtime Observation 建立一次连续性锚点:
bash
scripts/deploy/server_release_gate.py bootstrap-continuity \
--deployment-manifest "$XHIM_DEPLOYMENT_MANIFEST" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--runtime-observation-file "$XHIM_EVIDENCE_DIR/runtime-before.json" \
--bootstrap-approval-id CHANGE-APPROVAL-2026-001 \
--output \
/REPLACE_WITH_OBSERVED_DEPLOYMENT_ROOT/shared/.server-continuity-anchor.json输出必须与 Manifest 的 continuity_anchor_file 完全相同。锚点应由发布账号以 0600 创建,并进入不可覆盖、可审计的备份;文件已存在、Symlink、审批号缺失 或 Fingerprint Key 改变时 Bootstrap 必须失败。它不能作为“修复连续性失败”的 重试开关,也不能在 Cursor、签名或数据库等持久身份变化后重新初始化。合法的 凭证轮换必须走单独的轮换方案、双 Key 兼容期和审批,不属于普通发布。
本地 O_EXCL 只能防覆盖,无法阻止拥有目标主机 Root 权限的人删除锚点。商用 环境必须把锚点 Hash、审批号和第一次 Postflight 写入外部不可变/WORM 审计存储, 并让后续发布强制提供上一份 Postflight。锚点或外部 Adoption Receipt 任一缺失时 必须停止发布并人工核查,禁止通过“再跑一次 bootstrap”恢复绿色状态;本脚本不 把普通磁盘文件伪装成防 Root 删除的可信账本。
4. Preflight
本节先说明 Preflight 接口,但真实执行顺序必须是: Runtime Observation → 第 5 节备份/恢复与 Migration Readiness → 第 6 节镜像 构建/推送与 Image Provenance → Preflight → 第 7 节发布。不得先生成空白证据 通过 Preflight,再补做备份或构建。
4.1 三份必需的受保护候选文件
每次发布都必须准备以下三个绝对路径、0600、非 Symlink、未被 Git 跟踪的 Regular File。它们只能由 Protected CI Runner 或受控运维作业生成和传递,不能 由开发者手填后上传普通 Artifact:
candidate.env.release:严格的 Dotenv 文件,只含XHIM_SERVER_IMAGE、XHIM_SERVER_VERSION、XHIM_SERVER_COMMIT。它由 Protected Image Job 在 Registry 推送、摘要解析完成后生成;image-provenance.json:Schemacom.xihansoftware.xhim.server-image-provenance.v1。它由 Protected CI Image Job 根据 Clean Commit、git rev-parse <commit>:server、Registry Digest、实际 Image ID、SBOM SHA-256 和镜像签名验证结果生成;migration-readiness.json:Schemacom.xihansoftware.xhim.server-migration-readiness.v1。它由受保护的数据库 运维作业根据本次 Runtime Observation 的在线 Migration 集合、候选 Migration Tree、加密备份校验、隔离 Restore Drill 和 N/N-1 双向兼容结果生成。
后两份 JSON 必须用第 2 节同一个受保护 Fingerprint Key 对 Canonical JSON 做 HMAC-SHA256,写入 evidence_hmac_sha256;Preflight 会验签并把证据 Hash 写入 自己的签名结果。signature_verified=true 还表示 Registry 中候选镜像的供应链 签名已经由 Protected Image Job 独立验证,它不能由发布人员手工改成 true。
Image Provenance 的必需结构是:
json
{
"schema": "com.xihansoftware.xhim.server-image-provenance.v1",
"schema_version": 1,
"product": "XHIM",
"status": "attested",
"created_at_utc": "2026-07-29T00:00:00Z",
"source": {
"commit": "<full-clean-commit>",
"version": "<semver>",
"server_tree": "<git-tree-object-id>"
},
"image": {
"reference": "registry.example/xhim/server@sha256:<64-hex>",
"repository_digest": "registry.example/xhim/server@sha256:<same-64-hex>",
"image_id": "sha256:<64-hex>",
"sbom_sha256": "<64-hex>",
"signature_verified": true
},
"evidence_hmac_sha256": "<protected-job-signature>"
}Migration Readiness 的必需结构是:
json
{
"schema": "com.xihansoftware.xhim.server-migration-readiness.v1",
"schema_version": 1,
"product": "XHIM",
"status": "passed",
"checked_at_utc": "2026-07-29T00:00:00Z",
"source_commit": "<full-clean-commit>",
"candidate_files": {
"0001_core.sql": "<file-sha256>"
},
"candidate_tree_sha256": "<canonical-tree-sha256>",
"baseline_applied_migrations": [
"0001_core.sql"
],
"baseline_schema_sha256": "<online-migration-set-sha256>",
"backup": {
"bundle_directory": "/secure/.../postgres-backup",
"metadata_sha256": "<actual-backup-metadata-sha256>",
"encrypted_artifact_file": "/secure/.../postgres-backup.tar.age",
"encrypted_artifact_sha256": "<actual-encrypted-file-sha256>",
"encryption_report_file": "/secure/.../backup-encryption-report.json",
"encryption_report_sha256": "<actual-report-sha256>"
},
"restore_drill": {
"evidence_file": "/secure/.../restore-drill-report.json",
"evidence_sha256": "<restore-report-sha256>"
},
"compatibility": {
"declaration_file": "/secure/.../postgres-compatibility.json",
"declaration_sha256": "<declaration-sha256>",
"test_report_file": "/secure/.../n-minus-one-run-report.json",
"test_report_sha256": "<actual-run-report-sha256>"
},
"evidence_hmac_sha256": "<protected-job-signature>"
}Readiness 必须绑定完整候选 Migration 文件 Hash 和 Observation 中的完整在线集合, 且生成时间不得超过七天。门禁会实际打开以上绝对路径:备份目录必须为私有真实 目录且只含 xhim-postgres.dump、backup-metadata.json、SHA256SUMS; 大 Dump 和加密制品采用流式 SHA-256,不会整体载入内存;JSON、权限、实际 Hash、 基线 Version/Schema、恢复表集合和候选 Version/Schema 任一不一致都会失败。
postgres-compatibility.json 只是带有效期的兼容声明,不能冒充运行证明。 n-minus-one-run-report.json 必须是 Schema com.xihansoftware.xhim.postgres-n-minus-one-verification.v1 的实际受保护运行 报告,绑定本次 Commit、N/N-1 版本、候选 Schema,并逐项记录 N、N-1 读写、 Ready、已认证 WSS 与消息幂等回归。没有该报告时正式 Preflight 必须失败。 以上 JSON 只是 Schema 说明,不是允许复制修改的发布模板;正式文件必须来自 对应的 Protected CI/运维作业。
加密验证报告必须是 Canonical JSON,且 method 只能为 age 或 kms-envelope:
json
{
"schema": "com.xihansoftware.xhim.postgres-backup-encryption-verification.v1",
"schema_version": 1,
"product": "XHIM",
"status": "verified",
"verified_at_utc": "2026-07-29T00:00:00Z",
"method": "age",
"storage_reference": "vault-or-object-version-id",
"backup_bundle_sha256": "<canonical-three-file-set-sha256>",
"encrypted_artifact_sha256": "<actual-encrypted-file-sha256>"
}N/N-1 实际运行报告的 checks 必须精确包含以下五项且全部为 true:
json
{
"schema": "com.xihansoftware.xhim.postgres-n-minus-one-verification.v1",
"schema_version": 1,
"product": "XHIM",
"status": "passed",
"checked_at_utc": "2026-07-29T00:00:00Z",
"source_commit": "<full-clean-commit>",
"from_server_version": "<baseline-semver>",
"to_server_version": "<candidate-semver>",
"database_schema_version": "<candidate-latest-migration.sql>",
"checks": {
"release_n_read_write": true,
"release_n_minus_one_read_write": true,
"ready": true,
"authenticated_websocket": true,
"message_idempotency": true
}
}普通发布必须引用上一次成功 Postflight,使 Secret 和现网镜像形成连续链:
bash
XHIM_VERSION=0.1.0-dev.4
XHIM_COMMIT="$(git rev-parse HEAD)"
XHIM_CANDIDATE_RELEASE_ENV=/run/xhim-release/candidate.env.release
XHIM_IMAGE_PROVENANCE=/run/xhim-release/image-provenance.json
XHIM_MIGRATION_READINESS=/run/xhim-release/migration-readiness.json
scripts/deploy/server_release_gate.py preflight \
--repository-root "$PWD" \
--expected-commit "$XHIM_COMMIT" \
--expected-version "$XHIM_VERSION" \
--deployment-manifest "$XHIM_DEPLOYMENT_MANIFEST" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--baseline-runtime-observation \
"$XHIM_EVIDENCE_DIR/runtime-before.json" \
--previous-postflight-file \
"$XHIM_EVIDENCE_DIR/previous-postflight.json" \
--deployment-mode server-only \
--candidate-release-env-file "$XHIM_CANDIDATE_RELEASE_ENV" \
--image-provenance-file "$XHIM_IMAGE_PROVENANCE" \
--migration-readiness-file "$XHIM_MIGRATION_READINESS" \
--output "$XHIM_EVIDENCE_DIR/preflight.json"首次引入门禁没有 Previous Postflight 时,只能引用第 3.1 节已经创建的一次性 Continuity Anchor;普通 Preflight 不提供重新初始化连续性的参数。第一次成功 Postflight 之后,后续发布必须引用上一份成功 Postflight。
模式:
server-only:候选 Compose Hash 必须与现网一致,只允许更新 Server Image。 发布前后 PostgreSQL/Gateway 容器 ID 和 Image ID、Compose Network ID/集合, 以及 Server/PostgreSQL/Gateway 的 Mount Source/Destination/RW 属性必须完全 不变;任何依赖容器被重建、镜像漂移、Network 重建或 Mount 漂移都拒绝通过;server-and-config:允许把仓库内候选server/compose.yaml作为本次受控 Compose 变更,并通过--candidate-runtime-env-file接收 Secret Manager 导出的0600、未被 Git 跟踪的候选运行配置。Preflight 同时记录旧/新 Compose Hash 与.envHMAC,Postflight 强制验证新值。
普通 server-and-config 只允许修改以下运行参数;新增、删除或修改其他任何 环境变量都必须拒绝,并改走对应的网络、身份、凭证、存储或数据迁移专项变更:
text
XHIM_REQUEST_RATE
XHIM_REQUEST_BURST
XHIM_MAX_WEBSOCKETS
XHIM_SHUTDOWN_TIMEOUT
XHIM_ENDPOINT_LIFETIME
XHIM_MINIMUM_CLIENT_PROTOCOL_VERSION
XHIM_MULTI_LOGIN_POLICY
XHIM_MAX_SESSIONS_PER_USER
XHIM_MAX_BURN_AFTER_READ
XHIM_BURN_WORKER_BATCH_SIZE
XHIM_BURN_WORKER_IDLE_INTERVAL
XHIM_MEDIA_AUTH_LIFETIME
XHIM_PUSH_REQUEST_TIMEOUT
XHIM_PUSH_BATCH_SIZE
XHIM_PUSH_MAX_ATTEMPTS
XHIM_PUSH_LEASE_DURATION
XHIM_PUSH_IDLE_INTERVAL
XHIM_PUSH_MAX_DEVICES_PER_USER
XHIM_PUSH_DELIVERY_CONCURRENCY
XHIM_WEBHOOK_REQUEST_TIMEOUT
XHIM_WEBHOOK_BATCH_SIZE
XHIM_WEBHOOK_MAX_ATTEMPTS
XHIM_WEBHOOK_LEASE_DURATION
XHIM_WEBHOOK_IDLE_INTERVAL
XHIM_WEBHOOK_BASE_BACKOFF
XHIM_WEBHOOK_MAX_BACKOFF
XHIM_WEBHOOK_DELIVERY_CONCURRENCY
XHIM_RTC_TOKEN_LIFETIME尤其域名/Public URL、数据库地址、对象存储、环境类型、Development Login、 签名 Key ID、Verify Keys、Token Issuer/Audience 和所有持久 Secret 都不在普通 Allowlist。凭证或身份轮换是另一套审批流程,不能夹带在普通配置发布中。
Preflight 拒绝 Dirty Worktree、Commit/Version 错配、过期或被篡改的 Runtime Observation、路径/Project/Volume 漂移、Secret 指纹断链以及未审批的 Compose 变化;候选 .env.release 与 Image Provenance 不一致、Image/SBOM/签名来源 无法验签,或 Migration Readiness 与候选 Tree/在线 Schema/备份恢复/N/N-1 证据不一致时同样 Fail Closed。
5. 加密备份
.env 禁止明文复制到 /var/backups 或普通 CI Artifact。先配置一个经过审批的 Age Recipient,或使用客户 Vault/KMS 的版本化 Secret Backup。Age 示例:
bash
XHIM_RUNTIME_ENV='REPLACE_WITH_OBSERVED_RUNTIME_ENV_PATH'
XHIM_RELEASE_ENV='REPLACE_WITH_OBSERVED_RELEASE_ENV_PATH'
XHIM_DEPLOYED_COMPOSE='REPLACE_WITH_OBSERVED_COMPOSE_PATH'
XHIM_GATEWAY_CONFIG='REPLACE_WITH_OBSERVED_CADDYFILE_PATH'
XHIM_ENCRYPTED_BACKUP_ROOT='REPLACE_WITH_ENCRYPTED_BACKUP_MOUNT'
XHIM_RELEASE_ID='VERSION-FULL_COMMIT'
XHIM_BACKUP_SET="$XHIM_ENCRYPTED_BACKUP_ROOT/$XHIM_RELEASE_ID"
XHIM_AGE_RECIPIENTS=/secure/xhim/backup-recipients.txt
test ! -e "$XHIM_BACKUP_SET"
install -d -m 0700 "$XHIM_BACKUP_SET"
age --encrypt \
--recipients-file "$XHIM_AGE_RECIPIENTS" \
--output "$XHIM_BACKUP_SET/runtime-env.age" \
"$XHIM_RUNTIME_ENV"
install -m 0600 \
"$XHIM_RELEASE_ENV" \
"$XHIM_BACKUP_SET/.env.release"
install -m 0600 \
"$XHIM_DEPLOYED_COMPOSE" \
"$XHIM_BACKUP_SET/compose.yaml"
install -d -m 0700 "$XHIM_BACKUP_SET/deploy"
install -m 0600 \
"$XHIM_GATEWAY_CONFIG" \
"$XHIM_BACKUP_SET/deploy/Caddyfile"XHIM_ENCRYPTED_BACKUP_ROOT 必须是加密、访问受控且有保留策略的目标。若使用 Vault/KMS,不生成 runtime-env.age,而是把版本化 Secret Reference 和审计 ID 写入发布记录。
PostgreSQL Dump 同样包含敏感数据,只能写入上述加密目标:
bash
export XHIM_POSTGRES_SOURCE_URL='<由 Secret Manager 注入>'
server/scripts/postgres/backup.sh \
--output-dir "$XHIM_BACKUP_SET/postgres" \
--server-version 'CURRENT_ONLINE_VERSION'
server/scripts/postgres/verify_backup.sh \
--bundle-dir "$XHIM_BACKUP_SET/postgres" \
--expected-server-version 'CURRENT_ONLINE_VERSION' \
--expected-schema-version 'CURRENT_ONLINE_MIGRATION'
tar --create --file=- \
--directory "$XHIM_BACKUP_SET" postgres |
age --encrypt \
--recipients-file "$XHIM_AGE_RECIPIENTS" \
--output "$XHIM_BACKUP_SET/postgres-backup.tar.age"正式环境还必须在隔离空库完成 Restore Drill。备份、Hash、TOC、恢复演练任一 失败即停止发布。受保护作业还必须对真实 postgres-backup.tar.age 做解密校验, 把三文件集合 Hash、加密文件 Hash、Method 和不可变 Storage Reference 写入上节 加密验证报告;只填写 encrypted=true 或虚构 Hash 不能通过门禁。
5.1 Migration 与 Schema 证据
发布记录必须绑定同一 Commit 的完整 Migration Tree,而不是只记录“最新文件名”:
bash
find server/internal/adapters/postgres/migrations \
-type f -name '*.sql' -print0 |
sort -z |
xargs -0 sha256sum \
>"$XHIM_EVIDENCE_DIR/migration-tree.sha256"同时保存并交叉验证:
- 发布 Commit 中按顺序、无缺号/重名的 Migration Tree 与整体 Hash;
- 备份前、Migration 后和 Postflight 时从在线库读取的真实 Schema Version;
backup-metadata.json、SHA256SUMS、TOC 校验及隔离空库 Restore Drill 报告;postgres-compatibility.json中 Release N、N-1、目标 Schema 和未过期 兼容窗口;rolling_upgrade_preflight.sh结果,以及 N 和 N-1 在目标 Schema 上实际 完成读写、Ready、WSS 与消息幂等回归的证据。
Migration Tree 或在线 Schema 与候选 Commit 不一致、恢复演练缺失、N/N-1 证据缺失或兼容窗口过期时,禁止更新任何 Server 副本。即使本次没有新增 Migration,也要记录“Tree 未变化”和发布前后在线 Schema 相同的证据。具体备份、 恢复及兼容性工具见 server/scripts/postgres/README.md。 受保护数据库运维作业把这些来源证据的 SHA-256 和双向兼容结论写入第 4.1 节的 migration-readiness.json 并完成 HMAC 签名;普通 CI Job 不能自行制造 status=passed。
6. 构建不可变镜像
bash
XHIM_IMAGE_TAG="registry.example/xhim/server:${XHIM_VERSION}-${XHIM_COMMIT}"
docker build --pull \
--build-arg XHIM_VERSION="$XHIM_VERSION" \
--build-arg XHIM_COMMIT="$XHIM_COMMIT" \
--tag "$XHIM_IMAGE_TAG" \
server推送后从 Registry 读取不可变摘要并用于部署:
bash
docker push "$XHIM_IMAGE_TAG"
XHIM_IMAGE="$(
docker image inspect "$XHIM_IMAGE_TAG" \
--format '{{index .RepoDigests 0}}'
)"
test -n "$XHIM_IMAGE"Production .env.release 只能写上述 image@sha256:<digest>。latest、只有 Tag、 空 RepoDigests、只有本机 Image ID,或缺少 OCI Revision Label 的镜像不能通过 Postflight。
Protected Image Job 随后必须验证镜像供应链签名、生成或验证 SBOM、读取实际 Image ID 与 Repo Digest,并按第 4.1 节生成 HMAC 签名的 image-provenance.json 和严格三字段的 candidate.env.release。二者必须引用 同一个 image@sha256;在这两份受保护文件生成前不能执行 Preflight。
门禁会校验 Provenance 的 HMAC、Commit/Server Tree、Image Digest/Image ID 与 SBOM Hash,但不会在目标主机重新登录 Registry、运行 Cosign 或解析整份 SBOM。 因此 signature_verified=true 是受信 Protected Image Job 的断言,不是本脚本 本地完成的独立验签。该 Job 的权限必须与普通开发者隔离,并把实际 SBOM、签名 验证报告、Issuer/Subject、工具版本和 Registry 不可变引用保存到供应链审计存储; 缺少这些外部制品时只能说“Provenance 断言已验签”,不能声称供应链独立审计完成。
7. 受控发布
server-only 不修改 Runtime Env 或 Compose;server-and-config 只能安装 Preflight 已绑定的候选 Runtime Env 和 Compose。两种模式都不能轮换 Secret 或 身份,也不能用三个彼此独立的 mv 制造“部分新、部分旧”的配置。
把 .env、.env.release、compose.yaml 和 deploy/Caddyfile 放入同一个 不可变版本化 Config Set。先在部署锁内完成候选目录写入、权限与 Preflight Hash 校验,再用同文件系统上的 Rename 原子切换 shared/current Symlink。Manifest 的四个部署文件路径都必须通过 shared/current/ 指向同一个 Config Set:
bash
XHIM_SHARED='REPLACE_WITH_OBSERVED_SHARED_DIRECTORY'
XHIM_RELEASE_ID="${XHIM_VERSION}-${XHIM_COMMIT}"
XHIM_CONFIG_SET="$XHIM_SHARED/config-sets/$XHIM_RELEASE_ID"
XHIM_CURRENT_NEXT="$XHIM_SHARED/.current.next"
XHIM_CANDIDATE_RUNTIME_ENV=/run/secrets/xhim-candidate-runtime-env
XHIM_CANDIDATE_RELEASE_ENV=/run/xhim-release/candidate.env.release
test ! -e "$XHIM_CONFIG_SET"
test ! -e "$XHIM_CURRENT_NEXT"
umask 077
install -d -m 0700 "$XHIM_CONFIG_SET"
# server-only 必须复制当前文件且内容不变;server-and-config 才使用经过
# Preflight 的候选 Runtime Env 和候选 Compose。
install -m 0600 "$XHIM_SHARED/current/.env" "$XHIM_CONFIG_SET/.env"
install -m 0600 \
"$XHIM_SHARED/current/compose.yaml" \
"$XHIM_CONFIG_SET/compose.yaml"
install -d -m 0700 "$XHIM_CONFIG_SET/deploy"
install -m 0600 \
"$XHIM_SHARED/current/deploy/Caddyfile" \
"$XHIM_CONFIG_SET/deploy/Caddyfile"
install -m 0600 \
"$XHIM_CANDIDATE_RELEASE_ENV" \
"$XHIM_CONFIG_SET/.env.release"server-and-config 应把上面复制的三个文件替换为同一次 Preflight 已绑定的 候选内容;随后重新核对 .env HMAC、Compose SHA-256、.env.release SHA-256、Caddyfile SHA-256、Commit/Version/Image Digest 和文件权限。发布时 必须安装 Preflight 使用的原始 candidate.env.release,不能重新拼接一个 “看起来相同”的文件。 示意:
bash
install -m 0600 \
"$XHIM_CANDIDATE_RUNTIME_ENV" \
"$XHIM_CONFIG_SET/.env"
install -m 0600 \
server/compose.yaml \
"$XHIM_CONFIG_SET/compose.yaml"
install -d -m 0700 "$XHIM_CONFIG_SET/deploy"
install -m 0600 \
server/deploy/Caddyfile \
"$XHIM_CONFIG_SET/deploy/Caddyfile"四个文件完整落盘并校验后,执行 sync -f "$XHIM_CONFIG_SET",创建指向新 Config Set 的相对 Symlink,并用 rename(2) 语义替换 shared/current。该过程必须持有 唯一部署锁;不支持原子 Symlink Replace 的编排器不得执行 server-and-config。切换完成后只更新 Server Service:
bash
ln -s "config-sets/$XHIM_RELEASE_ID" "$XHIM_CURRENT_NEXT"
mv -Tf "$XHIM_CURRENT_NEXT" "$XHIM_SHARED/current"
docker compose \
--project-name 'REPLACE_WITH_OBSERVED_PROJECT' \
--env-file "$XHIM_SHARED/current/.env" \
--env-file "$XHIM_SHARED/current/.env.release" \
--file "$XHIM_SHARED/current/compose.yaml" \
up -d --no-deps server禁止 docker compose down、删除 Volume 或在同一命令重建 PostgreSQL/Gateway。 up -d --no-deps server 本身不是“依赖未变化”的证明;发布后 Observation 还 必须对比 PostgreSQL/Gateway 容器与 Image、Compose Network 和全部 Mount 身份。
8. 发布后 Observation 与 Postflight
先重新执行 observe-runtime,输出 runtime-after.json。Development 会从自动 register → password-login 探针取得短期 Token,不需要另外提供 Token 文件:
bash
scripts/deploy/server_release_gate.py postflight \
--mode rollout \
--preflight-file "$XHIM_EVIDENCE_DIR/preflight.json" \
--deployment-manifest "$XHIM_DEPLOYMENT_MANIFEST" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--runtime-observation-file "$XHIM_EVIDENCE_DIR/runtime-after.json" \
--output "$XHIM_EVIDENCE_DIR/postflight.json"Test/Production 没有 Development 登录接口,必须由客户认证服务或受保护运维作业 签发一个最小权限、短期、可撤销的用户 Access Token,并以绝对路径 0600 Regular File 注入。文件只含 Token 本身,不带 Bearer 前缀;Postflight 不会 把 Token 写入证据:
bash
XHIM_WSS_PROBE_TOKEN=/run/secrets/xhim-wss-probe-token
scripts/deploy/server_release_gate.py postflight \
--mode rollout \
--preflight-file "$XHIM_EVIDENCE_DIR/preflight.json" \
--deployment-manifest "$XHIM_DEPLOYMENT_MANIFEST" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--runtime-observation-file "$XHIM_EVIDENCE_DIR/runtime-after.json" \
--websocket-probe-token-file "$XHIM_WSS_PROBE_TOKEN" \
--output "$XHIM_EVIDENCE_DIR/postflight.json"Postflight 必须同时观察到:
.env.releaseCommit/Version;- Container 实际 OCI Revision/Version;
- Image ID Content Digest;
- Candidate Compose Hash;
- 原 Project/Volume;
- 三个 Service 的 Container/Image/Network/Mount 观测;
server-only下不变的依赖容器/Image、Network 和 Mount;- 在线
xhim_schema_migrations与已签名 Migration Readiness; - 相同持久 Secret HMAC;
/health/ready、/v1/sdk/config的 Version/Protocol;- 使用公开 WebSocket URL、真实 TLS 校验和 Bearer Token 获得
101,且Upgrade、Sec-WebSocket-Accept与Sec-WebSocket-Protocol: xhim.v1响应正确。
只把 Preflight 中的 Expected Commit 抄进报告不算验证。证据中的 deployed_revision_verified=true 仅在上述 Runtime Observation 完整匹配后出现。
Manifest 的 development_phone_auth_required=true 时,Runtime Env 必须配置 Development Phone Auth,公开配置必须返回 development_phone_auth_enabled=true,注册登录探针必须自动执行且成功;把 能力关闭或返回 false 不是跳过测试,而是发布失败。Production 的 Manifest 必须为 false,服务端也不得公开该能力。
HTTP 与已认证 WSS Upgrade Postflight 通过仍不是完整的 IM 验收。发布记录还 必须附上:
- 至少两个账号在目标端正式 SDK 制品上完成登录、Bootstrap/Sync、A 发 B 收、 ACK/服务端序号、Ping/Pong、Sync Hint、已读同步和重新连接后的补偿同步;
- 本次销售/交付覆盖的每个目标平台均有对应 SDK 版本、设备/系统版本、时间和 结果;客户端与服务端证据必须绑定同一 Server Version/Commit。
审计脚本会真实完成“认证成功的 WebSocket Upgrade”,不再只验证 HTTP;但它 不会发送完整 IM Frame,也不会代替真机上的消息、同步和重连回归。完整 SDK 验收仍由发布人员或测试编排执行并上传证据。
9. 受控应用回滚
回滚不复用普通 Rollout 的 24 小时 Preflight Age。先用上一次成功发布的 Preflight/Postflight 和 PostgreSQL Compatibility 声明生成一小时授权:
bash
scripts/deploy/server_release_gate.py rollback-preflight \
--repository-root "$PWD" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--target-preflight-file "$XHIM_EVIDENCE_DIR/old-preflight.json" \
--target-postflight-file "$XHIM_EVIDENCE_DIR/old-postflight.json" \
--compatibility-file /secure/xhim/postgres-compatibility.json \
--output "$XHIM_EVIDENCE_DIR/rollback-authorization.json"通过后:
server-only回滚切回旧 Image Digest,但仍要求 Runtime Env/Compose 与 依赖容器/Image、Network、Mount 不变;server-and-config必须从同一个加密备份集恢复完整旧 Config Set,并在 一个部署锁内原子切回旧.env、旧 Compose、旧deploy/Caddyfile和旧.env.release;禁止只恢复其中一项后启动 Server;- 仍只执行
up -d --no-deps server; - 重新执行
observe-runtime; - 执行回滚 Postflight,并重新完成 HTTP、已认证 WSS Upgrade 与 SDK 验收。 下面是 Test/Production 示例,因此必须再次提供受保护 Token 文件; Development 才可以省略该参数并使用自动注册登录 Token:
bash
scripts/deploy/server_release_gate.py postflight \
--mode rollback \
--rollback-authorization-file \
"$XHIM_EVIDENCE_DIR/rollback-authorization.json" \
--preflight-file "$XHIM_EVIDENCE_DIR/old-preflight.json" \
--deployment-manifest "$XHIM_DEPLOYMENT_MANIFEST" \
--fingerprint-key-file "$XHIM_FINGERPRINT_KEY" \
--runtime-observation-file "$XHIM_EVIDENCE_DIR/runtime-after-rollback.json" \
--websocket-probe-token-file /run/secrets/xhim-wss-probe-token \
--output "$XHIM_EVIDENCE_DIR/rollback-postflight.json"回滚授权只允许旧应用镜像,不授权数据库恢复或密钥轮换。数据库保留已经执行的 前向 Schema;Compatibility Gate 失败时停止自动回滚,进入暂停写入与前向修复 流程。
10. 完成状态
已部署且审计通过:人工/受控编排已实际发布,备份、两次 Observation、 Preflight、Postflight、WSS 和目标端真机 SDK 回归齐全;代码完成,服务端未部署:没有真实服务器发布或 Runtime Observation;发布失败并已回滚:Rollback Authorization 与 Rollback Postflight 通过;发布失败且回滚受阻:停止自动操作并升级事故响应。
不存在“后台本地改完即默认部署”的状态。