chore: 记录改造前基线
改造开始前的完整状态,作为可回退的基点。此提交之后:Pester 175 项、零依赖套件 101 项全绿;PowerShell 5.1 尚不可用(源文件无 BOM)。 包含此前未提交的在制品:安全描述符套件、Hyper-V 实验环境(tools/lab)、agent 约定(AGENTS.md 与 docs/agents)。 .gitignore 增加 *.key / *.pfx:BackupConfig.psd1 的 PasswordFile 此前默认指向仓库内的 baknret.key,一次 git add -A 就会把口令提交进版本库。默认值在后续提交中改为空。
This commit is contained in:
1 parent
7173e8ae10
commit
2937eb6652
32 files changed
+8775
-1691
No files matched your search
+95
-27
@@ -1,14 +1,20 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
把按路径命名的旧归档重命名成软件名,并重建 manifest.json。
|
||||
把归档名对齐到当前清单规则,并重建 manifest.json。
|
||||
|
||||
.DESCRIPTION
|
||||
重构前的归档名是 `<末级名>_from_<上级路径>`(如 FooClolor_from_C_+Programs.7z)。
|
||||
引入软件名录后,归档名默认就是软件名(FooClolor.7z)。这个脚本负责把存量归档搬过去。
|
||||
归档名由清单条目决定:
|
||||
|
||||
* 软件名条目 -> 归档名 = 软件名(`Edge.7z`);
|
||||
* 手写路径条目 -> 归档名 = `<末级名>_from_<上级路径>`(`FooClolor_from_C_+Programs.7z`)。
|
||||
|
||||
条目写法变过(把软件名改成手写路径、改名、合并条目……)之后,磁盘上的旧归档名就与当前
|
||||
规则对不上了 —— 那样的归档恢复不到,会被当成孤儿。这个脚本负责把它们搬过去。
|
||||
|
||||
做法:
|
||||
1. 遍历清单条目,算出"旧名"(路径命名算法)与"新名"(当前规则);
|
||||
2. 只在两者不同、且旧名归档确实存在时才处理;
|
||||
1. 遍历清单条目,算出**当前规则下的目标名**,以及一组**候选旧名**
|
||||
(路径命名算法 / 名录里的软件名 / manifest 里记过的归档名);
|
||||
2. 目标名已经存在就跳过;否则在候选旧名里找实际存在的归档;
|
||||
3. 重命名(不是复制,同卷上是元数据操作,不搬数据);
|
||||
4. 重建 manifest.json,把旧记录的历史字段(成功次数、SHA256 等)迁过去;
|
||||
5. 比对重命名前后的文件大小做完整性自检。
|
||||
@@ -74,6 +80,29 @@ function Find-ArchiveByBaseName {
|
||||
|
||||
$manifestOld = Read-BaknretManifest -Path $manifestPath
|
||||
|
||||
# manifest 里的历史归档名按 source / resolvedSource 建索引:
|
||||
# 条目写法改过(软件名 -> 手写路径、改名、合并)之后,键对不上了,
|
||||
# 但"这条清单行原本指向哪儿"通常还留在这两个字段里,靠它才能把旧归档接上。
|
||||
$manifestBySource = @{}
|
||||
foreach ($key in @($manifestOld.items.Keys)) {
|
||||
$record = $manifestOld.items[$key]
|
||||
if (-not $record) { continue }
|
||||
|
||||
$archiveName = $key
|
||||
if (($record.PSObject.Properties.Name -contains 'archive') -and $record.archive) {
|
||||
$archiveName = [System.IO.Path]::GetFileNameWithoutExtension([string]$record.archive)
|
||||
}
|
||||
|
||||
foreach ($field in 'source', 'resolvedSource', 'catalog') {
|
||||
if (-not ($record.PSObject.Properties.Name -contains $field)) { continue }
|
||||
$value = [string]$record.$field
|
||||
if ([string]::IsNullOrWhiteSpace($value)) { continue }
|
||||
$mapKey = $value.Trim().ToLower()
|
||||
if (-not $manifestBySource.ContainsKey($mapKey)) { $manifestBySource[$mapKey] = @() }
|
||||
$manifestBySource[$mapKey] += $archiveName
|
||||
}
|
||||
}
|
||||
|
||||
$plan = @()
|
||||
$unchanged = 0
|
||||
$missingOld = 0
|
||||
@@ -85,23 +114,9 @@ foreach ($line in (Get-Content -LiteralPath $BackupListPath)) {
|
||||
$item = ConvertFrom-BackupListLine -Line $line
|
||||
if (-not $item) { continue }
|
||||
|
||||
# 旧名 = 对"真实源路径"跑路径命名算法。
|
||||
# 注意:清单里现在写的是软件名,直接把它丢给 Get-BackupBaseName 会得到一个
|
||||
# 恰好和软件名一模一样的"旧名"(legendary -> legendary),于是永远算不出
|
||||
# 真正的旧名。必须先解析出真实路径。
|
||||
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth
|
||||
$newName = $resolved.BaseName
|
||||
|
||||
$oldNameSource = $item.Path
|
||||
if ($resolved.IsName -and $resolved.CatalogEntry) { $oldNameSource = $resolved.CatalogEntry.Path }
|
||||
if ([string]::IsNullOrWhiteSpace([string]$oldNameSource)) {
|
||||
# 数组形式的名录条目没有唯一的"原路径",推不出旧归档名,跳过即可
|
||||
Write-Host (" 跳过 {0}:名录条目是数组形式,算不出旧归档名" -f $item.Path) -ForegroundColor DarkGray
|
||||
continue
|
||||
}
|
||||
$oldName = Get-BackupBaseName -RawPath $oldNameSource
|
||||
|
||||
if (-not $oldName -or -not $newName) { continue }
|
||||
if (-not $newName) { continue }
|
||||
|
||||
if ($seenNew.ContainsKey($newName)) {
|
||||
$conflicts += "归档名 '$newName' 被 '$($seenNew[$newName])' 和 '$($item.Path)' 同时使用"
|
||||
@@ -109,11 +124,57 @@ foreach ($line in (Get-Content -LiteralPath $BackupListPath)) {
|
||||
}
|
||||
$seenNew[$newName] = $item.Path
|
||||
|
||||
$entries += [pscustomobject]@{ Item = $item; OldName = $oldName; NewName = $newName; Resolved = $resolved }
|
||||
# 候选旧名(按可能性排序):
|
||||
# 1. 路径命名算法(对名录条目要用 Slot 的 Path,直接拿软件名算出来的是错的);
|
||||
# 2. 名录里的软件名(旧规则:归档名 = 软件名);
|
||||
# 3. manifest 里为这条记录记过的归档名。
|
||||
$candidates = @()
|
||||
|
||||
if ($oldName -eq $newName) { $unchanged++; continue }
|
||||
$pathSource = $item.Path
|
||||
if ($resolved.IsName) {
|
||||
$slots = @($resolved.CatalogEntry.Slots)
|
||||
$pathSource = if ($slots.Count -eq 1) { $slots[0].Declared } else { $null }
|
||||
}
|
||||
if ($pathSource) {
|
||||
$derived = Get-BackupBaseName -RawPath $pathSource
|
||||
if ($derived) { $candidates += $derived }
|
||||
}
|
||||
if ($resolved.IsName) {
|
||||
$candidates += (Format-CatalogName -Name $item.Path)
|
||||
}
|
||||
if ($manifestOld.items.Contains($newName)) {
|
||||
$recorded = $manifestOld.items[$newName]
|
||||
if (($recorded.PSObject.Properties.Name -contains 'archive') -and $recorded.archive) {
|
||||
$candidates += [System.IO.Path]::GetFileNameWithoutExtension([string]$recorded.archive)
|
||||
}
|
||||
}
|
||||
|
||||
$oldFile = Find-ArchiveByBaseName -BaseName $oldName -Directory $BackupDir -Formats $supportedFormats
|
||||
# manifest 里"指向过同一个源"的历史归档名
|
||||
$lookupKeys = @([string]$item.Path)
|
||||
foreach ($entryItem in @($resolved.Items)) {
|
||||
if ($entryItem.Declared) { $lookupKeys += [string]$entryItem.Declared }
|
||||
if ($entryItem.RealPath) { $lookupKeys += [string]$entryItem.RealPath }
|
||||
}
|
||||
foreach ($lookupKey in $lookupKeys) {
|
||||
if ([string]::IsNullOrWhiteSpace($lookupKey)) { continue }
|
||||
$mapKey = $lookupKey.Trim().ToLower()
|
||||
if ($manifestBySource.ContainsKey($mapKey)) { $candidates += @($manifestBySource[$mapKey]) }
|
||||
}
|
||||
|
||||
$candidates = @($candidates | Where-Object { $_ -and $_ -ne $newName } | Select-Object -Unique)
|
||||
|
||||
$entries += [pscustomobject]@{ Item = $item; NewName = $newName; Resolved = $resolved; Candidates = $candidates }
|
||||
|
||||
if (Find-ArchiveByBaseName -BaseName $newName -Directory $BackupDir -Formats $supportedFormats) {
|
||||
$unchanged++
|
||||
continue
|
||||
}
|
||||
|
||||
$oldFile = $null
|
||||
foreach ($candidate in $candidates) {
|
||||
$foundCandidate = Find-ArchiveByBaseName -BaseName $candidate -Directory $BackupDir -Formats $supportedFormats
|
||||
if ($foundCandidate) { $oldFile = $foundCandidate; break }
|
||||
}
|
||||
if (-not $oldFile) { $missingOld++; continue }
|
||||
|
||||
$plan += [pscustomobject]@{
|
||||
@@ -190,10 +251,14 @@ $now = (Get-Date).ToString('o')
|
||||
foreach ($entry in $entries) {
|
||||
$file = Find-ArchiveByBaseName -BaseName $entry.NewName -Directory $BackupDir -Formats $supportedFormats
|
||||
|
||||
# 历史字段优先从新键取,其次从旧键(路径命名)取
|
||||
# 历史字段优先从新键取,其次从候选旧名里取
|
||||
$previous = $null
|
||||
if ($manifestOld.items.Contains($entry.NewName)) { $previous = $manifestOld.items[$entry.NewName] }
|
||||
elseif ($manifestOld.items.Contains($entry.OldName)) { $previous = $manifestOld.items[$entry.OldName] }
|
||||
else {
|
||||
foreach ($candidate in $entry.Candidates) {
|
||||
if ($manifestOld.items.Contains($candidate)) { $previous = $manifestOld.items[$candidate]; break }
|
||||
}
|
||||
}
|
||||
|
||||
$getPrevious = {
|
||||
param([string]$Field)
|
||||
@@ -205,7 +270,10 @@ foreach ($entry in $entries) {
|
||||
baseName = $entry.NewName
|
||||
source = $entry.Item.Path
|
||||
resolvedSource = [Environment]::ExpandEnvironmentVariables($entry.Item.Path)
|
||||
roots = @($entry.Resolved.Sources | ForEach-Object { $_.RootName })
|
||||
roots = @($entry.Resolved.Items | ForEach-Object { $_.TopName } | Select-Object -Unique)
|
||||
layouts = @($entry.Resolved.Items | ForEach-Object {
|
||||
[ordered]@{ name = $_.ArchivePath; kind = $(if ($_.IsFile) { 'file' } else { 'dir' }) }
|
||||
})
|
||||
catalog = $(if ($entry.Resolved.CatalogEntry) { $entry.Resolved.CatalogEntry.Path } else { $null })
|
||||
archive = $(if ($file) { $file.Name } else { $entry.NewName + '.7z' })
|
||||
action = $(if ($file) { 'backed-up' } else { 'missing-source' })
|
||||
@@ -218,7 +286,7 @@ foreach ($entry in $entries) {
|
||||
verified = $false
|
||||
warnings = $false
|
||||
attemptWarnings = $false
|
||||
encrypted = ($entry.Item.Flags -contains 'encrypt')
|
||||
encrypted = [bool]$entry.Resolved.Encrypt
|
||||
sourceFiles = (& $getPrevious 'sourceFiles')
|
||||
sourceBytes = (& $getPrevious 'sourceBytes')
|
||||
archiveBytes = $(if ($file) { $file.Length } else { $null })
|
||||
|
||||
Reference in new issue
Block a user