Smail/docs/部署后自动归档与证书保留.md
smail 9c450f1734
Some checks are pending
Security Checks / security (push) Waiting to run
运行时配置清理阶段检查点
2026-10-05 01:31:34 +08:00

134 lines
12 KiB
Markdown
Raw Permalink 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.

# 部署后自动归档与证书保留
## 已确认要求
- 正式程序在任意部署目录启动后自动创建目录,不依赖手动整理或开发脚本。
- 已采用 `config/ssl/data/log` 布局的旧部署,启动时自动归档三把业务密钥,保留原字节。
- 不再创建 `ssl/managed`。自动签发的成对文件直接存放于 `ssl/`,保留当前新证书和上一套未过期证书,上一套到期后清理。
- 域名证书原文件继续保存在 `ssl/` 根目录,不与业务加密密钥混放。
- 模拟部署使用 `yunx-test/`,修改完成后必须完整构建并核验最新二进制。
## 程序创建的目录
```text
部署目录/
├── config/ # 初始化完成后的五个生效 JSON
├── ssl/
│ ├── credentials/
│ │ └── credential.key
│ ├── external-email-encryption/
│ │ └── external-email-encryption.key
│ ├── external-email-fingerprint/
│ │ └── external-email-fingerprint.key
│ ├── dkim/
│ ├── smail-tls-<版本>.crt # 自动签发的当前或上一套证书
│ ├── smail-tls-<版本>.key
│ ├── 域名.crt
│ └── 域名.key
├── data/
├── log/
├── smail
└── smail-admin
```
目录由主服务启动入口 `InitializeDeployment` 创建;密钥由对应业务首次需要时创建,不能为凑齐文件生成无用密钥。Windows 二进制带 `.exe` 后缀。初始化未完成时不会伪造五个完成态配置文件。
三个目录分别保存配置凭据加密密钥、外部邮箱加密密钥、外部邮箱检索指纹密钥。ACME 账户密钥的进一步归档不在本次三个业务目录改动中,原文件继续保留。
## 启动迁移
启动先检查再移动:已初始化部署缺少密钥或数据库、新旧密钥重复、密钥不是 32 字节、存在链接或无效目标目录时拒绝继续,不覆盖、不重新生成替代密钥。Linux 还检查密钥文件权限。
有效的 `ssl/credential.key` 等旧文件使用重命名归档到业务目录,内容不变,因此旧配置密文、外部邮箱密文及指纹仍有效。每次移动后同步目录;迁移中断后允许下次启动继续,重复启动不会再次生成密钥。
更早的 `config/` 或 `data/` 混放部署仍须执行原有完整迁移流程,不能只搬三把密钥而遗漏数据库、证书和历史引用。
## 两份保留规则
- 一份是完整的证书和私钥组合,不是两个单独文件。
- 当前生效证书优先保留;提交新版本时记录上一份完整且未过期的材料,不按文件时间补足两份,也不保留更早的历史链。
- 待应用候选、正在导入或续期的材料、磁盘已提交引用受到保护;操作进行时可能暂时超过两份,应用或取消后再次清理。
- 启动后、定期证书维护、配置应用、回滚、取消候选及重新加载时自动执行。
- 仅删除程序识别的历史版本。未知文件、链接、无法安全检查的目录保留并告警;事务恢复未完成时暂停清理。
- 私钥文件锁保护跨进程暂存,配置状态锁协调提交与清理。停止或替换管理器时释放暂存锁。
- 每分钟检查上一套的到期时间;先通过五分区配置事务清空引用,再删除程序生成的成对文件。当前材料不可用或事务未恢复时暂停回收。
- 已落盘但运行态发布失败的清理事务保留恢复入口,下轮维护继续发布,不重复提交同一代次。
- 成对清理先删除私钥再删除证书;中断遗留的已识别孤立证书在下轮重试,无法解析或配对的材料不擅自删除。
- 已清理证书的历史配置不能回滚,预览和执行均返回明确中文错误,必须重新导入有效证书。
## 域名文件与运行引用的边界
`ssl/域名.crt`、`ssl/域名.key` 是用户放置的证书原文件,自动归档和清理不删除它们。
自定义证书直接保存原始路径,不复制。部署目录内优先使用 `ssl/域名.crt`、`ssl/域名.key`,部署目录外保留绝对路径;普通文件、链接、大小、私钥权限、强度、有效期、配对及服务域名校验仍执行。
启动时自动迁移旧 `managed` 引用:自定义模式优先复用字节完全相同的根目录原件;没有匹配原件时将必要材料迁到根目录程序版本文件。先提交配置事务,再回收已退出引用的旧目录;未知文件不会删除。
自动续期先写一套独立新文件,完整校验后事务切换当前引用。SMTP、IMAP、POP3、内置 HTTPS 共享动态证书,新 TLS 握手读取新证书,已有连接不主动关闭。新材料加载失败时仅回退到仍有效的已校验证书;重启无内存缓存时尝试配置中的上一套。
手工证书不受自动删除管理,也不自动复制一份历史。外部维护者应先准备好完整的新材料再更新引用;分别覆盖证书与私钥期间验证失败时进程保留有效缓存,但不保证此时重启仍能加载被覆盖的旧材料。
## 验证与边界
- 自动化回归覆盖新建目录、旧密钥字节不变、中断续迁、重复启动、冲突拒绝、跨模式总数两份、暂存保护、未知文件保护、过期前版本、旧 `current` 清理及历史回滚拒绝。
- `test/deployment/verify_ssl_startup.py` 在原前后端停服且最新包已部署后运行,将原 `yunx-test` 暂存至 `test-log`,用同一二进制在空 `yunx-test` 正式启动两次,再恢复原部署。所有测试产物保留,不删除业务库。
- 空目录正式启动只验收自动目录创建和初始化页面可达,不等于完成整个首次初始化、管理员恢复权限或插件生产隔离验收。
- Linux 编译通过不等于 Linux 生产运行验收。正式管理员恢复通道的保护不降低。
## 本轮实测记录(2026-09-09)
| 验证项目 | 本轮结果 | 记录位置 |
| --- | --- | --- |
| 完整自动化回归 | 前端 155/155、部署 4/4、Go 全量通过 | `test-log/run-2026-09-09T09-12-56-738Z-9840/` |
| 专项并发检查 | 配置、SSL 设置及 HTTP、SMTP、IMAP、POP3 监听包通过 `-race` | `test-log/run-2026-09-09T09-04-43-786Z-46884/` |
| 证书生命周期 | 当前加上一套保留、到期事务清理、发布失败恢复、暂存租约、未知材料保护、孤立证书重试通过 | `test/backend/ssl_lifecycle_test.go` 及上述回归日志 |
| TLS 热切换 | Go 本机 TLS 测试中,新握手换用新证书,原连接继续收发;无缓存及缓存到期时恢复上一套 | 同上,属于自动化 TLS 测试,不代表真实 CA 续期 |
| 开发 HTTPS | 实际 HTTPS 服务执行 `setSecureContext` 后新连接换证、原连接保留,绝对路径和上一套恢复通过 | `test/frontend/dev-tls.test.mjs` 及上述回归日志 |
| 最新包部署 | 前端、三个插件、主程序及管理员程序完整构建,部署后源码指纹和文件哈希验证通过 | `out/build-state.json`、`test-log/service-launch/` |
| 真实配置迁移 | 启动事务将代次 27 升到 28,改为 `ssl/example.com.crt` 和 `ssl/example.com.key`,旧 `ssl/managed` 不再存在 | 本机 `yunx-test/config/ssl.json` |
| 原文件保护 | 原证书、原私钥、ACME 账户密钥、三把业务密钥的字节及创建时间不变 | `test-log/ssl-direct-baseline-20260909.json` 为比较基线,不含密钥内容 |
| 浏览器保存与刷新 | MCP 表单输入同一原件的绝对路径,经当前密码确认后显示“配置已保存”;规范化成原件相对路径,五分区代次均为 29;刷新状态及忽略缓存重载后路径一致、登录仍有效 | 当前会话 MCP 操作记录 |
浏览器操作期间后端 PID 为 31988,前端 PID 为 14648,保存配置未触发服务重启;这些编号仅代表此次现场。前端保留源码热更新,后端使用 `yunx-test` 最新编译程序。部署根目录只有 `config`、`ssl`、`data`、`log` 及两个运行程序。
当前仍是自定义证书模式,因此不会为手工原件额外复制上一套,也没有触发真实 ACME 签发。自动续期的两套保留通过独立测试材料验证;Linux 实机运行、正式管理员恢复通道、真实 CA 续期、全流程邮件收发仍不属于本轮验收。数据库没有清空,本轮是存量部署迁移验收,不是首次初始化验收。
开发服务器首次进入懒加载页面时发生过依赖预构建刷新,Vite 日志可确认;再次打开页面后完成上述验证。构建仍有富文本编辑器约 809 kB 的分包告警,开发代理有 `util._extend` 弃用提示,不属于本次 SSL 改造范围。MCP 快照落盘被其工作区根目录限制拒绝,未绕过限制写入;页面验收证据保留在本会话工具记录中。
### 本轮任务状态
- [✓] 自定义证书直接引用,不生成副本。
- [✓] 自动续期使用根目录版本文件,保留当前与上一套未过期证书。
- [✓] 到期先清除配置引用再清理文件,暂存及异常材料受到保护。
- [✓] 新握手热加载,不主动关闭已有连接,失败保留有效证书。
- [✓] 旧部署迁移、完整构建、部署包核验和浏览器保存刷新完成。
- [!] 真实 CA 续期及 Linux 生产实机运行未执行,不能视为已验收。
## 历史实测记录(2026-09-08,旧存储方案)
以下为旧 `managed` 存储方案的历史记录,不代表 2026-09-09 直接引用与热切换改造已验收。
| 验证项目 | 实测结果 | 本地记录 |
| --- | --- | --- |
| 空目录正式启动两次 | 仅放两个程序,使用 `smail.exe -p 15201`,两次均自动创建部署目录及五个 SSL 子目录,初始化页面返回 200 | `test-log/ssl-startup-20260908-202756/result.json` |
| 旧部署自动升级 | 三把业务密钥归档前后字节不变,旧位置不再保留重复文件 | `test-log/ssl-upgrade-result-20260908.json` |
| 旧证书自动清理 | 由新程序启动完成,受管材料从 11 套收敛为 2 套,当前证书和私钥仍存在 | 同上 |
| 原文件保护 | 五个生效 JSON、根目录证书、ACME 密钥及其他原 SSL 文件字节不变;已有运行目录完整恢复 | 同上及空目录启动记录 |
| 邮件 TLS | 465、993、995 均完成 TLS 1.3 握手,实际证书指纹与当前配置指向的证书一致 | `test-log/ssl-upgrade-tls-20260908.log` |
| 全量自动化回归 | 前端 140/140、部署 4/4、Go 全量通过 | `test-log/run-2026-09-08T12-25-13-445Z-12312/` |
| 专项并发检查 | 配置、运行路径、配置凭据、外部邮箱及 SSL 设置包的 `-race` 测试通过 | `test-log/run-2026-09-08T12-26-15-343Z-27176/` |
| 最新部署包 | 前端、三个插件、Windows 内嵌资源主程序及管理员程序完成构建,源码指纹和部署哈希通过 `node deploy/build-state.mjs verify --deployed` 核验 | `out/build-state.json` |
当前页面优化环境保留为前端源码热更新、后端最新编译程序,运行配置统一读取 `yunx-test/config/`。前端 HTTPS 及后端 `/api/ping` 均返回 200;后端使用已明确标记的开发恢复通道豁免参数,与上表不含该参数的空目录正式启动测试分开记录。
本轮没有进行浏览器页面交互验收,也没有宣称完整生产初始化或 Linux 运行验收通过。前端已有富文本编辑器分包体积提醒不在本次修改范围内。
## 历史任务状态(2026-09-08)
- [✓] 正式主程序自动创建业务目录,不依赖部署脚本创建。
- [✓] 已采用 SSL 布局的旧部署自动迁移三把业务密钥,重复启动不更换密钥。
- [✓] 受管证书正常稳定状态保留两套,保护生效、暂存和恢复所需材料。
- [✓] 最新包部署、空目录启动、旧部署升级及邮件 TLS 验证完成。
- [!] 当时尚未实现根目录直接引用,已由上文 2026-09-09 新规则替代。
- [!] Linux 正式运行、完整首次初始化及正式管理员恢复流程尚未完成本轮实机验收。