Scert/doc/Linux生产流程测试记录.md

781 lines
84 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.

# Linux 生产流程测试记录
## 当前进展:发布说明修正与入口、依赖审计
2026-09-20 03:12 起接续,03:22 完成本节所列检查。本节为最新状态,下文按各自时间保留历史证据。实际仓库仍为 `D:\Desktop\Scert`,目标为 `45.136.13.241`,本次管理域名为 `odacm.cn`;这些测试参数不硬编码进产品,也不构成验收依据。本轮未登录服务器、上传产物、修改 DNS/Nginx、修改系统信任库或清空共享数据库,完整生产部署验收尚未通过。
### 需求与任务状态
- [✓] 接回 Windows 与 Linux 目标的两项 `govulncheck`,均已正常退出;没有重复启动未结束任务。
- [✓] 修改前检查点 `71fe48c` 已推送,暂存区事先为空;旧补丁、二进制、脚本及历史构建目录全部保留。
- [✓] 修正 `deploy/README.md`:生产探测不再跳过 TLS,分别说明回环上游和正常 DNS 公开入口验证;统一标准 `443` 示例,旧 `8003/8004` 仅保留为自定义端口示例。
- [✓] 按当前实现修正 PostgreSQL 备份恢复说明,注明官方工具、版本、严格 TLS、同目标同对象清单限制及独立保护的集群资源,不扩大为灾难恢复已验收。
- [✓] 复核 Go/Wails 的 `-trimpath` 和前端禁用源码映射,发布检查及 CLI 定向回归通过,已有 Linux 产物路径扫描再次通过。
- [!] SSH 新指纹尚未通过可信控制台核对;没有发送密码、更新 `known_hosts` 或接受新主机身份。
- [!] 两家 HTTPS DNS 查询仍指向旧地址,当前入口 TLS 检查失败;不可用固定解析探测替代域名生产验收。
- [!] 本机 PowerShell 有效策略仍为 `Restricted`,没有修改或绕过;最新正式发布脚本、Linux 双层集成与部署流程仍未完成。
### 入口与主机身份
本机 `Resolve-DnsName odacm.cn -Type A -DnsOnly` 返回 `198.18.1.249`。严格 HTTPS DoH 查询 Cloudflare 与 Google 均返回 `odacm.cn A 47.108.78.12`、TTL 600,不是本次目标 `45.136.13.241`。这是两个递归解析器的当前观察,不宣称已直接验证权威 DNS;本机普通 DNS 的结果不能替代公共解析结论,也没有据此更改网络或 DNS 设置。
以下三项均已实际执行,无跳过证书验证:
```text
curl.exe --noproxy "*" --fail --silent --show-error --max-time 15 https://odacm.cn/readyz
curl.exe --noproxy "*" --fail --silent --show-error --max-time 15 --resolve odacm.cn:443:45.136.13.241 https://odacm.cn/readyz
curl.exe --noproxy "*" --silent --show-error --max-time 15 --resolve odacm.cn:80:45.136.13.241 -I http://odacm.cn/
```
前两项返回 curl 错误 35:`schannel: failed to receive handshake, SSL/TLS connection failed`;第三项返回错误 52:`Empty reply from server`。只能说明本机所经网络路径上的请求失败,不能证明目标 Nginx 配置、服务状态或证书根因;透明代理等网络因素仍未排除。
03:21 左右,Paramiko 无认证握手读取到 Ed25519 指纹 `SHA256:cVoSXsYmhALjAdN2Gso3JvbnvYQCQBFuRv3IZjFvzsE`,`authenticated=False`。它与历史可信值 `SHA256:XN9mJ75b4CJsFPZ7fbkAmU1yFxKZ4Tw01z8TN2EJMlk` 不同;网络上反复读到同一新公钥不构成可信确认。须在服务商可信控制台执行 `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub` 后核对,再决定是否认证。
### 依赖与回归证据
```text
go mod verify
govulncheck ./...
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 govulncheck ./...
govulncheck -show verbose ./...
npm.cmd audit --omit=dev --json
go test -trimpath -count=1 -p=2 ./internal/releasearchive ./internal/cli
git diff --check
```
Go 命令在 `backend` 执行,npm 命令在 `frontend` 执行;Linux 环境变量一行是目标配置的简写,实际通过 PowerShell 子进程环境设置。`go mod verify` 返回 `all modules verified`。两平台漏洞扫描均报告调用路径零项、导入包零项、依赖模块一项;Windows 详细报告将其定位为 `GO-2026-5932`,涉及 `golang.org/x/crypto@v0.56.0` 中不再维护的 `openpgp` 包,无修复版本,当前代码未导入受影响包或调用其符号。没有为消除模块提示而移除仍需要的加密依赖,也不能将本结果描述成所有依赖完全没有已知风险。
前端生产依赖审计报告零漏洞,不包含开发依赖,也不代表未来漏洞保证。发布检查包与 CLI 回归均通过;真实 PostgreSQL 集成入口未提供官方工具和隔离环境参数,本轮不算新增真实数据库演练。部署说明的两个 JSON 配置块通过解析检查,差异检查无错误;没有在 Linux 上执行新文档中的 Nginx 配置或健康探测示例。
### 本地路径要求与产物边界
重新调用 `scert-release --verify-only`,对 `.runtime/clean-path-artifacts-20260920-0243/` 的三个 Linux 程序与整个资源目录,以及 `.runtime/app-integration-20260920-0307/app.test` 执行构建标记和路径扫描,均通过。扫描参数包含当前与旧工作区、用户目录、实际 Go 工具链及缓存、Go/Node 安装目录与临时目录;这些参数不写入产物。已有 Web 产物未找到 `.map` 文件。
这两处是前序已生成的核验产物,本轮没有重新编译完整发布包,也没有把新部署说明复制到旧产物中;此前记录的摘要及源码版本边界继续适用。正式候选必须从最终提交重新运行完整发布脚本,生成清单和校验文件并通过同样检查后再部署。模块导入路径、相对文件名和运行时部署路径不等于本机构建绝对路径,裁剪要求不删除这些运行所需信息。
### 待解除的验收阻塞
- [ ] 通过可信控制台确认当前 SSH 主机身份;确认后先只读检查现场,不能沿用旧服务器 PID、端口或配置结论。
- [ ] 核对 `odacm.cn` 的实际 DNS 配置与迁移范围,使其指向授权目标;分别验证权威/公共解析、本机解析及正常 DNS 的严格 HTTPS 入口。
- [ ] 在获准的构建环境运行完整发布脚本,并在隔离 Linux 单元执行五个应用与 Nginx 双层场景,确认没有跳过。
- [ ] 完成 root 辅助服务/systemd、中断恢复、最新候选部署、管理证书续期、目标推送、通知送达、升级回退与容量验收;未完成前不能宣告生产部署通过。
## 当前进展:真实应用与 Nginx 双层切换集成用例
2026-09-20 02:54 起接续。本节为最新状态,后续章节保留为历史证据。本轮没有登录服务器、部署程序、修改系统信任库、修改正式 Nginx 或清空共享数据库。完整生产部署验收尚未通过。
### 需求与任务状态
- [✓] 核对实际仓库 `D:\Desktop\Scert`,保留旧补丁、未跟踪二进制及历史产物;修改前中文检查点 `53ed3d6` 已推送。
- [✓] 延续用户要求,所有本轮 Go 测试及交叉编译均使用 `-trimpath`;最新 Linux 测试程序通过构建标记及本机构建路径扫描。
- [✓] 新增真实应用重建回归,验证 HTTPS、SQLite 和凭据存储重建后的就绪状态与数据库内容保留。
- [x] 新增五个真实应用与 Nginx 双层集成场景,源码复审、交叉编译及 Linux 目标静态检查通过,等待 Linux 实际运行复审。
- [✓] 测试提交 `c7d5344` 已推送;本节更新前已检查暂存区,仅包含三个新增测试文件。
- [!] SSH 主机身份变化仍未获得可信确认,没有发送密码或接受新指纹;远端部署继续等待身份核对。
- [!] 本机无可用 WSL、Docker 或 Podman;PowerShell 有效执行策略仍为 `Restricted`,没有修改或绕过策略,完整发布脚本仍待执行。
### 集成范围与边界
`backend/internal/app/management_integration_test.go` 复用正式 `NewWithConfigPath`、`HTTP.ListenAndServe`、SQLite 迁移和敏感信息存储,使用独立临时目录、随机回环端口和测试证书。跨平台用例关闭并重建应用,严格 TLS 探测 `/readyz`,同时检查预置数据库记录保留;没有访问真实 DNS 或申请证书。
`management_nginx_linux_test.go` 与 `management_nginx_fixture_linux_test.go` 通过正式应用适配器和 Unix IPC 连接独立 Nginx,覆盖以下场景:
1. 正常双层切换,证书路径持久化,应用重建后保持已提交候选。
2. 暂存已完成但应答丢失,回退代理,应用保持旧证书。
3. 提交已完成但应答丢失,通过事务与实际双层证书核验确认成功。
4. 暂存后恢复查询不可达,保留 `prepared` 与代理 `staged`,重建应用后恢复旧证书。
5. 上游配置持久化并切换后,提交与恢复查询不可达,保留 `upstream`,重建后完成提交。
故障通过客户端包装层注入,不是真正丢弃网络帧或杀死辅助进程。应用关闭后由配置重建,使用实际恢复工作循环处理持久化事务;不等同于进程强杀、断电、固定生产 socket 或 systemd 服务重启验收。测试辅助服务与应用使用同一隔离 UID,不替代此前不同 UID/root IPC 边界用例。
每个 Linux 场景在独立子进程中通过 `SSL_CERT_FILE` 加载测试 CA,避免系统根证书缓存影响;没有给生产 API 增加信任注入接口,也没有关闭证书验证。独立 Nginx 开启上游可信链验证,只使用本轮配置、PID、版本目录和回环端口。重建前后均检查真实两层叶证书、协调记录、配置未被无关修改及数据库内容。
### 本轮执行证据
以下 Windows 本地命令已执行通过:
```text
go test -trimpath -count=1 -p=2 ./...
go vet -p=2 ./...
go test -race -trimpath -count=10 -p=1 -run '^TestApplicationManagementListenerRestartsWithPersistentReadiness$' ./internal/app
go test -race -trimpath -count=1 -p=1 ./internal/app
```
新增真实应用重建测试首跑通过;连续 10 轮竞态测试包内用时 10.034 秒,应用包全量竞态测试包内用时 19.710 秒。Windows 全量测试不包含 Linux 专用用例。本轮未修改前端,未重复浏览器或前端构建验收。
Linux 目标 `go vet -p=2 ./internal/app ./internal/nginxactivation ./cmd/scert-nginx` 通过;最终测试源码调整后再次运行应用包 Linux 目标 vet 与交叉编译,均通过。最新测试程序为 `.runtime/app-integration-20260920-0307/app.test`,Go 1.26.6、`GOOS=linux`、`CGO_ENABLED=0`、`-trimpath=true`,SHA-256:
```text
ce3a89d9450c88ffcdb0802234a757bbe0d8888e88f737a21e9427d5f19fca77
```
扫描覆盖当前与旧工作区、用户目录(包含实际 Go 工具链、模块缓存、构建缓存与用户临时目录)、常见 Go/Node 安装目录及系统临时目录,结果通过。该文件是测试程序,不是正式安装包,也没有 Linux 执行通过的证据;旧 `.runtime/app-nginx-20260920-0303.test` 保留,不作为最新用例产物。
### 下一步验收
- [ ] 通过服务商可信控制台核对 `45.136.13.241` 的 SSH 主机公钥,再只读检查实际服务、DNS、端口、数据库和容量。
- [ ] 在非特权隔离 Linux 单元中显式指定 `SCERT_TEST_NGINX_BINARY`,执行应用双层集成测试,确认五个场景均实际运行、没有跳过,并重复执行检查稳定性。
- [ ] 验证独立 root 辅助服务初始化与 socket/service 模板,补齐进程强杀及 systemd 恢复证据。
- [ ] 执行完整发布脚本,生成具备清单、校验和且路径扫描通过的最新正式候选,再完成管理证书续期、目标推送、通知送达、升级回退与容量验收。
## 当前进展:辅助服务初始化、真实监听回归与干净源码路径复验
2026-09-20 02:25 至 02:47 接续。本节为最新状态,后续各节保留为历史证据;不把旧服务器记录当作当前环境。本轮仅完成本地修改、交叉构建和测试,没有登录服务器、部署程序、修改 Nginx 或清空数据库。
### 需求与完成状态
- [✓] 路径裁剪提交 `a212401` 已推送;本轮再次复审发布入口与扫描器,确认 Go/Wails 使用 `-trimpath`,前端关闭源码映射,归档前检查构建信息及本机构建路径。
- [✓] 修改前检查点 `aa5d669` 与辅助服务提交 `d8b4850` 已推送。补齐 root 策略、安全初始化入口、systemd socket/service、中文部署说明与打包接线。
- [✓] 新增真实监听回归提交 `549d6a832eec0cbc8dfeb01a43eea2eecfdba2fc` 已推送,远程 main 已核对一致;该提交也是更新本节前的检查点。
- [✓] 使用独立干净克隆重新构建三个 Linux 程序与 Web,全部通过路径扫描,构建标记为 `vcs.modified=false`。
- [!] 完整 PowerShell 发布脚本仍受有效执行策略 `Restricted` 限制,本轮未改变或绕过执行策略;直接工具构建不等于发布脚本已验收。
- [!] SSH 主机身份变化仍未获可信确认,不能发送 root 密码或继续远端部署。完整生产部署验收尚未通过。
### 辅助服务与真实监听测试
`scert-nginx --initialize` 验证输入文件、可信路径、权限、证书链、两层实际 TLS 证书、就绪响应与 `nginx -t`,只在专用目录生成私有版本及引用。已有状态、链接、不可信路径或宽权限私钥均拒绝;初始化不覆盖、不重载,也不安装服务。普通安装器仍不启用该 root 辅助服务。
新增 socket/service 保持本地 Unix IPC 与唯一允许 UID,不给应用进程增加权限;辅助单元仅保留 `CAP_DAC_OVERRIDE CAP_CHOWN`,并依赖、跟随 `nginx.service`。模板与初始化已完成源码和交叉构建检查,尚无本轮目标 Linux/systemd 执行证据。
`backend/internal/httpapi/management_session_integration_test.go` 通过真实 `Server.ListenAndServe()`、临时 SQLite 及迁移、受保护凭据存储和严格 HTTPS 就绪探测覆盖三项:
1. 独占会话中切换证书,只修改配置中的证书路径;监听器重建后保留新证书与数据库内容。
2. 模拟证书配置已落盘而内存快照尚旧的中断窗口,拒绝继续协调;服务重建后从持久化配置恢复候选。
3. 数据库关闭后,即使 HTTPS 仍可连接,就绪探测也必须失败。
三项首跑及连续 20 轮通过。HTTP API 全包竞态测试 `go test -race -trimpath -count=1 -p=1 ./internal/httpapi` 完成并通过,包内用时 67.667 秒。这是 Windows 真实上游监听测试,不是完整 Nginx 双层协调、进程强杀或 systemd 重启验收。
### 干净源码与产物证据
独立克隆 `.runtime/clean-path-20260920-0243/` 对应提交 `549d6a8`,构建前后 Git 状态均干净。执行以下检查全部通过:
```text
go test -trimpath -count=1 -p=2 ./...
go vet -p=2 ./...
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go vet -p=2 ./internal/app ./internal/nginxactivation ./cmd/scert-nginx
npm test
npm run build -- --outDir <独立新建目录>/web --emptyOutDir false
```
前两项为 Windows 本地执行;第三项仅为 Linux 目标静态检查。前端 54 项测试通过,生产构建无 `.map` 文件;本轮未修改页面,也未重复浏览器渲染验收。
`.runtime/clean-path-artifacts-20260920-0243/` 保留三个 Linux 程序、Web、安装器、服务模板、Nginx 模板和说明,共 18 个文件。三个程序均由 Go 1.26.6 构建,`-trimpath=true`、`CGO_ENABLED=0`、`GOOS=linux`,内嵌提交为 `549d6a8`、`vcs.modified=false`。整目录扫描覆盖当前与旧工作区、干净克隆、产物目录、用户目录、Go/Node 安装目录、Go 缓存和临时目录,结果通过。
| 程序 | SHA-256 |
| --- | --- |
| `scert` | `aadc2c3d08fc1ab0e466ac7f332dbe024bb17009553901bdb5d91f108d65c038` |
| `scert-admin` | `f41cdc58721231512656bf1a41bc25faa671d7aef6d05955e0553111ff4604f0` |
| `scert-nginx` | `42d957f5f0263419d8c3b0a935b5882b80b6be1ec81e11d2d82c6bdb83c5fca8` |
该目录没有正式发布脚本生成的 `manifest.json` 和 `SHA256SUMS`,仅用于核验,禁止当作正式安装包。路径扫描不会删除模块导入路径、相对源码文件名或运行时必需部署路径,也不保证识别源码中手写的任意其他机器路径。
三类 Linux 测试程序从同一干净克隆以 `go test -trimpath -c` 生成,保留在 `.runtime/clean-tests-20260920-0246/`:`httpapi.test`、`nginxactivation.test`、`scert-nginx.test`。仅交叉编译通过,尚未在 Linux 运行。前轮 `.runtime/path-audit-20260920-0236/` 及其他旧产物全部保留,其 `vcs.modified=true` 标记不再作为新干净产物的证据。
### 连接边界与待验收项目
02:47 左右,无认证 SSH 握手取得 `45.136.13.241` 的 Ed25519 指纹 `SHA256:cVoSXsYmhALjAdN2Gso3JvbnvYQCQBFuRv3IZjFvzsE`,与历史可信值 `SHA256:XN9mJ75b4CJsFPZ7fbkAmU1yFxKZ4Tw01z8TN2EJMlk` 不同。Windows 自带 `ssh-keyscan` 因不支持服务器选择的 KEX 未取得公钥,随后使用 Paramiko 只完成握手与公钥摘要读取;没有认证、写入 `known_hosts` 或传送业务数据。网络上读取到的公钥不能自证可信,仍须在服务商可信控制台执行 `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub` 核对。
- [ ] 获准执行完整发布脚本,生成带清单和校验文件的正式候选;不得以核验目录替代。
- [ ] 确认主机身份后只读检查现场服务、域名、端口、数据库及容量,不沿用历史部署参数。
- [ ] 在隔离 Linux 环境执行应用适配器、真实 Nginx 双层协调、root IPC、systemd、应答丢失及中断恢复测试。
- [ ] 完成最新候选部署、真实管理证书续期、目标机推送、通知送达、重启、升级与业务回退及容量验收。
## 当前进展:路径裁剪与本地发布检查
2026-09-20 接续。本节为最新状态,后文为各阶段历史记录。实际仓库位于 `D:\Desktop\Scert`,原 `D:\Desktop\SSL` 已不存在。本次修改前检查点 `ba6c6d1` 已推送;本节产物来自修改中的工作区,仅用于构建验证,不是正式可部署发布包。
### 需求与完成状态
- [✓] 按用户最新要求,Go 与 Wails 发布入口均使用 `-trimpath`,手动构建示例同步补齐;前端明确关闭源码映射。
- [✓] 发布检查读取每个 Go 二进制的构建信息,缺少 `-trimpath=true` 时拒绝归档;扫描发布目录的全部文件,发现本机构建路径时拒绝归档。
- [✓] 扫描覆盖正反斜杠、JSON 转义、UTF-8、UTF-16 和跨读取边界;单元测试覆盖未裁剪二进制、静态资源残留、读取错误与缺失文件。
- [✓] 三处安装器及服务模板测试不再依赖编译时 `runtime.Caller` 路径,支持 `go test -trimpath`。
- [✓] Linux 发布入口补入 `scert-nginx`,归档可执行权限由实际二进制列表生成;单元测试和实际归档检查通过。普通安装器仍不安装或启用 root 辅助服务。
- [!] 当前主机禁止运行 PowerShell 脚本,完整发布脚本未实际执行通过;没有修改执行策略。语法检查通过,实际构建和扫描通过直接调用工具验证。
- [!] SSH 主机身份变化尚未确认,没有发送密码、上传文件、变更 `known_hosts`、重载 Nginx 或清空数据库。
### 本轮验证证据
- Windows 后端 `go test -trimpath -count=1 -p=2 ./...` 全部通过。
- Windows `go test -race -trimpath -count=1 -p=1 ./internal/releasearchive ./internal/admin` 通过;桌面模块 `go test -trimpath -count=1 -p=2 ./...` 通过。
- Windows 与 Linux 目标的发布检查、安装管理包及发布命令 `go vet` 通过;Linux 额外检查 `cmd/scert-nginx`。这是静态检查,不是 Linux 测试运行。
- 前端 54 项测试通过,Vite 生产构建成功,无源码映射文件。
- `.runtime/path-check-20260920-0151/` 保留本轮 Linux 的 `scert`、`scert-admin`、`scert-nginx`,Windows 的 `scert.exe`、`scert-admin.exe`、`ScertDesktop.exe` 和 Web 资源。
- Wails 2.11.0 使用 `-trimpath -nopackage` 完成 Windows 产物构建。为保留已有生成文件且避免依赖文件改写,本轮先构建前端,再传入 `-s -skipbindings -m -nosyncgomod`;这不是完整发布脚本的桌面步骤验收。
- 六个二进制均通过构建信息检查;包含部署说明、安装器和服务模板的目录通过路径扫描,额外覆盖旧工作区和当前 Go/Node 安装目录。扫描检查本次指定的构建环境路径,不等于识别所有可能手写的任意机器路径。
- `.runtime/path-check-linux-20260920-0158.tar.gz` 为直接调用归档工具生成的验证包,确认三个 Linux 程序和安装器均为 0755。该包没有完整发布脚本生成的清单及校验文件,不用于安装。SHA-256:`cda83659c0b8a6382efda4907b1adaa26170e35e4232e9bffc8c494407db7f4a`。
- 新交叉编译的 `.runtime/nginx-request-trimpath-20260920-0158.test` 构建信息包含 `-trimpath=true`,SHA-256 为 `ecfe71a2562fde94755339270988910e01b0ac76e2e21a2472e7e1484fc972cd`。未在 Linux 执行;旧测试程序保留,不作为后续部署产物。
### 前序交接与下一步
接手时的源码检查点 `822e204` 已包含请求取消栅栏、请求编号恢复、辅助服务能力检查及应用 HTTPS 监听保护。本轮未重复将交接中的 Linux 交叉编译结果算作真实执行,也未重跑历史服务器夹具。
待从可信控制台核实目标 SSH 主机身份后,先核对现场服务、端口、DNS、数据库和容量,再执行隔离的 Linux/Nginx/Unix IPC 测试。仍需完成辅助服务安装策略与 socket 模板、真实应用配置持久化协调验收、正式候选构建、管理证书续期、目标机推送、通知、重启及升级回滚。当前未达到生产部署验收条件。
## 当前进展:受限本地激活服务与身份边界验证
2026-09-18 02:20 起接续,修改前检查点 `cc94a61` 已推送。本轮继续验证独立辅助服务,没有替换正式二进制、安装正式 socket、重载正式 Nginx 或清空共享数据库。代理模式续期仍在申请前拒绝,完整生产验收尚未完成。
### 已完成的修改
- [✓] 补齐受限 Unix socket 服务、客户端及独立 root 策略的测试;仅开放 `ready/state/stage/commit/reconcile`,请求不能指定命令或路径。
- [✓] 修复监听器异常关闭被误报为正常退出;请求校验失败也清理已解析的私钥切片,并清理编码、读取持有的帧缓冲。
- [✓] 策略加载增加 socket 父目录信任校验,覆盖非法 UID、地址、域名、权限、符号链接、硬链接、FIFO、超限及错误 JSON。
- [✓] 激活等待改为连续三次新连接的严格 TLS/就绪探测;补测新旧证书交替时需重新累计成功次数。
- [✓] 实际不同 UID 子进程验证授权与拒绝;伪装服务端即使将 socket inode 改为 root 所有,客户端仍通过内核身份拒绝,不发送请求。
- [✓] 继承 FD3 的子进程验证合法 Unix socket、错误 PID、错误 FD 数量、错误路径及 TCP 监听器拒绝,关闭不删除托管 socket。
### 验证证据
本地 `go test -p=2 ./...`、`go test -race ./internal/nginxactivation -count=1` 通过;竞态测试仅覆盖 Windows 通用代码。Linux 目标 `go vet ./internal/nginxactivation ./cmd/scert-nginx`、辅助命令构建及两类测试程序交叉编译通过。
远端测试程序位于 `/scert/acceptance-20260917-1044/`,同名本地文件位于 `.runtime/`;上传前后 SHA-256 一致:
| 文件 | SHA-256 |
| --- | --- |
| `nginx-ipc-20260918-0240.test` | `0c41d5a0f0fd7990bb33377280af651054198529eef076adeedf7d0a3bd5d497` |
| `nginx-listener-20260918-0230.test` | `c99dc7ef748ec8c6b3219859d7c070935c77312449b4338ce9e8539e28e3ea1e` |
`scert-nginx-ipc-20260918-0240.service` 以 Scert 用户运行,私有网络与临时目录、只读系统、隐藏用户目录、禁止提权,限制 192 MiB 内存、禁用 Swap、CPU 50%、64 个任务。通过 `InaccessiblePaths` 隐藏 `/scert/conf /scert/data /scert/log /scert/backups /ssl-scert-acceptance -/ssl`。显式使用 `/usr/sbin/nginx`,以 `-test.timeout=150s -test.count=10` 连续十轮通过,退出零,运行 111.123 秒、CPU 7.418 秒、峰值内存 21.9 MiB。不同 UID 用例在非 root 单元中按设计跳过,在下列独立 root 单元中执行。
`scert-nginx-listener-20260918-0230.service` 使用同类非特权隔离,继承监听器五个场景连续三轮通过,退出零,运行 170 毫秒、峰值内存 6.1 MiB。
`scert-nginx-identity-20260918-0242.service` 是仅执行身份边界用例的临时 root 隔离单元,保持私有网络、私有临时目录、只读系统和正式数据不可访问,能力限制为 `CAP_CHOWN CAP_SETUID CAP_SETGID CAP_DAC_OVERRIDE` 并显式设为环境能力。设置 `SCERT_TEST_ROOT_IPC=1`,以 `-test.run=^TestRootPeerIdentity$ -test.count=3` 验证实际 UID 65534 的授权请求、UID 65533 的拒绝及非 root 服务身份伪装,全部通过,运行 310 毫秒、峰值内存 32.6 MiB。没有向正式 Scert 服务增加能力。
最终留档单元 `scert-nginx-ipc-final-20260918-0244.service` 使用相同最终测试程序再运行一轮,31 项顶层测试通过,仅 root 身份用例按设计跳过;该用例已在上面的独立单元通过。最终单元运行 11.094 秒、峰值内存 20.3 MiB,详细日志保存在 systemd journal,可使用 `journalctl -u scert-nginx-ipc-final-20260918-0244.service` 复核。
2026-09-18 02:42 结束检查:正式 Scert PID `589832`、Nginx PID `578671`、MySQL PID `589785` 均未变化;正式程序 SHA-256 仍为 `f5b08bf0fb88f7b19281e91f942aa2995866f96420e422c015890b841bae6366`,Nginx 主配置及 Scert 站点摘要与本轮开始一致。最终测试与身份测试单元均已退出,PID 为零。`nginx -t` 通过,使用管理域名严格验证回环 TLS 后 `/readyz` 返回 200 和 `ready`。本轮不包含浏览器或新增公网验收。
### 失败与修复记录
初始测试单元因误写不存在的 `/scert/logs` 在执行测试前出现 `226/NAMESPACE`;核实正式日志目录为 `/scert/log` 后修正隔离参数。旧测试程序 `nginx-ipc-20260918-0230.test` 重复运行暴露应答丢失用例的抢锁竞争,以及真实 Nginx 单次探测成功后仍遇到旧工作进程。前者改为先确认暂存已获得锁再模拟应答丢失;后者加强连续探测并增加回归测试,而非简单增加测试超时。旧程序和失败记录均保留。
不同 UID 单元最初只设置能力边界,子进程在执行前报 `operation not permitted`;独立诊断确认有效能力中缺少 `CAP_SETUID`,`setpriv` 同样失败。显式设置所需环境能力后,同一测试程序通过,不将首次未进入校验逻辑的失败计为安全验证。
### 仍需完成
- [ ] 安装 socket/service、专用版本目录和目标站点 include;当前正式站点仍直接引用原证书文件。
- [ ] 应用持久化双层协调与启动恢复、完整生命周期串行化、应答丢失及中断恢复;目前不能自动处理任意应用崩溃窗口。
- [ ] 最新完整候选构建部署、管理证书续期闭环、目标机真实推送、通知送达、完整灾难恢复和容量验收。
## 前序进展:Nginx 证书激活事务组件实机验证
2026-09-18 01:45 至 02:03 本轮接续,修改前检查点 `10645ea` 已推送。本节为最新状态;本轮没有替换正式程序、重载正式 Nginx 或清空正式数据库,组件验证通过不代表完整生产验收完成。
### 已完成的修改与复审
- [✓] 探测仅允许规范化回环 IP、端口和具体管理域名;每次建立新 TLS 连接,严格验证可信链、域名和预期叶证书,并检查受限大小的 Scert 就绪响应。不使用环境代理,不跟随重定向。
- [✓] 激活器专用目录及父路径检查所有者、权限和符号链接;专用目录要求当前激活器用户所有且权限为 0700。仅例外允许 root 所有、带粘滞位的共享父目录,不允许其他可写父目录。
- [✓] 状态读取使用禁止跟随符号链接的文件打开方式,检查普通文件、文件身份、大小、所有者、写权限及硬链接数。新证书、私钥和事务快照以 0600 写入并同步。
- [✓] 切换前验证代理、上游和专用目录内旧材料一致;候选证书必须符合两侧信任配置、覆盖管理域名、处于有效期内并延长实际有效期。
- [✓] 专用 include 仅接受固定格式和随机版本目录,事务记录验证版本编号、引用和证书。切换、配置检查和就绪探测后重新核对 include,发现外部修改则拒绝覆盖或报告成功。
- [✓] 暂存只切 Nginx;上游切换后双层证书一致才允许提交。取消、检查失败、重载失败和探测失败进行有界回退;未完成事务可恢复,已提交事务不自动回退。日志写入失败不会被误报为成功。
### 验证证据
本地 `go test -p=2 ./...` 通过,未变更包使用 Go 缓存;`go test -race ./internal/nginxactivation -count=1` 通过,Windows 只覆盖通用探测代码。使用 Linux 编译目标执行 `go vet ./internal/nginxactivation` 通过。Linux 测试覆盖目录、文件、事务和受限命令实现。
最终测试程序为 `.runtime/nginx-activation-20260918-0202.test`,远端 `/scert/acceptance-20260917-1044/nginx-activation-20260918-0202.test`;两端 SHA-256 一致:`26db07a3ed15f4324af4e742cd252e0e8700253b0543bee3757a13cc5845f796`。此前两版测试程序保留,没有覆盖上传。
最终单元 `scert-nginx-final-20260918-0202.service` 以 Scert 用户运行,私有网络、私有临时目录、只读系统、隐藏用户目录、禁止提权;正式数据、配置、日志、备份及证书目录禁止访问。限制内存 192 MiB、禁用 Swap、CPU 50%、64 个任务和 120 秒运行上限。通过 `SCERT_TEST_NGINX_BINARY=/usr/sbin/nginx` 显式启用真实 Nginx 用例,执行 `-test.v -test.timeout=90s -test.count=3`:16 项顶层测试连续三轮通过,无失败、无跳过;运行 4.492 秒,CPU 1.102 秒,峰值内存 13 MiB,Swap 为零。
真实 Nginx 用例使用独立配置、PID、临时目录与随机回环端口,启动独立非特权进程;验证暂存重载、上游切换后提交、下一轮未提交事务恢复及实际入口证书。退出时等待独立 Nginx 正常终止,不操作正式 Nginx 服务。测试还覆盖跨进程锁互斥及释放后重新获取、候选错误、双层不一致、日志失败、模拟重启阶段、外部 include 或材料变更、符号链接、硬链接、FIFO 和可写父目录。
最初单元 `scert-nginx-unit-20260918-0155.service` 在测试开始前因 `/ssl` 不存在而出现 systemd `226/NAMESPACE`;调整为 `InaccessiblePaths` 中 `-/ssl` 后,缺失路径可忽略,而现有正式目录仍强制隔离。后续单元 `scert-nginx-unit-20260918-0156.service`、`scert-nginx-real-20260918-0159.service` 及最终单元均退出零。
结束时正式 Scert PID `589832`、Nginx `578671`、MySQL `589785` 与本轮起点一致。正式二进制摘要仍为 `f5b08bf0fb88f7b19281e91f942aa2995866f96420e422c015890b841bae6366`;Nginx 主配置和 Scert 站点摘要未变。`nginx -t` 通过,按管理域名严格 TLS 验证的回环 `/readyz` 返回 200 和 `ready`。最终隔离单元为 inactive、PID 为零;本轮未进行浏览器或独立公网探测验收。
### 接线前必须完成
- [ ] 独立受限辅助进程、受保护本地 IPC、请求身份鉴别及固定本地策略;不能将 Nginx 重载权限赋予 Scert 主进程,也不能通过 HTTP 接收任意命令或配置路径。
- [ ] 安装时将当前证书复制到激活器专用版本目录并生成 include;现有线上配置仍直接引用原证书目录,不满足组件专用目录契约,不能直接切换。
- [ ] 应用协调者持久化双层激活状态,处理上游已持久化但代理未提交、提交应答丢失、调用者退出及超时恢复;组件 Recover 只回退 Nginx,不替代上游配置恢复。
- [ ] 确认上述接线后再解除代理模式申请前保护,部署最新候选,继续真实目标机推送、通知送达、灾难恢复、容量及完整生产验收。
## 当前进展:Linux 测试隔离与跨平台路径复验
2026-09-18 本轮修改前检查点 `5a96623` 已推送。下方旧阶段的“当前进展”和待办按各自记录时间理解,以本节为最新状态;本轮没有替换正式程序,也不表示完整生产验收通过。
### 问题与修复
- [✓] 定位上一阶段管理任务测试的只读数据库错误:Linux `config.Default` 固定使用正式安装路径,HTTP 测试辅助函数仅传入临时目录并不能隔离数据库。现显式设置临时安装、配置、数据、日志、证书、备份目录及 SQLite DSN,不改变生产默认路径。
- [✓] 新增路径边界与数据库隔离测试,使用 SQLite `PRAGMA database_list` 核对实际打开的文件,并验证两个新数据库均为空且可分别写入相同任务 ID。
- [✓] 扩大全量测试后发现设置接口的保存失败用例以 Windows 反斜杠拼接路径,Linux 上未真正制造目录阻塞。该文件的路径拼接已改为 `filepath.Join`;重新验证了保存失败时凭据回退与令牌不消费。
- [✓] 已提交代码 `c8c7121` 包含独立管理证书续期任务、调度接入、审计及通知;当前仍只接通直连激活,代理模式会在申请前拒绝,不能标记 Nginx 自动续期完成。
### 本地验证
```sh
go test -p=2 ./...
go test -race -p=2 ./internal/httpapi -run 'TestReadyTestConfig|TestTaskAPI|TestManagement|TestTLSCertificate' -count=1
go test -race -p=2 ./internal/httpapi -count=1
go vet -p=2 ./internal/httpapi
```
以上均通过。后端全量测试在隔离辅助函数修改后执行;随后设置测试路径修改由 HTTP 全量竞态测试覆盖,耗时 63.647 秒。格式检查无待格式化文件。
### Linux 实机验证
测试以 `Scert` 用户运行,不清空或复用正式数据库。开启私有临时目录、私有网络、只读系统、隐藏用户目录和禁止提权;额外将 `/scert/data`、`/scert/conf`、`/scert/log`、`/scert/backups`、`/ssl`、`/ssl-scert-acceptance` 设置为不可访问,防止测试再次读取正式数据。测试只访问独立临时数据和本机隔离网络,不调用真实 DNS 或 ACME。
- 定向复验单元 `scert-http-isolation-20260918-0126.service` 成功退出,覆盖原失败管理任务、任务 API、管理激活、TLS 与新隔离用例。
- 首次全量单元 `scert-http-full-20260918-0128.service` 暴露上述 Windows 路径拼接问题,退出 1,原日志与测试程序保留。
- 修复后的全量单元 `scert-http-full-20260918-0132.service` 退出 0:169 项顶层测试通过、失败零、跳过零。运行 12.976 秒,CPU 时间 6.416 秒,峰值内存 24.1 MiB,Swap 为零;限制为内存 192 MiB、CPU 50%、64 个任务、180 秒运行上限。
最终测试程序为 `.runtime/http-isolation-20260918-0130.test`,远端路径为 `/scert/acceptance-20260917-1044/http-isolation-20260918-0130.test`。两端 SHA-256 一致:`2af3d17ba5bca2be1dd2052ddab8e5a836777caee1569a471d398f8cec8c1a5e`。使用新文件名上传,没有覆盖原失败证据。
结束时测试单元为 `inactive`、PID 为零。正式 Scert PID `588768`、Nginx `578671`、MySQL `588722` 与本轮检查起点一致;Scert 二进制摘要仍为 `f5b08bf0fb88f7b19281e91f942aa2995866f96420e422c015890b841bae6366`。这些 PID 与更早阶段不同,不能沿用旧记录声称跨阶段始终未重启。本轮没有执行正式服务重启。
`nginx -t` 通过,回环地址按管理域名严格验证 TLS 后,`/readyz` 返回 200、`status=ready`。Nginx 主配置与 Smail 站点摘要未变。入口仍为 `web.apmmk.cn` 的 80 跳转 443,443 反代本地 HTTPS 9004;本轮没有浏览器验收。
### 尚未完成的验收
- [✓] 本轮 HTTP 测试隔离和跨平台路径复审通过,不代表所有包的 Linux 全量测试已通过。
- [ ] 受限 Nginx 管理证书激活器、双层 TLS 就绪确认及失败回退。
- [ ] 包含管理存储隔离与续期调度的最新候选部署,以及真实目标机证书推送、通知送达、灾难恢复、容量和最终生产验收。
## 当前进展:管理证书存储隔离
2026-09-18 接续优化,修改前检查点 `6d7972f`、代码提交 `ec432f0` 均已推送。已修复同名业务证书更新可能清理管理入口引用目录的问题;正式程序尚未替换,本节不表示整体验收完成。
### 已验证的存储边界
- [✓] 新申请的管理域名证书存入 `<证书根目录>/management-domain/<域名>/<类型>/<版本>`,管理备份也使用独立的 `management-domain` 子目录;域名来自配置,不写死测试域名。
- [✓] 单域名、泛域名和 SAN 的管理申请与同名业务签发互不覆盖;业务当前证书读取不会选中管理候选。
- [✓] 兼容既有配置:通过服务启动配置中的管理证书或私钥路径保护旧业务目录中的引用,不迁移、不删除已部署的管理证书。
- [✓] 引用保护识别符号链接,不把相同字符串前缀的相邻目录误认为子目录;引用无法解析时,在备份或候选写入之前返回错误。
- [✓] 既有管理引用经过同名业务签发和导入仍保持全部文件字节不变;无管理引用的业务旧版本继续按原单当前版本规则清理。
- [✓] 无效管理存储根目录在凭据读取和外部调用之前被拒绝;阿里云保护域名检查仍优先执行。
### 验证证据
Windows 工作区执行以下命令全部通过:
```sh
go test -p=2 ./...
go test -race ./internal/certificate -run 'TestManagement|TestFileStore|TestServiceIssuesSetup|TestAliyun' -count=1
go vet -p=2 ./internal/certificate ./internal/httpapi ./internal/app ./internal/bootstrap
git diff --cached --check
```
从提交 `ec432f0` 对应源码交叉编译 Linux 测试程序,本地 `.runtime/certificate-isolation-ec432f0.test` 与远端 `/scert/acceptance-20260917-1044/certificate-isolation-ec432f0.test` 的 SHA-256 均为 `e4828059a3deff60d86e4b39c1bc0f310785e395c185cf5c8b9ebf373f9465c8`。
在真实 Linux 上,以 `Scert` 用户运行独立单元 `scert-isolation-ec432f0.service`:内存上限 128 MiB、禁用 Swap、CPU 50%、最多 48 个任务,开启私有临时目录、隔离网络、只读系统、禁止提权及隐藏用户目录。测试匹配上述定向表达式,包含 Linux 符号链接用例,全部通过且无跳过;单元退出零,运行 1.861 秒,峰值内存 7.8 MiB、Swap 为零。日志可通过 `journalctl -u scert-isolation-ec432f0.service` 查询。所有测试使用新建临时证书与目录,不访问正式数据库、真实 DNS 或 ACME。
结束时隔离单元 `inactive`、PID 为零;正式 Scert PID `579364`、Nginx `578671`、MySQL `549584` 均未变。通过本机回环地址访问 Nginx 的管理域名、严格验证 TLS 后,`/readyz` 返回 200 和 `status=ready`,`nginx -t` 通过。此项是部署机回环复核,不替代上一轮独立 Linux 公网探测,也不代表新程序已正式部署。
### 剩余验收项
- [x] 管理证书存储隔离及旧引用保护已完成本地与 Linux 组件复审,等待包含该提交的正式候选部署验证。
- [ ] 将管理证书纳入续期调度,补齐持久化任务、审计及通知;当前调度仍只处理业务域名。
- [ ] 在不扩大 Scert 服务权限的前提下接通 Nginx 证书更新,完成双层 TLS 激活、就绪等待和失败回退。
- [ ] 最新候选构建与 Linux 部署、真实目标机推送、通知送达、完整灾难恢复及容量验收。
## 前序进展:管理证书热更新
2026-09-18 接续优化,代码提交 `9074d83` 已推送;修改前检查点为 `230cbaa`。本节是续期基础能力的本地和 Linux 隔离验证,不是线上自动续期已完成。
### 已通过的组件检查
- [✓] HTTPS 服务使用不可变证书快照,新握手可加载更新后的证书,不需要重启 HTTP 服务。
- [✓] 新证书需通过私钥配对、有效期、管理地址覆盖和可信链校验;缺失文件、错误域名、过期、尚未生效及不可信链均拒绝,旧证书继续可用。
- [✓] 先持久化证书配置再切换内存;配置写入失败不修改内存证书和内存配置。
- [✓] 并发握手、无 SNI 的 IP 握手、显式回退和服务重启后读取新证书通过严格 TLS 验证。
- [✓] 管理证书申请保留旧版本目录,重复申请和初始化回退不会破坏先前引用的文件;普通业务证书仍保持原来的单当前版本规则。
- [✓] 修复新增证书解析步骤暴露的关闭顺序回归:已关闭服务再次启动优先返回关闭状态,不能重新监听。
热更新入口仅供进程内部调用,没有增加上传证书、任意路径或远程执行命令的 HTTP 接口。证书读取状态不再依赖业务域名列表,可供后续管理续期调度使用。
### 验证证据
Windows 工作区与提交内容一致,执行以下命令全部通过:
```sh
go test -p=2 ./...
go test -race ./internal/httpapi ./internal/certificate -run 'TestTLSCertificateRotation|TestManagementCertificatePreserves|TestShutdownBeforeStart' -count=1
go vet -p=2 ./internal/httpapi ./internal/certificate ./internal/app ./internal/bootstrap
git diff --cached --check
```
使用 Go 1.26.6 交叉编译两个 Linux 测试程序,上传前后 SHA-256 一致。远端文件均在 `/scert/acceptance-20260917-1044/`:
| 文件 | SHA-256 |
| --- | --- |
| `httpapi-tls-rotation-9074d83.test` | `a3859533e83f7502f711c172481ebfb5f812ef3282c14d3b547e7eaa4b2e377e` |
| `certificate-management-9074d83.test` | `bed93ab19132f762ab013d5ca58f9f4354dd4f187f86b8a25df5cb5e3b6bd1a0` |
两个独立 systemd 测试单元以 `Scert` 用户运行,内存上限 128 MiB、禁用 Swap、CPU 50%、最多 48 个任务,并开启私有临时目录、只读系统保护及禁止提权。TLS 更新与关闭生命周期测试、管理存储与业务存储回归全部退出零;峰值内存分别约 8.7 MiB 和 7.2 MiB。测试使用全新临时证书与配置,不访问真实 DNS、ACME 或业务数据库。
线上 Scert 未替换二进制、未重启,仍运行此前版本。只读复核时 Scert PID `579364`、Nginx `578671`、MySQL `549584`、8002 上的 Smail `574584` 均未变化,管理 HTTPS 就绪响应仍正常。未清空共享数据库,未删除既有补丁、构建产物或服务器文件。
### 继续验收的门槛
- [ ] 将管理证书纳入续期调度,并补齐持久化任务、审计及通知;当前仍不是自动续期闭环。
- [ ] 管理候选证书与业务存储隔离,防止同名业务证书操作清理管理入口引用的路径。
- [ ] 在不扩大 Scert 服务权限的前提下接通 Nginx 更新,完成双层 TLS 激活、就绪校验及失败回退。
- [ ] 从最新已验证源码构建候选版本,再进行 Linux 部署、真实目标机推送、通知送达、完整回退与容量验收。
## 已通过:标准域名入口
2026-09-18 00:01 起,按用户确认改为复用已开放的 80/443,通过 Nginx 域名站点转发。以下结果覆盖旧 8003/8004 方案的公网入口阻塞,不改变管理证书续期等尚未完成事项的结论。
- [✓] `http://web.apmmk.cn` 返回 308,跳转到 `https://web.apmmk.cn`,保留路径及查询参数。
- [✓] `https://web.apmmk.cn` 转发到 `https://127.0.0.1:9004`,入口和上游均严格验证证书。
- [✓] 应用 `server.proxy_https_port` 已改为 443;未改变上游监听端口、域名或数据库。
- [✓] 独立 Linux 主机 `8.137.23.95` 通过正常公网 DNS 访问 `/readyz` 返回 200、`status=ready`,HTTP 跳转同样通过。未使用指定解析、隧道或跳过证书验证。
- [✓] 新域名地址的桌面浏览器登录、刷新、退出及 401 检查通过。此项使用独立 SSH 隧道和严格 TLS,证据目录 `.runtime/apmmk-browser-20260918-standard/`,与上一项公网协议测试分开记录。
- [✓] 原 8003/8004 已无监听;9004 仍仅回环监听。此前 UFW 放行规则暂保留,本轮没有删除规则或文件。
- [✓] 共享 Nginx 主配置和 Smail 站点内容未变;Smail HTTPS 返回 200、TLS 验证结果为零。
备份位于 `/var/backups/scert-standard-20260918-0005-v2`,含受限配置备份及原站点。只修改 Scert 站点,不新增重复默认站点。首次切换时立即探测收到 SNI 拒绝,脚本自动恢复旧配置;随后加入平滑重载后的有界就绪等待,第二次切换成功。Scert 为加载对外端口配置经过重启,旧会话需重新登录;未重启同机 Smail、MySQL 或 Nginx 主进程。
完成时 Scert PID `579364`、重启计数零;Nginx 主进程 `578671`、Smail `574584`、MySQL `549584` 均与本轮开始一致。Nginx 配置语法检查通过,1440/1024 桌面测试无横向溢出、损坏图片或页面脚本错误。
Windows 本机直接访问仍出现 HTTPS 握手错误及 HTTP `Server: Beaver` 403,与独立 Linux 公网测试结果不同;该本地网络路径尚未定位,不宣称所有客户端网络均验证通过。本轮没有更改本机代理设置。
部署模板已改为共用端口的命名虚拟主机,外部完整地址使用 `__MANAGEMENT_ORIGIN__` 占位符,默认测试值不再包含 8004。管理证书自动续期与双层 TLS 更新、真实目标机推送及完整故障回退仍需后续完成,本次仅完成用户要求的标准入口调整。
## 历史结论:管理入口接续验收
以下为 2026-09-17 23:16 至 23:35 的接续结果,覆盖后文早期阶段关于域名未确认、初始化未完成和入口未部署的旧状态。不能将各阶段的通过项混为完整生产验收。
### 需求与任务状态
已确认管理域名可配置,本次使用 `web.apmmk.cn`,Nginx HTTP 8003 跳转 HTTPS 8004,上游为 `https://127.0.0.1:9004`。实际仓库为 `D:\Desktop\Scert`,不使用已不存在的旧目录。
- [✓] 复核真实初始化与正式证书,上游严格 TLS 校验下就绪。
- [✓] 独立 Nginx 站点部署、主机防火墙放行、未知域名拒绝、HTTP 跳转及双层 TLS 校验。
- [✓] 隧道访问线上站点的桌面浏览器登录、刷新会话、退出及接口鉴权测试。
- [✓] 六组跨域写入拒绝、退出后旧 Cookie 重放拒绝。
- [✓] Scert 开机自启已启用;未重启共享主机,不能视为重启演练通过。
- [x] 通用反代配置样例和部署说明已补充,等待整体生产复审。
- [!] 公网 8003/8004 在独立 Linux 主机访问超时,需核对云安全组或上游网络。
- [!] 管理证书未进入续期队列,且尚无上游 TLS 与 Nginx 的证书更新生效机制。
- [ ] 真实目标机证书推送、完整故障回退、通知送达与容量验收仍需完成。
### 已核对的运行版本
正式程序来自已推送提交 `b6d1b96426437df412b508e2bfff06b1dde02871`。其阿里云修复使用限定域名及精确 TXT 记录查询,以兼容当前 RAM 权限,同时对 DNS 主机名进行不区分大小写比较,记录 ID 和 TXT 内容仍严格匹配。本阶段没有再次替换正式二进制。
23:30 重新计算正式程序 `/scert/scert` 的 SHA-256 为 `f5b08bf0fb88f7b19281e91f942aa2995866f96420e422c015890b841bae6366`;升级前快照 `/var/backups/scert-pre-dns-b6d1b96/snapshot.tar.gz` 的摘要为 `5dae6d9444768243a9440e69dfaf41870cbbc2a85702d2d8923579763ca77715`。
当前管理证书为 Let's Encrypt YR2 签发,主题 `web.apmmk.cn`,有效期截止 `2026-12-16 14:14:04 UTC`。上游及 Nginx 探测均使用公共 CA 和主机名验证,没有使用跳过证书验证选项。
本阶段延续已完成初始化的独立测试数据集,没有清空数据库或重跑初始化。管理员密码仅保留在管理机 root 可读的受限凭据文件中,不进入代码、报告和截图。
### 反向代理部署
新增 `/etc/nginx/conf.d/scert-apmmk.conf`,保留 `/etc/nginx/nginx.conf` 与 `smail.conf`,部署前后内容摘要一致。配置备份位于 `/var/backups/scert-nginx-20260917-2320-v2`。第一次配置因本机参数传递导致空字符串引号损坏,被 `nginx -t` 拦截并移出有效加载目录,未重载;修正后语法检查成功才平滑重载。
8003 对已配置域名返回 308,示例路径 `/login?acceptance=1` 正确跳转至 `https://web.apmmk.cn:8004/login?acceptance=1`;未知域名返回 404。8004 的未知 SNI 拒绝握手。已配置域名通过 `proxy_ssl_verify on` 和 `proxy_ssl_name web.apmmk.cn` 验证 9004 上游,保留 `Host $http_host` 中的外部端口。
UFW 新增 TCP 8003、8004 的 IPv4/IPv6 入站规则;9004 仍只监听回环地址。`Scert.service` 此前为 disabled,现已执行 enable;Nginx 原已启用。没有重启 Scert、Smail、MySQL 或主机。
23:30 状态:Scert PID `576928`、重启零;Nginx 主进程 PID `575746`;Smail PID `574584`、重启零;MySQL PID `549584`、重启一。应用进程均与本阶段开始时一致。
### 浏览器与安全测试
本轮没有可调用的浏览器 MCP,使用独立 Playwright Chromium 上下文作为替代。新建上下文并禁止 Service Worker,不复用旧缓存。通过本机回环 SSH 隧道连接线上 Nginx 8004,浏览器 URL、TLS SNI 和证书校验仍使用真实管理域名;没有忽略 HTTPS 错误。这是线上应用测试,不是公网入口测试。
通过真实表单登录、页面跳转、刷新后会话保持、设置和用户等只读接口访问、退出、退出后 401 检查。Cookie 为 `Secure`、`HttpOnly`、`SameSite=Lax`、路径 `/`。1440 和 1024 桌面宽度没有横向溢出、损坏图片或页面脚本错误;截图仅代表这些桌面尺寸,不代表移动端验收或完整视觉复审。
本地证据目录 `.runtime/apmmk-browser-20260917-2330/` 含 `result.json`、`login-1440.png`、`dashboard-1440.png`、`dashboard-1024.png`,保持 Git 忽略。脚本不把管理员密码或 Cookie 写入结果。
远端协议脚本 `apmmk_security_acceptance.py` 使用严格 TLS 测试三个错误 Origin:异域、缺失外部端口、错误协议。对设置写入及退出各测试一次,共六次均为 403,且原会话仍有效;正常退出后重放原 Cookie 为 401。
100 次就绪请求、并发四,全部返回正常;中位约 28.57 毫秒、P95 约 38.91 毫秒。测试前主机可用内存约 779 MiB、Swap 使用约 155 MiB。这仅是短时只读探测,不替代签发、部署、数据库写入或持续负载的容量测试。
### 当前阻塞与续期风险
Windows 本机 DNS 返回代理软件虚拟地址 `198.18.0.51`,默认访问失败,不能据此单独归因服务器。另从目标机 `8.137.23.95` 只读访问管理机公网,两个端口均连接超时;指定真实 IP 的 HTTPS 再测也超时。同期管理机监听与 UFW 正常,短时抓包未捕获该外部连接。当前证据提示云安全组或其他上游网络限制,尚未读取云安全组规则,不能断言已定位唯一原因。本次在目标机只执行网络探测,没有部署、清库或重启操作。
续期 API 实际返回 `checked=0`、`selected=0`;域名列表为空。源码核对表明 `IssueForSetup` 只保存管理证书,而当前调度器遍历托管域名仓库;HTTP 服务调用静态 `ListenAndServeTLS`,Nginx 也引用固定版本路径。因此证书有效不等于管理证书能够自动续期。必须补齐续期调度、证书事务切换、双层 TLS 更新、失败保留旧证书和恢复验证后再解除该门槛。
## 历史阶段记录
## 范围与任务状态
2026-09-17 在管理机 `47.108.78.12` 执行当前版本的受控测试。`8.137.23.95` 仅为推送测试机,本记录不包含对它的操作。不运行固定安装器,不切换正式服务,不清空既有数据库,不修改共享 Nginx 或业务证书。
- [✓] 密码 SSH 登录并通过已有主机密钥校验。
- [✓] 核对管理机服务、工具、空间和健康接口。
- [✓] 独立干净克隆构建 Linux 包,本地与远程归档、测试程序 SHA-256 一致。
- [✓] PostgreSQL 18 第一轮超时后完成资源故障取证、残留集群停止及受限七场景重测。
- [✓] Linux Bootstrap 测试包及服务配置测试通过;修复地址族白名单后,候选程序加固启动、HTTPS 协议及重启失效检查通过。
- [✓] 初始化前三步的桌面浏览器检查、提示修复和 SMTP 输入框裁切复测通过;未完成整个初始化流程。
- [!] 授权 DNS 签发、初始化完成、正式切流和业务推送未验收。
## 发布来源
候选源码提交:`8990710ee145809eb3a937d3b4eb578adbdeb3fe`。发布清单 `source_dirty=false`,两个程序均内嵌相同提交及 `vcs.modified=false`。构建工具链为 Go `1.26.6`,目标 `linux/amd64`,`CGO_ENABLED=0`。
本地发布目录:`build/linux-production-20260917-1040/`。远程独立目录:`/scert/acceptance-20260917-1044/`。前端 39 项自动化测试通过,依赖安装审计为零漏洞;这些不是浏览器验收。
| 产物 | SHA-256 |
| --- | --- |
| `release.tar.gz` | `3eee5a62403d6dadcc1f36e105de8cfa1684f65b688af7a1e898c7269e475344` |
| `cli.test` | `7bf731c5dade9ceb24f385dd0bb4c54d1809e3e6a9400c710ecfe871c9de1d1f` |
本地发布清单 14 项与包内校验清单 12 项通过。远程两项上传文件哈希及解压后的 12 项校验通过,安装器 `bash -n` 通过,候选程序返回 `Scert 0.1.0`。归档成员清单只含相对路径下的普通文件及目录,可执行程序和安装器为 `0755`。
## 实机初查
10:43 至 10:44,通过可信 SSH 通道获得:
- Linux `7.0.0-29-generic`,`x86_64`,根分区剩余约 28 GiB。
- `Scert.service` 为 `active/running`,用户 `Scert`,PID `530794`,监听 `127.0.0.1:9004`。
- 已有 PostgreSQL `18.6` 官方工具,既有数据库端口 `5432` 不作为测试目标。
- `/healthz` 为 HTTP `200`;`/readyz` 为 HTTP `503`,原因 `setup_incomplete`。
- 正式 ACME 目录正常 TLS 校验下为 HTTP `200`,TCP 约 `0.206` 秒、TLS 约 `0.484` 秒。此前网络超时本次未复现,但目录可达不代表签发通过。
- 在用 `/scert/scert` SHA-256:`ce2438fdddb1e471184d42ec98374abe1e96f74d3687ec60ad493eb659d5eb43`。
## 测试执行与异常
远程测试命令先在新目录解压,执行包内校验、安装器语法检查、版本检查,然后以 `Scert` 用户运行 `TestPostgresRealBackupRestore`。测试使用 `/usr/lib/postgresql/18/bin`,产物根目录为 `/scert/acceptance-20260917-1044/postgres-artifacts`,测试超时为五分钟。
该测试自行创建回环随机端口集群、每场景全新数据库、SCRAM 密码和严格 TLS;不接受业务 DSN,完成时停止自己的集群并保留产物。
10:50 左右尚未收到测试命令结果,另建 SSH 连接在协议 banner 阶段超时。约 10:53 原连接收到测试退出码 `2`:成功恢复与证书失败回滚场景打印检查通过;第三个 `cancel` 场景尚在初始备份清单检查时触发总超时,栈位于 `validateBackupManifest` 的 `Lstat`,还未进入取消注入阶段。其余四场景未执行。不能将该结果归因为取消回滚逻辑故障,也不能报告整轮通过。
测试产物:`/scert/acceptance-20260917-1044/postgres-artifacts/scert-postgres-integration-2406225956/`。总超时导致测试清理回调不保证执行,随后只读检查请求也出现 `Timeout opening channel`。已请求仅停止该目录中的隔离集群,停止结果仍待确认。未向既有 PostgreSQL 服务或正式 Scert 发送停止命令。
恢复连接后必须先核对本轮测试进程、`postmaster.pid`、端口、系统负载及原服务状态,再决定是否重试。保留所有产物,不重跑解压命令覆盖已有目录,不在连接不稳定时继续启动额外测试负载。
## 资源故障恢复与受限重测
以下为 11:04 之后的续测结果,补充并更新上节当时尚未确认的状态。
- 主机约 1607 MiB 内存、无 Swap。内核在 11:00:20 记录全局 OOM,MySQL 被杀死;同时存在 journald、logind watchdog 超时。该证据确认系统发生内存故障,但不能仅凭受害进程或单次堆栈认定某个测试是唯一来源。
- MySQL 自动恢复为 PID `549584`,重启计数为 1;正式 Scert PID `530794` 未变。未重启这些服务,未调整 Swap、MySQL 配置或其他项目的资源设置。
- 核对首轮隔离集群目录、PID `549227` 及端口 `44387` 后,使用该集群的 `pg_ctl -m fast -w -t 30 stop` 正常停止;数据库与测试产物保留。既有 PostgreSQL 端口 `5432` 未操作。
- 测试辅助配置降低至 `shared_buffers=16MB`、`work_mem=1MB`、`maintenance_work_mem=16MB`、`max_connections=32`,禁用并行 worker。该变更只作用于新建隔离集群,不调整业务 PostgreSQL。
- 使用独立 systemd 临时单元串行运行:`MemoryMax=180M`、`MemorySwapMax=0`、`CPUQuota=50%`、`TasksMax=64`、`RuntimeMaxSec=300`、`KillMode=control-group`、`OOMPolicy=stop`,并设置 `GOMEMLIMIT=32MiB`、`GOMAXPROCS=1`。这些是本轮小数据演练参数,不是生产容量建议。
- 11:08:00 至 11:08:07,七个真实场景全部通过,用时 7.23 秒,单元退出码为 0。产物目录为 `postgres-bounded/scert-postgres-integration-710949348`;测试程序 SHA-256 为 `634666af045474b7dcec8f4b6c9eb325ef3fd95a28b628a2c388bbd304472f2e`。
- 此后内核另有其他项目测试单元的 `CONSTRAINT_MEMCG` OOM,不能记为本轮 Scert 测试或新的全局 OOM。共享机器仍需协调测试负载,不能因本轮通过就认为容量问题已消失。
## 加固启动缺陷与新候选
旧候选在保留地址族白名单的真实 systemd 环境中启动失败:`route ip+net: netlinkrib: address family not supported by protocol`。原因是初始化临时证书读取网卡地址需要 `AF_NETLINK`,原 unit 只允许 `AF_UNIX AF_INET AF_INET6`。此前单元测试只校验原有文本,未发现实际系统调用受限。
修复提交 `5573c64b209eae391c77f2a476ccc6784fd5b2e2` 同步更新生成式 unit 和 `deploy/systemd/Scert.service`,仅增加 `AF_NETLINK`;新增发布 unit 的安全属性断言。主服务仍为 `Scert` 用户,capability 集为空,保留 `NoNewPrivileges` 和其他加固;超级管理员恢复服务仍限制为本地 IPC,未增加网络地址族。
新候选从独立干净克隆构建:
- 本地:`build/linux-netlink-20260917-1119/Scert-20260917-V0.1.0-linux-amd64.tar.gz`。
- 远程:`/scert/acceptance-20260917-1044/release-netlink/`。
- 归档 SHA-256:`d592bf5753e88326de9b7ab7986338f07e92028743e75f4c1a9e9e2eb2f8a544`。
- 清单 `source_dirty=false`,程序内嵌 `vcs.modified=false` 和上述提交;本地清单、远程上传哈希及包内 12 项校验通过,安装器语法检查通过。
- 本地 CLI 与 servicehost 回归通过;Linux 原 Bootstrap 测试包通过,峰值内存约 16 MiB。新增 servicehost Linux 测试(含发布 unit 断言)也全部通过。Bootstrap 测试内部的 DNS/签发替身不代表真实外部签发。
- 11:22:21 至 11:22:27,使用新候选 CLI 再跑七个 PostgreSQL 真实场景,全部通过,用时 6.91 秒,退出码 0;新产物为 `postgres-bounded/scert-postgres-integration-1008171600`。取消场景仍采用测试程序进程内确定性注入,其他 CLI 场景使用新包中的可执行文件。
- 本地追加执行 Bootstrap、servicehost、管理员恢复和归档模块回归,全部通过;`git diff --check` 无格式错误。
## 候选程序实际协议检查
使用新候选程序、全新隔离配置和 SQLite,监听 `127.0.0.1:19017`,保持非 root 与系统加固。由于 Linux 初始化地址固定写入 `/run/scert/bootstrap-url`,测试单元使用私有 `/run` 及 `BindPaths` 将独立目录映射到该路径,不写正式运行目录。不运行固定安装器、不替换 `/scert/scert`。
通过项目:
1. 从可信 SSH 日志取得当前启动证书指纹,与实际 TLS 证书匹配;之后所有业务请求严格验证证书及 IP。探测证书时不发送令牌、Cookie 或业务请求。
2. 初始化页面和构建 JS/CSS 返回有效内容;这只是协议与资源检查,不是浏览器渲染验收。
3. 未登录的设置接口返回 401;跨域令牌兑换返回 403;正确令牌兑换成功后重复兑换返回 401。
4. Cookie 具有 `Secure`、`HttpOnly`、`SameSite=Strict`,路径限定为 `/api/v1/bootstrap`;令牌文件为 `0600`,URL 查询参数不携带令牌。
5. SQLite 写权限验证通过;缺少 DNS 令牌返回 400,随机无效 DNS 令牌返回 422;配置字节不变,不伪造初始化完成。
6. 重启本轮候选单元后,临时证书和一次性令牌均轮换,旧令牌及旧 Cookie 返回 401,新令牌可兑换。
7. 停止本轮候选单元后,端口不再监听,程序正常清理自己的初始化地址文件。隔离配置、密钥及测试产物保留在受限目录,禁止提交 Git。
协议检查脚本保留在远程 `bootstrap_protocol_netlink_v2.py`,不含固定密码或 Token;测试使用的临时令牌和随机管理员密码仅在内存中处理。第一次脚本把缺少 DNS 参数的预期码误写为 422,修正为 400 并单独增加无效令牌 422 检查后整轮通过,未因此修改应用校验规则。
11:24 收尾确认:没有本轮 PostgreSQL 进程残留,`19017` 无监听;正式 Scert 仍为 PID `530794`、重启次数 0,MySQL 仍为 PID `549584`、重启次数 1;正式程序 SHA-256 与初查一致。正式就绪接口仍报告 `setup_incomplete`。当时 `MemAvailable` 约 215 MiB,主机容量风险仍需持续监控。
## 桌面浏览器修复与最终候选复测
11:36 起继续使用独立本地配置和远端隔离目录验证,未清空业务数据库。浏览器测试连接本机 Windows 候选,Linux 实机测试通过 SSH 执行真实协议请求;二者不能混称为远程浏览器全流程验收。
修复与证据:
1. `4993199`:邮箱每 250 毫秒自动同步会清除全局错误,导致数据库未验证提示一闪即逝。自动同步现在保留错误,手动编辑仍可清除;新增回归先失败后通过。同步修复用户名与邮箱输入框上边缘错位,补齐数据库字段标识。
2. `f0c735a`:SMTP 固定四列在窄桌面裁切密码框,改用自适应网格,并补齐 DNS 明文复选框标识。1024×768 与 1440×900 的控件边界测量、实际截图检查通过。
3. `0bee64c`:真实浏览器发现数据库验证成功后仍显示此前的数据库步骤错误。只在验证成功时清除两类数据库提示,不清除密码等其他错误。新增三项回归后前端共 44 项全部通过;真实浏览器确认验证前错误持续显示、成功后错误消失且成功提示保留。
最终候选源码为 `0bee64ce8607831d025df8ea1ef52e39b57d026f`,从独立干净克隆构建,清单 `source_dirty=false`。本地归档为 `build/linux-db-message-20260917-1220/Scert-20260917-V0.1.0-linux-amd64.tar.gz`,SHA-256 为 `5a0b74167961cae3cbab9f7e47052df61ecf47c2c96584255c7c3842e470cb2a`。新 JS 为 `index-DYAdDhBP.js`,CSS 为 `index-B53m06GR.css`。
最终本地浏览器使用全新配置、数据库路径和 Chrome profile,监听 `https://127.0.0.1:19027`。先核对日志证书 SHA-256,再仅允许匹配 SPKI 的临时证书;没有全局忽略证书错误。页面实际加载新 JS,SQLite 在线写权限验证通过。两种桌面宽度下所有 SMTP 输入和下拉框均在标签与面板内,无整页横向溢出。截图保留于 `.runtime/browser-20260917-1223/smtp-1024.png` 和 `smtp-1440.png`,均已实际查看。此前版本的 SMTP/HTTP 必填提示与 DNS 缺凭据阻断也已检查;没有提交真实 DNS 凭据或发起 ACME 签发。浏览器另记录一条表单字段缺少标识的非阻断提示,未将其宣称为已解决。
最终 Linux 归档上传至 `/scert/acceptance-20260917-1044/release-message-0bee64c.tar.gz`,解压到独立 `message-0bee64c/release`。归档哈希、包内 12 项校验、安装器语法和程序版本检查通过。候选单元 `scert-message-0bee64c.service` 保留发布加固,限定 `MemoryMax=96M`、`MemorySwapMax=0`、`CPUQuota=25%`、`GOMEMLIMIT=48MiB`、`GOMAXPROCS=1`、`RuntimeMaxSec=900`,使用私有 `/run` 映射,不替换正式程序。
首次紧接启动执行协议脚本时未及时读到证书指纹而退出;候选就绪后重跑全部通过,没有为此放宽证书校验。通过项包括当前构建资源、严格 TLS 指纹、未登录 401、跨域兑换 403、一次性令牌不可复用、安全 Cookie、SQLite 写权限、重启证书与令牌轮换、旧会话失效及配置字节不变。脚本分别保留为远端 `linux_message_candidate.py` 和 `linux_message_protocol.py`。
约 12:22 收尾确认:最终 Linux 候选已停止、PID 为 0、19017 无监听;正式 Scert PID 仍为 `530794`、重启次数 0,MySQL PID 仍为 `549584`、重启次数 1;正式程序哈希不变。`MemAvailable` 约 320 MiB。正式 `/readyz` 仍为 `setup_incomplete`,共享机器的历史 OOM 和容量风险尚未解除。
## 尚未满足的生产准入条件
桌面浏览器前三步已部分验收,但合法授权域名与 RAM 的 DNS 写入清理、ACME 签发、初始化完成到正式入口的切换、登录与多用户、升级回退、目标机证书推送及最终业务访问仍未完成。本轮没有操作 `8.137.23.95`,没有改动 `pcux.cn` 及其子域。继续外部签发前需明确可写测试域名与凭据授权范围,SSH 密码不等同于 DNS 授权。正式服务仍返回 `setup_incomplete` 时不可正式切流;受控隔离测试通过不能替代上述验收。
## 账户边界修复与续测
12:30 至 12:50 继续审查用户管理、登录和修改密码,完成以下项目:
- [✓] `60b00f1` 修复用户管理可修改主超级管理员数据库行却不更新配置的问题。新增回归先复现用户名、邮箱、密码修改返回 200,以及新增第二个超级管理员返回 201,修复后均拒绝。
- [✓] 按已确认的单超级管理员需求,禁止通过用户管理新增或提权为超级管理员;禁止通过该接口修改主账号。既有历史账号不自动删除,不修改正式数据库。
- [✓] 主账号登录只接受配置凭据,数据库残留凭据不能取得随后立即失效的会话。保留持当前密码的自助改密;真实 SQLite 回归检查配置与数据库摘要一致、旧会话失效、旧密码拒绝、新密码登录及重建服务后的会话有效。
- [✓] 本地后端 HTTP API、仓库、认证、CLI、归档五个包回归通过;前端共 47 项测试通过。
- [✓] Linux 执行账户相关 9 个顶层测试及 5 个字段子场景全部通过,退出码 0。独立 systemd 单元限制 96 MiB、CPU 25%、无交换、单 Go 线程配额;运行 5.556 秒,峰值内存 17.7 MiB。测试程序 SHA-256 为 `24e779f320228b2c4afe2abcc5c781c2bcc0a490f502a6c49371d2a53a9226dd`。这是实机运行的自动化测试,不是生产账号全流程验收。
- [✓] 真实浏览器发现用户管理依赖父组件的作用域样式,弹窗实际落在普通文档流中且按钮无样式。`a3d0f6c` 在用户管理自己的 CSS 中补齐遮罩定位、表单、按钮和响应式约束。
- [✓] 最终发布资源使用本地只读界面夹具、独立 Chrome profile、端口 19028 验证。1024×768 和 1440×900 下弹窗为 620×439,8 个控件和按钮均在弹窗边界内;主账号没有编辑、禁用和删除按钮,新增角色没有超级管理员选项。两张修复后截图均已查看:`.runtime/users-browser-20260917/create-fixed-1024.png`、`create-fixed-1440.png`。夹具不是正式后端,不能据此宣称浏览器完整登录或写入通过。
最终候选从独立干净克隆构建,源码为 `a3d0f6c`,清单 `source_dirty=false`。本地归档 `build/linux-users-ui-20260917-1245/Scert-20260917-V0.1.0-linux-amd64.tar.gz`,SHA-256 为 `92b5d22b44a1b1a91ef6ba62eea9d48faebace26cc04e892aa230368e32f30ec`;资源为 `index-DsUM7ZTg.js` 和 `index-C7wT0wtw.css`。远程归档及包内 12 项校验通过,独立目录为 `/scert/acceptance-20260917-1044/users-ui-a3d0f6c/release`。
最终候选在发布 systemd 加固基础上使用私有运行目录、96 MiB 内存上限和 CPU 25% 配额,实际启动通过。严格 TLS 指纹、新构建资源、未登录 401、跨域 403、一次性令牌、安全 Cookie、SQLite 写权限、重启证书及令牌轮换、旧会话拒绝、配置字节不变全部通过。未发起 DNS 或 ACME 操作。辅助脚本为远端 `linux_users_ui_candidate.py` 和 `linux_users_ui_protocol.py`。
12:50 收尾确认:本轮两个 Linux 候选单元已停止,19017 无监听;本地夹具及专用 Chrome MCP 已停止,SSH 已关闭。正式 Scert PID `530794`、重启次数 0,MySQL PID `549584`、重启次数 1,正式程序哈希未变;正式 `/readyz` 仍为 `setup_incomplete`,可用内存约 313 MiB。升级回退、真实 DNS 签发、完整初始化与切流、目标机推送及业务验收仍未完成。原有测试产物全部保留。
## 安装失败回退修复与发布包续测
12:54 至 13:11 审查安装升级流程,修复提交为 `8c3d8786808cd142229af4b1c6422e9d638aa0c0`。修改前检查点 `9d3bd50` 和修复提交均已推送。没有在宿主机直接运行固定路径安装器,没有停止或替换正式服务。
### 缺陷与修复
旧包在真实 Linux 文件系统上复现四类问题:既有 Web 符号链接在二进制已经替换后才拒绝;二进制目标为目录时错误返回安装成功;第二个 unit 写入失败与 daemon-reload 失败均留下新旧发布文件混用。旧包四场景测试整体退出码为 1,不是成功验收。
新安装器提前检查文件和目录类型,保留 root 所有、`0700` 的 `/scert/.install-*` 副本;捕获安装失败后恢复发布文件并重新加载 systemd,不启动服务。Web 改为整目录替换,旧文件和失败现场只移动保留、不删除。回退自身失败时明确提示禁止启动服务。业务目录权限规范化、用户创建、断电和 SIGKILL 不在自动回退保证内,安装成功后的数据库迁移也不由此机制恢复,详见运维说明 4.2。
### 隔离方法与结果
使用仓库 `deploy/tests/install-linux-test.py`,在私有挂载命名空间中将新建测试目录映射到固定的 `/scert`、`/etc/systemd/system` 和本机既有 `/ssl-scert-acceptance`。源码以只读挂载保留,执行前核对挂载 inode。systemctl 被限定为测试桩,只允许状态检查和 daemon-reload,不能操作宿主服务。使用已有 Scert 用户,不创建新的宿主账号。
第一版脚本因宿主 `/ssl` 不存在在预检阶段退出,未执行安装器;随后显式指定已有证书挂载点,并把产物放在被覆盖路径以外的 `/var/tmp/scert-installer-20260917-1303`。所有产物保留。受控单元限制 `MemoryMax=96M`、`MemorySwapMax=0`、`CPUQuota=25%`、`TasksMax=32`、`RuntimeMaxSec=180`,与候选服务顺序执行。
最终发布包八场景均通过,整体退出码 0:
| 场景 | 安装器预期退出码 | 最终产物子目录 |
| --- | --- | --- |
| Web 符号链接拒绝 | 1 | `web-link-y4ivfj_n` |
| 二进制目标目录拒绝 | 1 | `binary-directory-o3gvmqpe` |
| unit 写入失败后恢复旧版 | 43 | `unit-failure-4c4c8ou_` |
| reload 失败后恢复旧版 | 42 | `reload-failure-bffej3sp` |
| 回退 reload 再失败,明确禁止启动 | 42 | `rollback-reload-failure-qrwm_v37` |
| 升级成功,旧 Web 资源退出生效目录 | 0 | `success-cgz9m71a` |
| 首次安装失败,发布文件回到缺失状态 | 43 | `fresh-failure-y_2axidy` |
| 首次安装成功 | 0 | `fresh-success-e7foazs_` |
每场景检查业务文件内容与证书文件不变、旧发布文件恢复、服务文件恢复或缺失状态、升级副本权限及服务操作白名单;各目录包含 `report.json` 和 `output.txt`。这是隔离文件安装与故障注入回归,不能等同于真实 systemd 升级、正式数据库迁移或完整业务回退验收。
### 新包与通用检查
- 独立干净克隆:`C:/Users/ikun/AppData/Local/Temp/scert-installer-20260917-1306`,源码 `8c3d878`,清单 `source_dirty=false`。
- 发布包:`build/linux-installer-20260917-1306/Scert-20260917-V0.1.0-linux-amd64.tar.gz`。
- SHA-256:`9c89140467264ee620bcd1ab4ba4fcea6a43430a4ea87fa6c7f91fa3be97c6ea`。
- 远程:`/scert/acceptance-20260917-1044/installer-8c3d878/release`;归档哈希、包内 12 项校验、安装器语法及版本检查通过。
- Windows 本地执行 `go test ./...`、`go test -race ./...`、`go vet ./...`、`go mod verify` 全部通过;前端 47 项测试通过。不能将这些表述为 Linux 全量测试。
- `govulncheck ./...` 及详细扫描退出码 0:调用符号和导入包无已知漏洞,依赖模块包含 `GO-2026-5932`,涉及未维护的 `golang.org/x/crypto/openpgp`,当前代码没有导入或调用该漏洞包,公告没有可用修复版本。不能写成依赖模块零漏洞。
- 新候选 `scert-installer-8c3d878.service` 使用发布加固、私有运行目录和 96 MiB 限制启动;严格证书指纹、当前资源、未登录 401、跨域兑换 403、令牌不可复用、安全 Cookie、SQLite 验证、重启证书与令牌轮换、旧会话失效和配置字节不变均通过。没有 DNS 或 ACME 操作。本轮不涉及页面修改,未重复浏览器渲染测试。
13:09 收尾检查:候选服务 inactive、PID 0、19017 无监听;正式 Scert PID `530794`、重启次数 0,MySQL PID `549584`、重启次数 1,正式程序 SHA-256 未变。可用内存约 314 MiB。正式回环端口 9004 使用 HTTP,先用 HTTPS 探测返回协议错误,改用实际 HTTP 后 `/readyz` 明确返回 `setup_incomplete`;这不是正式 TLS 验收。SSH 已关闭,所有测试进程结束。
- [✓] 安装器缺陷复现、修复、新发布包八场景复测及受限候选协议检查。
- [✓] 本地全量测试、race、vet、模块完整性及漏洞调用路径扫描。
- [!] 真实 systemd 升级与业务回退、DNS 签发、完整初始化、正式切流和目标机推送仍未完成。继续外部签发需要用户明确授权测试域名及 DNS 凭据范围,当前不可宣称具备正式切流条件。
## 安装前停服状态核验补充(2026-09-17)
### 现场恢复与需求
- [✓] 原工作目录 `D:/Desktop/SSL` 已不存在;当前项目在 `D:/Desktop/Scert`,起始提交仍为 `cf1fe15`,已跟踪文件无改动。未移动、删除或覆盖既有未跟踪产物。
- [✓] 修改前创建并推送中文检查点 `f04f873`。
- [✓] 管理机只读复核:正式 Scert PID `530794`、重启次数零;MySQL PID `549584`、重启次数一;候选服务停止;正式程序摘要与前轮一致。
- [!] 正式 `/readyz` 仍为 HTTP 503、`setup_incomplete`。管理机 443 由 Smail 占用,最终入口方案需要协调,不可直接占用或停止该服务。
### 缺陷与复现
旧安装器将 `systemctl is-active` 的所有非零退出都当成已停服;不能区分过渡状态和查询故障。使用旧包 `8c3d878` 在私有挂载命名空间注入启动中、停止中、主服务查询失败和管理服务查询失败,四个场景都错误返回零并替换隔离目录中的发布文件。
首次测试因产物目录不存在在预检阶段退出,没有执行安装器;创建全新产物目录后完成上述四场景复现。证据目录 `/var/tmp/scert-install-state-before-20260917-1530`,测试整体退出码一。路径中的时间后缀仅为标识,不代表实际执行时刻。
### 修复与验证
安装器改为显式检查六个 systemd 属性,未知状态和查询失败均拒绝。回归覆盖原八场景以及 active、activating、deactivating、reloading、failed 但仍有进程、排队任务、masked、首次不存在、failed 且已停服、查询失败、空输出、缺失字段和管理服务拒绝分支,共 22 场景。
- [✓] 本地 `go test -count=1 ./internal/admin` 通过,`git diff --check` 通过。
- [✓] 使用旧发布文件的独立副本加上新安装器,Linux 22 场景全部通过;退出码零,运行约 33.6 秒。
- [✓] 拒绝路径对整个安装目录快照(内容、类型、权限、所有者)不产生变化,unit 和证书保持不变,未执行 daemon-reload。
- [✓] 其余场景继续验证首次安装、旧资源退出生效目录、失败发布文件恢复、回退失败提示以及业务文件保护。
产物 `/var/tmp/scert-install-state-after-20260917-1535`。测试通过 systemd 临时单元限制内存 96 MiB、无 Swap、CPU 25%、32 任务、240 秒;systemctl 在安装器命名空间内仍为状态/故障注入桩。宿主 systemd 仅管理测试资源,正式 Scert、MySQL 和 Smail 不停止、不重启。
这证明安装器的状态分支和文件行为,不证明完整真实 systemd 升级、业务回退或最终发布包已验收;最终包验证需单独补记。未使用 DNS、ACME 或业务数据库。
### 发布与收尾
- [✓] 修复提交 `6b6fc126868731003e70e1225f6ae455f26fb235` 已推送,远程 main 已核对一致;本地 `go test -race -count=1 ./internal/admin` 通过。
- [✓] 本地与远程实测安装器 SHA-256 一致:`08b7b04411b9250414d0429b10e99a4642c750163febf3da65281b445eb7fa57`。
- [!] 干净克隆位于 `C:/Users/ikun/AppData/Local/Temp/scert-state-20260917-1532`。调用仓库发布脚本时被 PowerShell 执行策略拒绝,未绕过策略,未生成或上传本次新发布包。此前 `8c3d878` 发布包不包含本次停服检查修复,不能用于宣称最新版本验收。
- [✓] 15:32 收尾核查:测试 unit 为 inactive、PID 零、结果 success;正式 Scert PID `530794`、重启次数零,MySQL PID `549584`、重启次数一,正式程序摘要未变。可用内存约 320 MiB,19017 无监听,SSH 随后关闭。
- [!] 正式初始化、维护窗口内停服升级、业务回退、DNS 与 ACME、目标机业务推送及最终入口仍未验收。继续需要发布脚本按本机管理策略获准执行,并确认维护窗口、域名及 DNS 操作范围、与既有 Smail 不冲突的入口方案。
## 初始化探针修复与正式升级复验(2026-09-17 晚间)
### 需求与结论
- [✓] 接续升级后的在用服务,核对 HTTPS 身份、健康与就绪状态。
- [✓] 修复首次初始化阶段健康探针缺失,保持未初始化就绪门禁。
- [✓] 干净提交构建、发布包校验、Linux 隔离协议及正式停服升级。
- [✓] 升级前后受保护文件一致性、完整快照及独立目录解包验证。
- [!] 正式初始化、公共信任证书、外部入口、业务推送与真实故障回退仍未通过,不能正式切流。
### 缺陷与本地验证
接手时正式进程 PID 为 `570938`,监听 `127.0.0.1:9004`,处于新的 Bootstrap HTTPS 模式。通过当前 systemd 启动日志取得指纹,核对网络证书后,再使用该证书作为专用信任根检查证书名称和有效期。页面与状态接口返回 200,设置接口返回 401,但 `/healthz` 和 `/readyz` 均为 404。旧记录中的 HTTP 503 不适用于这个新启动链路。
修改前检查点 `48c2996` 已推送;首次推送认证失败,重试成功。修复提交 `c1ac3e91c07447243ead56fb0d8eca0b6ca8a938` 已推送,仅修改 Bootstrap 处理器及测试。新增回归先复现两个 404,再验证健康返回 200、就绪返回 503 和 `setup_incomplete`,拒绝错误方法,不缓存、不创建会话,也不提前打开业务数据库。
以下本地验证均通过:
```text
go test -p 1 -count=1 -timeout=3m ./...
go test -count=1 -timeout=3m ./internal/bootstrap ./internal/httpapi
go test -race -count=1 -timeout=3m ./internal/bootstrap
go vet -p=1 ./internal/bootstrap ./internal/httpapi
npm test
git diff --check
```
前端共 47 项通过。本轮没有修改前端,也没有重新进行浏览器渲染验收;没有执行新的全量竞态检查或漏洞扫描。
### 发布与隔离实机
从独立干净克隆构建,发布清单为 `source_dirty=false`,工具链 Go 1.26.6。首次因克隆缺少前端依赖而构建失败,保留失败目录;执行锁定依赖安装后改用全新输出目录构建成功。
- 本地归档:`build/linux-health-20260917-2205/Scert-20260917-V0.1.0-linux-amd64.tar.gz`。
- 归档 SHA-256:`b44f5b2a1125af08fd15356477f25ea4e17f8cf3a29d4f07ccbe8d63467d76b0`。
- 主程序 SHA-256:`b91c1a88382b29b6016488e340087cb25a205f684273c0d10d473803b10b9a41`。
- 本地清单 14 项、远端包内 12 项均核对通过。目录时间后缀是标识,不代表实际执行时间。
远端脚本及包位于 `/scert/acceptance-20260917-1044/`,最终隔离目录为 `health-c1ac3e9-v2`。候选限制内存 96 MiB、无 Swap、CPU 25%、32 任务,使用全新 SQLite 路径与私有运行目录,监听回环 19017。通过项包括 HTTPS 指纹与完整验证、新探针、构建资源、未登录拒绝、跨域拒绝、一次性令牌、安全 Cookie、SQLite 写权限、缺少 DNS 验证拒绝提交、重启证书及令牌轮换、旧会话失效、配置字节不变。
第一次协议检查通过后,附加断言错误地要求 SQLite 文件不存在。实际写权限测试会留下空数据库,现场查询无业务表且完整性正常;修正测试断言后,在新目录重跑全部通过,退出码零。没有为通过测试而修改业务实现,也没有删除第一轮产物。
同一源码交叉编译的 Bootstrap 测试程序在 Linux 执行,探针、路由隔离和取消清理通过。另执行 `TestHTTPSBootstrapCompletesSQLiteAndStartsApplication` 的两个场景,严格 TLS、SQLite 初始化、正式服务切换、登录、用户接口及夹具 `/readyz=200` 全部通过。两组受限测试峰值内存分别约 52.4 MiB 和 53.4 MiB。该集成夹具不执行真实云端 DNS 或 ACME,不能替代在用服务初始化。
### 备份与正式升级
21:50 左右核验此前快照 `/var/backups/scert-pre-upgrade-20260917-211804/snapshot.tar.gz`,SHA-256 为 `a9ba56f7e7e9dd3f6ac2ae2b13d11f00a3ae5ff7a749493aa09d52f99a198288`。配置、凭据、密钥和 SQLite 与快照逐项一致,业务数据库只读完整性检查为 `ok`。之后解包至全新 `0700` 目录 `/var/backups/scert-restore-check-c1ac3e9`,29 个文件与归档逐项一致,恢复副本 SQLite 完整性正常。未启动恢复副本,不把文件恢复验证称为完整业务回退。
约 22:00 正式更新到 `c1ac3e9` 前,再次停止 Scert 并创建完整快照 `/var/backups/scert-pre-health-c1ac3e9/snapshot.tar.gz`,逐文件核对通过,SHA-256 为 `7d6c39afbd43cb22ae95a98f94b5a2a4042aa86057b7ce7623698569c1e1bac9`。快照目录权限 0700,文件按 0600 创建。安装器退出码零,主程序摘要与新包一致,随后启动正式服务。
升级脚本检查以下结果全部通过:
- 正式 Scert 为 active/running,PID `572525`,重启次数零。
- 当前进程证书与可信 SSH 日志指纹一致,随后完整 TLS 验证下 `/healthz=200`、`/readyz=503 setup_incomplete`。
- Bootstrap 状态与页面 200,未登录设置 401,业务用户接口 404。
- 配置、数据库、密钥及证书文件的内容、权限、UID/GID 升级前后完全一致。
- MySQL PID `549584`、重启次数一,与升级前一致;443 的 Smail PID `570415` 不变。
- 所有本轮候选测试已停止,没有清空正式数据库、修改 DNS 或操作推送目标机。
### 剩余生产门槛
本轮正常 TLS 连接正式 ACME 目录已返回 HTTP 200,证书验证结果为零,旧的出站超时暂未复现;这仅证明当时目录连接成功,不代表签发成功或网络长期稳定。
在用配置仍是 `setup_ready=false`,后台域名仍为 `scert-accept-20260914.epwn.uno`,未对其发起签发。下一步需要用户明确管理域名、对应 DNS 写入和精确清理授权,以及不影响 Smail 443 的入口与维护范围,才能继续正式初始化、生产签发、外部 HTTPS/SNI、浏览器登录及真实业务推送。真实故障回退仍未完成,机器可用内存约 300 MiB,容量风险也未解除。
22:17 最终只读复核:Scert PID 仍为 `572525`、重启次数零,MySQL 状态不变;四个本轮测试单元均 inactive、PID 零,19017 无监听。备份摘要及 0700/0600 权限再次通过。此时 Smail 的 443 进程已经变为 PID `571735`,系统有 4095 MiB Swap、可用内存约 634 MiB,与升级时状态不同;本轮未操作 Smail 或 Swap,变更来源尚未核查,不能把升级时的“保持不变”延伸为整个时段。容量风险需依据新环境另做负载验证。