From e8f0a5ae19e72e7329735df674baaf2eed1fb74b Mon Sep 17 00:00:00 2001 From: Shuery <2463253700@qq.com> Date: Sun, 27 Sep 2026 14:10:04 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README=EF=BC=88?= =?UTF-8?q?=E5=8F=A3=E4=BB=A4=E4=B8=80=E8=8A=82=E3=80=81=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E3=80=81=E8=99=9A=E6=8B=9F=E6=9C=BA=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=EF=BC=89=E5=B9=B6=E4=BF=AE=E6=8E=89=E6=8B=86=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=97=B6=E7=95=99=E4=B8=8B=E7=9A=84=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E6=A0=87=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README 是被人真读的那份文档,而这一路改动让它有三处不再准确: 1. 「口令放在哪里」整节是错的:它写着"默认留空、必须放在仓库之外",而按你的决定,出厂默认值就是仓库根的 baknret.key(靠 .gitignore 兜住不被提交)。整节重写:如实写明这是一次取舍(省事 vs "不提交"依赖一个规则文件),并补上"相对路径按仓库根解析、不按工作目录"这条 —— 计划任务的工作目录是 C:\Windows\System32,按工作目录解析会让加密条目以"拿不到口令"失败而配置看上去没问题。关键提醒也改了:已经在仓库根的人什么都不用做,想搬走才需要动。 2. 测试项数过期:Pester 150 -> 183(含安全描述符套件)、零依赖 101 -> 111。 3. 新增「在 Hyper-V 虚拟机里验证」:这一层本机跑不到,而它覆盖的正是最需要真环境的那条路径 —— 安全描述符回放(把属主改成 NT AUTHORITY\SYSTEM、再靠 CREATOR OWNER 判断恢复后归谁)。附最近一次四步的结果与命令。 另修一个我在拆分 README 时留下的缺陷:4 个二级标题各重复了一次(块替换保留了原标题、又插入了带同名标题的指针行)。markdown 不经过 test.ps1,所以当时没被拦住 —— 现在按"相邻同标题只留一行"折掉,并复查为 0。 验收:test.ps1 9/9 全绿(7 与 5.1)。 --- README.md | 56 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 2e0d13e..f6a6367 100644 --- a/README.md +++ b/README.md @@ -70,20 +70,16 @@ | `tools/Rename-Archives.ps1` | 把归档名对齐到当前清单规则(默认试运行) | | `tools/Install-TestDependencies.ps1` | 把 Pester 与 PSScriptAnalyzer 装到仓库内的 `.tools/`(不动机器上的全局模块) | -## SoftwareCatalog.psd1 —— 软件名 → Slot 组 ## SoftwareCatalog.psd1 —— 软件名 → Slot 组 软件名录的语法:一个软件由若干 Slot 组成,每个 Slot 能写路径、排除、追加、加密与说明。详见 [software-catalog.md](./docs/software-catalog.md)。 ## BackupList.txt 语法 -## BackupList.txt 语法 清单每一行的完整语法:方向标记、修饰符、引号与记号边界。详见 [backup-list-syntax.md](./docs/backup-list-syntax.md)。 ## 归档布局、命名与迁移 -## 归档布局、命名与迁移 归档内的层级、归档名怎么来、以及改名与迁移时要注意什么。详见 [archive-layout.md](./docs/archive-layout.md)。 ## 安全描述符(属主 / ACL) -## 安全描述符(属主 / ACL) 属主与 ACL 为什么不是放进归档、而是旁挂一份 sidecar,以及恢复时怎么回放。详见 [security-descriptor.md](./docs/security-descriptor.md)。 ## 恢复语义 @@ -169,7 +165,7 @@ CompressionLevel = 9 ToolOutput = 'live' # live | quiet Snapshot = @{ Enabled = $false; KeepCount = 3; KeepDays = 30 } - Encryption = @{ Enabled = $false; PasswordFile = ''; EncryptHeaders = $true } + Encryption = @{ Enabled = $false; PasswordFile = 'baknret.key'; EncryptHeaders = $true } DefaultExcludes = @('!Thumbs.db', '!desktop.ini') } ``` @@ -204,16 +200,21 @@ $env:BAKNRET_PASSWORD = '...' # 或 ## 口令放在哪里 -**口令文件必须放在仓库之外。** `BackupConfig.psd1` 的 `PasswordFile` 默认留空,这不是遗漏: -出厂默认值指向仓库里的某个文件,等于鼓励把口令提交进版本库。`.gitignore` 里排除 `*.key` 只是 -第二道防线。三种给它口令的方式: +口令文件的出厂默认值是**仓库根的 `baknret.key`**,靠 `.gitignore` 的 `*.key` 兜住"不被提交"。 +这是一次取舍:留在仓库根最省事(口令与配置在一起,搬家时不会丢),代价是"不提交"这件事 +依赖一个规则文件 —— 谁写了 `git add -f`、或把整个目录复制到别处再 `git init`,口令就会跟着走。 -``` -powershell -# A. 环境变量(计划任务用这个最省事) +**相对路径按仓库根解析,不按当前工作目录。** 计划任务的工作目录通常是 `C:\Windows\System32`, +在那里 `Test-Path baknret.key` 为假 —— 如果按工作目录解析,加密条目会以"拿不到口令"失败, +而配置看上去毫无问题。 + +三种给它口令的方式(优先级见上一节): + +```powershell +# A. 环境变量(计划任务用这个最省事,也最不怕仓库被整体复制) $env:BAKNRET_PASSWORD = '...' -# B. 配置文件里指到一个仓库外的文件 +# B. 配置文件里指到一个仓库外的文件 —— 想更稳就走这条 Encryption = @{ Enabled = $false; PasswordFile = (Join-Path $env:USERPROFILE '.baknret.key'); EncryptHeaders = $true } # C. 单次指定 @@ -222,10 +223,10 @@ Encryption = @{ Enabled = $false; PasswordFile = (Join-Path $env:USERPROFILE '.b 取不到口令时,加密条目**明确失败**,绝不退化成明文归档 —— 这条行为没有放宽。 -> 从旧版本迁移的人注意:如果口令文件原先就放在仓库根(`baknret.key`),请把它移到仓库之外, -> 再按上面任一种方式指过去。搬之前可以先用 `pwsh -File .\Restore.ps1 -VerifyOnly -Only "WindowsTerminal" -KeyFile <新路径>` -> 验一下口令对不对(那份归档是加密的,口令不对会报错)。 - +> 从旧版本迁移:如果你的口令文件已经在仓库根(`baknret.key`),**什么都不用做**。 +> 想改用仓库外的位置,把它移走后按上面 B 或 C 指过去,并先用 +> `pwsh -File .\Restore.ps1 -VerifyOnly -Only "WindowsTerminal" -KeyFile <新路径>` 验一下 +> 口令对不对(那份归档是加密的,口令不对会报错)。 ## 计划任务 ```powershell @@ -242,11 +243,11 @@ Encryption = @{ Enabled = $false; PasswordFile = (Join-Path $env:USERPROFILE '.b | 套件 | 命令 | 需要什么 | 覆盖 | | --- | --- | --- | --- | -| **Pester 套件**(推荐) | `.\tests\Run-Pester.ps1` | Pester 5.0+ 与 7z | 150 项:清单语法(方向 / `::` / `:-` / `:+` / `:encrypt` / `@ Key='值'` / 整行引号与记号边界)、Slot 结构名录、归档命名、排除翻译(`-x!` / `-xr!` / `!re:`)、Slot 前缀分配、暂存、manifest / 配置 / 名录,外加**用子进程真正跑 `Backup.ps1` / `Restore.ps1`** 的端到端与回归 | -| 零依赖套件 | `.\tests\Run-Tests.ps1` | 只要 PowerShell + 7z | 101 项:同样的单元面,适合没装 Pester 的机器 | +| **Pester 套件**(推荐) | `.\tests\Run-Pester.ps1` | Pester 5.0+ 与 7z | 183 项(含安全描述符套件):清单语法(方向 / `::` / `:-` / `:+` / `:encrypt` / `@ Key='值'` / 整行引号与记号边界)、Slot 结构名录、归档命名、排除翻译(`-x!` / `-xr!` / `!re:`)、Slot 前缀分配、暂存、manifest / 配置 / 名录,外加**用子进程真正跑 `Backup.ps1` / `Restore.ps1`** 的端到端与回归 | +| 零依赖套件 | `.\tests\Run-Tests.ps1` | 只要 PowerShell + 7z | 111 项:同样的单元面,适合没装 Pester 的机器 | | 端到端验收 | `.\tests\Run-E2E.ps1` | 只要 PowerShell + 7z | 36 项:备份 → 确认排除生效 → 删源 → 恢复 → 逐字节对拍,含 `\` 布局、文件 Slot、方向标记与旧布局回退 | | **真实归档恢复演练** | `.\tests\Restore-Drill.ps1` | 只要 PowerShell + 7z | 12 个真实归档:解到临时目录再和活源逐字节对拍(全程不碰真实目录) | -| **安全描述符套件** | `.\tests\Run-Pester.ps1`(内含 `BakNRet.Security.Tests.ps1`) | Pester 5 + 7z | 25 项:排除判定与 7z `-x!/-xr!` 语义对齐、SID 映射边界(前缀 SID 不被误伤)、采集与 sidecar 往返、回放(`CREATOR OWNER` + 孤儿 SID + `protected` 逐字节一致)、以及真的用子进程跑 `Backup.ps1`/`Restore.ps1` 做端到端 | +| **安全描述符套件** | `.\tests\Run-Pester.ps1`(内含 `BakNRet.Security.Tests.ps1`) | Pester 5 + 7z | 含在上面 183 项里:排除判定与 7z `-x!/-xr!` 语义对齐、SID 映射边界(前缀 SID 不被误伤)、采集与 sidecar 往返、回放(`CREATOR OWNER` + 孤儿 SID + `protected` 逐字节一致)、以及真的用子进程跑 `Backup.ps1`/`Restore.ps1` 做端到端 | 演练会把"源在备份之后变过"和"归档/解压有问题"分开:内容不一致时看活源文件的修改时间, 晚于归档时间就算"源变了"(只提示),不晚于归档时间却内容不同才算失败。真实机器上的归档 @@ -309,6 +310,23 @@ powershell 不带 `-Settings` 的 `Invoke-ScriptAnalyzer -Severity Warning,Error` 会静默漏掉全部排版问题。 三条有意排除与 160 字符行长上限的理由见 [docs/adr/0008](./docs/adr/0008-analyzer-deviations.md)。 +### 在 Hyper-V 虚拟机里验证 + +上面几层都在本机跑。真正值得单独跑一遍的是 `tools/lab/`:它把仓库同步进一台**干净系统**的 +Hyper-V 虚拟机(`gsudo pwsh -File .\tools\lab\Lab.ps1 ...`),在那里跑完整流程。本机跑不到的 +路径只有它能覆盖 —— 最典型的是**安全描述符回放**:需要把某个目录的属主改成 `NT AUTHORITY\SYSTEM`、 +再靠 `CREATOR OWNER` 的继承规则判断恢复后归谁,而这件事只有在真 VM 里才敢做。 + +| 步骤 | 它验证什么 | 最近一次结果 | +| --- | --- | --- | +| `Lab.ps1 test -Suite all` | 三套仓库测试在干净系统上能不能跑 | Pester 183 / 零依赖 111 / 端到端 36,全部通过 | +| `Lab.ps1 backup` | 在 VM 内真跑 `Backup.ps1`(沙盒清单 + 配置) | 退出码 0 | +| `Lab.ps1 restore` | 用真实归档做恢复演练,**逐字节对拍** | 通过 6 / 失败 0(含连接点场景 4/4) | +| `Lab.ps1 acl-test` | 安全描述符:属主 / `CREATOR OWNER` / 安全指纹 | 全部通过 19 项(含负对照) | + +`acl-test` 里有一个**负对照**值得留意:只搬文件不回放安全描述符时,属主会变成"跑脚本的账户" +而不是原账户 —— 那条用例能证明这个演练分辨得出对错,而不是一路绿灯。 + ## 设计取舍(有意为之,不是遗漏) - **放弃 7z 的更新模式(`u`)。** 7z 默认固实压缩,`u` 本来就要重压大部分数据,收益很小,却让"排除规则改动"和"源里删掉的文件"永远进不了归档。