引入软件名录:清单写软件名,归档名也用软件名
新功能 - 新增 SoftwareCatalog.psd1 —— "软件名 -> 目录"映射表,BackupList.txt 里 直接写软件名即可,归档名也就是软件名(FooClolor.7z), 不再是 FooClolor_from_C_+Programs.7z 这种由路径拼出来的名字。 - 三种写法可混用:软件名、字面路径(现有清单无需改写)、软件名 @pathname。 - 名录支持前缀补全(legendary -> legendary_2.0.4,只认 <名>_* / <名>-*)、 Variants(同名目录在多处)、Includes(分文件维护)。 - tools/Rename-Archives.ps1:存量归档重命名,默认试运行,逐份大小校验并重建 manifest。 - 归档名重复直接报错,不再静默互相覆盖。 两套测试全绿:单元 42 项、端到端 23 项(新增名录命名/解析/迁移用例)。 过程中修掉的缺陷 - Resolve-BackupEntry 里 @pathname 与 Unresolved 分支顺序错误, @pathname 会被静默吃掉(改名后仍用软件名)。 - 源目录被删除时解析器丢掉 Sources,导致恢复端把软件名当路径、 报 "Cannot bind argument to parameter 'Path' because it is an empty string"。 恢复的语义恰恰是"源不存在就要还原回去",现在 Sources 照旧给出。 - 源存在性检查曾被漏掉,Get-Item 对不存在路径抛异常会中断整轮备份; 且不能用 Join-Path 探测——目标盘符不存在时它会直接抛异常。 - 计划任务脚本外的 Caller 需要 -DryRun 才能验,已实跑确认。
This commit is contained in:
1 parent
dbc0c00554
commit
045d51ac9c
9 files changed
+1330
-143
No files matched your search
@@ -1,7 +1,9 @@
|
||||
# BakNRet
|
||||
|
||||
把 `BackupList.txt` 里列出的目录 / 文件用 **7-Zip** 打包进 `Backups/`,并且能用 `Restore.ps1` 原样恢复的 Windows 备份工具。
|
||||
把 `BackupList.txt` 里列出的软件 / 目录用 **7-Zip** 打包进 `Backups/`,并且能用 `Restore.ps1` 原样恢复的 Windows 备份工具。
|
||||
|
||||
- 清单里**直接写软件名**即可(如 `FooClolor`),目录映射维护在 `SoftwareCatalog.psd1` 里。
|
||||
- 归档名就是软件名(`FooClolor.7z`),不再是 `FooClolor_from_C_+Programs.7z`。
|
||||
- 只依赖 PowerShell(5.1 或 7.x)与 7-Zip,无需安装模块。
|
||||
- 每个归档写完后做 `7z t` 内容校验,**先写临时文件、校验通过再原子替换**。
|
||||
- 每次运行产出可核对的 `Backups/manifest.json` 与 `logs/*.log`。
|
||||
@@ -24,9 +26,9 @@
|
||||
# 3b. 确认可以接受"有文件被占用而没打进归档"时,允许覆盖完整归档
|
||||
.\Backup.ps1 -Force -AcceptWarnings
|
||||
|
||||
# 4. 只备份 / 只恢复某几项(通配符匹配路径或归档名)
|
||||
.\Backup.ps1 -Only '*.ssh','C:\Programs\FooClolor'
|
||||
.\Restore.ps1 -Only '*Edge*' -Force
|
||||
# 4. 只备份 / 只恢复某几项(通配符匹配清单条目或归档名)
|
||||
.\Backup.ps1 -Only 'FooClolor','.ssh'
|
||||
.\Restore.ps1 -Only 'Edge' -Force
|
||||
|
||||
# 5. 恢复前先看计划(恢复会覆盖真实目录,务必先看一眼)
|
||||
.\Restore.ps1 -DryRun
|
||||
@@ -39,60 +41,125 @@
|
||||
|
||||
| 路径 | 作用 |
|
||||
| --- | --- |
|
||||
| `SoftwareCatalog.psd1` | **软件名 → 目录**的映射,清单里写软件名的依据 |
|
||||
| `BackupList.txt` | 备份 / 恢复共用的清单,唯一的"要备份什么"来源 |
|
||||
| `BackupConfig.psd1` | 目录、空间阈值、校验、加密等配置 |
|
||||
| `Backup.ps1` | 备份入口 |
|
||||
| `Restore.ps1` | 恢复入口 |
|
||||
| `Common.psm1` | 公共模块(日志、外部命令、解析、manifest) |
|
||||
| `Backup.ps1` / `Restore.ps1` | 备份 / 恢复入口 |
|
||||
| `Common.psm1` | 公共模块(日志、外部命令、解析、名录、manifest) |
|
||||
| `Backups/` | 归档与 `manifest.json`(已 gitignore) |
|
||||
| `logs/` | 每次运行的日志(已 gitignore) |
|
||||
| `tests/` | 单元测试与端到端验收 |
|
||||
| `tools/Register-BackupTask.ps1` | 注册 / 移除计划任务 |
|
||||
| `tools/Rename-Archives.ps1` | 把按路径命名的旧归档重命名成软件名(默认试运行) |
|
||||
|
||||
## SoftwareCatalog.psd1 —— 软件名 → 目录
|
||||
|
||||
```powershell
|
||||
@{
|
||||
FooClolor = 'C:\Programs\FooClolor'
|
||||
Kazumi = '%AppData%\com.example\Kazumi'
|
||||
'scoop-config' = '%UserProfile%\.config\scoop' # 含 - 或 . 的键必须加引号
|
||||
'.ssh' = '%UserProfile%\.ssh'
|
||||
}
|
||||
```
|
||||
|
||||
**含 `-` 或 `.` 的键一定要加引号**,否则 PowerShell 会把 `a-b` 解析成减法表达式并报
|
||||
`Missing '=' operator after key in hash literal`。这是最容易踩的一个坑。
|
||||
|
||||
两个便利特性:
|
||||
|
||||
1. **前缀补全**:写 `D:\Programs\legendary`,实际目录是 `legendary_2.0.4` 时会自动匹配。
|
||||
只认 `<名>_*` 与 `<名>-*`,不会把 `Legendary` 误配成 `LegendarySomething`。
|
||||
2. **同名目录在多处**时显式列出,所有位置都会打进同一个归档:
|
||||
|
||||
```powershell
|
||||
ImHex = @{
|
||||
Path = 'D:\Hex\ImHex'
|
||||
Variants = @('D:\Hex\ImHex', 'E:\Backup\ImHex')
|
||||
}
|
||||
```
|
||||
|
||||
**分文件维护**:用 `Includes` 引入其它名录文件(路径相对本文件):
|
||||
|
||||
```powershell
|
||||
@{
|
||||
Includes = @('SoftwareCatalog.games.psd1')
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## BackupList.txt 语法
|
||||
|
||||
```text
|
||||
<路径> [ :: <排除模式>[,<排除模式>...] ] [ @<标记> ]
|
||||
<软件名 或 路径> [ :: <排除模式>[,<排除模式>...] ] [ @<标记> ]
|
||||
```
|
||||
|
||||
| 部分 | 说明 |
|
||||
三种写法可以混用:
|
||||
|
||||
| 写法 | 说明 |
|
||||
| --- | --- |
|
||||
| 路径 | 支持 `%环境变量%`;可用双引号包裹(**引号只包路径**);`/` 与 `\` 等价;`#` 开头是注释 |
|
||||
| 排除模式 | 相对归档根目录(源目录的末级名)。分隔符 `,` 与 `;` 都可以 |
|
||||
| `!` 前缀 | 表示"任意层级下匹配这个名字",翻译成 7z 的 `-xr!`,例:`!*Cache` |
|
||||
| 标记 | `encrypt` = 用 7z 加密该归档,见下文「加密」 |
|
||||
| `FooClolor` | 软件名。去名录查目录,**归档名 = 软件名** |
|
||||
| `%UserProfile%\Documents\PowerShell` | 字面路径(含 `\` `/` 或 `%` 就按路径处理),归档名沿用 `<末级名>_from_<上级路径>` |
|
||||
| `FooClolor @pathname` | 软件名 + 强制用路径命名。适合想换到名录体系但暂时不想改归档名的条目 |
|
||||
|
||||
| 标记 | 作用 |
|
||||
| --- | --- |
|
||||
| `encrypt` | 用 7z 加密该归档,见下文「加密」 |
|
||||
| `pathname` | 用路径命名算法而不是软件名 |
|
||||
| `root=<名>` | 覆盖归档内的根目录名(默认源目录名) |
|
||||
|
||||
排除模式:相对归档根目录。以 `!` 开头表示"任意层级下匹配这个组件名"(7z 的 `-xr!`)。
|
||||
分隔符 `,` 与 `;` 都可以。**不要自己写引号**;模式里的空格会被自动转成 `?`。
|
||||
|
||||
几条已经踩过的坑(工具会处理,写的时候知道就行):
|
||||
|
||||
- **模式里不要写引号。** `-x!"路径"` 会让引号成为模式的一部分,结果是**永不匹配**。
|
||||
- **模式里的空格会被自动转成 `?`。** 7z 的排除模式不支持空格:`Default\Code Cache` 匹配不到任何东西,`Default\Code?Cache` 才可以。
|
||||
- **以第一个 `::` 为界切分。** `:` 在 Windows 路径里只可能是盘符,`::` 不会出现在真实路径里,所以整行被一对引号包住的历史写法(`"路径 :: 排除表"`)也能正确解析。
|
||||
- **改名即换归档。** 归档名由路径生成:`<末级名>_from_<上级路径用 + 连接>`。改动路径会生成新归档,旧归档需靠 `manifest.json` 找回。
|
||||
- **以第一个 `::` 为界切分。** `:` 在 Windows 路径里只可能是盘符,`::` 不会出现在真实路径里,所以整行被一对引号包住的历史写法也能正确解析。
|
||||
- **归档名重复会直接报错。** 归档名就是软件名,所以同一个软件写两遍会让两个条目互相覆盖 —— 脚本拒绝执行并提示。
|
||||
|
||||
## 归档命名与迁移
|
||||
|
||||
| 条目类型 | 归档名 |
|
||||
| --- | --- |
|
||||
| 软件名 | `<软件名>.7z` |
|
||||
| 字面路径 | `<末级名>_from_<上级路径用 + 连接>.7z` |
|
||||
| 软件名 + `@pathname` | 同字面路径 |
|
||||
|
||||
从旧版本升级时用重命名工具把存量归档搬过来(**默认试运行**、逐份大小校验、重建 manifest):
|
||||
|
||||
```powershell
|
||||
.\tools\Rename-Archives.ps1 # 先看计划
|
||||
.\tools\Rename-Archives.ps1 -Apply # 确认后执行
|
||||
```
|
||||
|
||||
## 恢复语义
|
||||
|
||||
- 用 `7z x` 解压到目标的**父目录**,覆盖同名文件。
|
||||
- **归档内部布局与历史完全一致**:根目录仍是源目录名(软件名只用于归档文件名)。
|
||||
所以恢复逻辑不需要"剥掉一层",现有归档也不会因为重命名而解不开。
|
||||
- **不做镜像同步**:目标目录里多出来的文件不会被删除。想得到"完全等于归档"的目录,请先清空目标。
|
||||
- 目标目录比归档新时**默认跳过**,需要覆盖就加 `-Force`。
|
||||
- `-WhatIf` / `-DryRun` 只打印计划;`-VerifyOnly` 只跑 `7z t`。
|
||||
- **排除规则只在下一份归档里生效**:已经生成的归档不会因为改了排除表而"变干净",需要重跑备份才会生成新归档。
|
||||
- **排除规则只在下一份归档里生效**:已经生成的归档不会因为改了排除表而"变干净"。
|
||||
|
||||
## manifest.json
|
||||
|
||||
`Backups/manifest.json` 以归档基础名为键记录每个条目:
|
||||
以归档基础名为键记录每个条目:
|
||||
|
||||
| 字段 | 含义 |
|
||||
| --- | --- |
|
||||
| `source` / `resolvedSource` | 清单里的原始路径(未展开环境变量)/ 实际路径 |
|
||||
| `source` | 清单里的原始写法(软件名或路径) |
|
||||
| `resolvedSource` | 展开后的路径 |
|
||||
| `roots` | 归档内的根目录名 |
|
||||
| `catalog` | 名录里记录的路径(便于追溯软件名到底指向哪) |
|
||||
| `archive` | 归档文件名 |
|
||||
| `action` | `backed-up` / `skip-unchanged` / `missing-source` / `invalid-path` / `failed` / `planned` |
|
||||
| `reason` | 跳过或失败的原因 |
|
||||
| `exitCode` / `verified` / `warnings` | 压缩工具退出码、是否通过 `7z t`、**当前在位归档**是否有警告 |
|
||||
| `attemptWarnings` | **本次尝试**是否报了警告(与 `warnings` 区分:保留旧归档时前者为 true、后者仍为 false) |
|
||||
| `sourceFiles` / `sourceBytes` / `archiveBytes` | 源文件数、源大小、归档大小 |
|
||||
| `startedAt` / `finishedAt` / `durationSec` | 时间与耗时 |
|
||||
| `lastSuccessAt` / `successCount` / `failCount` / `lastRestoreAt` | 历史 |
|
||||
| `encrypted` | 是否为加密归档(恢复时据此判断是否需要口令) |
|
||||
| `lastSuccessAt` / `successCount` / `failCount` / `lastRestoreAt` / `encrypted` | 历史与安全标记 |
|
||||
|
||||
`Restore.ps1` **优先用 manifest 定位归档**,查不到才退回"从文件名反推路径"。
|
||||
如果 `BackupList.txt` 丢了,`Restore.ps1` 会优先用 manifest 里的 `source` 自动重建。
|
||||
@@ -109,7 +176,9 @@
|
||||
BackupDir = 'Backups' # 相对路径按脚本所在目录解析
|
||||
LogDir = 'logs'
|
||||
SnapshotDir = 'Backups\snapshots'
|
||||
MinFreeSpaceGB = 5 # 低于此值告警;真放不下某个条目则跳过该条目
|
||||
SoftwareCatalog = 'SoftwareCatalog.psd1'
|
||||
CatalogMaxDepth = 5 # 前缀补全时最多向下找几层
|
||||
MinFreeSpaceGB = 5
|
||||
VerifyArchive = $true # 归档后跑 7z t
|
||||
ComputeHash = $false # 是否额外算 SHA256(大归档很慢)
|
||||
CompressionLevel = 9
|
||||
@@ -120,16 +189,16 @@
|
||||
}
|
||||
```
|
||||
|
||||
优先级:**命令行参数 > `BackupConfig.psd1` > 代码内置默认值**。也可以用 `-ConfigPath` 指定其它配置文件。
|
||||
优先级:**命令行参数 > `BackupConfig.psd1` > 代码内置默认值**,也可以用 `-ConfigPath` 指定其它配置文件。
|
||||
|
||||
## 加密
|
||||
|
||||
加密是**按需开启**的,默认关闭——一旦开启而口令丢失,备份就再也解不开。
|
||||
默认关闭 —— 一旦开启而口令丢失,备份就再也解不开。
|
||||
|
||||
```powershell
|
||||
# 方式一:只为个别条目加密(.ssh 里是私钥,最典型)
|
||||
# 在 BackupList.txt 里写成:
|
||||
# %UserProfile%/.ssh @encrypt
|
||||
# .ssh @encrypt
|
||||
|
||||
# 方式二:全部加密,改配置
|
||||
# Encryption = @{ Enabled = $true; PasswordFile = 'D:\secret\baknret.key' }
|
||||
@@ -142,31 +211,23 @@ $env:BAKNRET_PASSWORD = '...' # 或
|
||||
要求加密但取不到口令时,该条目会**明确失败**,绝不会退化成明文归档。
|
||||
恢复加密归档时同理:取不到口令就直接失败,不会让 7z 停在控制台等待输入(在计划任务里那会静默挂起)。
|
||||
|
||||
> ⚠️ 7-Zip 只接受命令行口令,口令在本机进程列表里会短暂可见。这是 7z 本身的限制,请自行权衡。加密保护的是"归档落在盘上之后"。
|
||||
> ⚠️ 7-Zip 只接受命令行口令,口令在本机进程列表里会短暂可见。这是 7z 本身的限制,请自行权衡。
|
||||
|
||||
## 计划任务
|
||||
|
||||
```powershell
|
||||
# 注册:每天 21:30 备份(默认用最高权限运行,因为部分目录需要管理员)
|
||||
.\tools\Register-BackupTask.ps1 -At '21:30'
|
||||
|
||||
# 只看将要注册什么
|
||||
.\tools\Register-BackupTask.ps1 -At '21:30' -DryRun
|
||||
|
||||
# 移除
|
||||
.\tools\Register-BackupTask.ps1 -Remove
|
||||
.\tools\Register-BackupTask.ps1 -At '21:30' -DryRun # 先看将要注册什么
|
||||
.\tools\Register-BackupTask.ps1 -At '21:30' # 注册
|
||||
.\tools\Register-BackupTask.ps1 -Remove # 移除
|
||||
```
|
||||
|
||||
任务会调用 `Backup.ps1`,脚本自身写日志并按失败数返回退出码,所以「上次运行结果」在任务计划程序里是可读的。
|
||||
任务调用 `Backup.ps1`,脚本自身写日志并按失败数返回退出码,所以「上次运行结果」在任务计划程序里可读。
|
||||
|
||||
## 测试
|
||||
|
||||
```powershell
|
||||
# 单元测试:解析、命名、排除参数翻译、命令行拼接、manifest、配置 + 真实 7z 集成
|
||||
.\tests\Run-Tests.ps1
|
||||
|
||||
# 端到端验收:备份 -> 验证排除 -> 删源 -> 恢复 -> 逐字节对拍(全程在临时目录)
|
||||
.\tests\Run-E2E.ps1
|
||||
.\tests\Run-Tests.ps1 # 单元测试 42 项
|
||||
.\tests\Run-E2E.ps1 # 端到端验收 23 项
|
||||
```
|
||||
|
||||
零依赖,不需要 Pester(本机只有 3.4.0,`Should -Be` 会直接语法错误)。
|
||||
@@ -175,29 +236,32 @@ $env:BAKNRET_PASSWORD = '...' # 或
|
||||
|
||||
| 问题 | 旧行为 | 现行为 |
|
||||
| --- | --- | --- |
|
||||
| `Start-Process -PassThru` 的 `ExitCode` 在 PowerShell 7.7.0-preview.4 上恒为 `$null` | 压缩明明成功(`Everything is Ok`)却报"压缩失败",`exit 2 → 删档重试` 的自愈分支永远不可达 | 用 `.NET Process` 继承控制台启动,退出码可靠 |
|
||||
| `Start-Process -PassThru` 的 `ExitCode` 在 PowerShell 7.7.0-preview.4 上恒为 `$null` | 压缩明明成功却报"压缩失败",`exit 2 → 删档重试` 的自愈分支永远不可达 | 用 `.NET Process` 继承控制台启动,退出码可靠 |
|
||||
| 排除模式写成 `-x!"路径"` | 引号成为模式的一部分,**排除对所有条目都失效** | 不再嵌引号;含空格自动转 `?`,`!` 前缀走 `-xr!` |
|
||||
| 解析器用 `;` 分隔,清单里写的是 `,` | 整串被当成一个模式,等于没有排除 | `,` 与 `;` 都支持 |
|
||||
| `^"([^"]+)"` 贪婪匹配 | 整行加引号的写法把排除表吞进路径 → 该条目被静默跳过,且 2.8 GB 归档成了找不到的孤儿 | 先按 `::` 切分再处理引号 |
|
||||
| `^"([^"]+)"` 贪婪匹配 | 整行加引号的写法把排除表吞进路径 → 该条目被静默跳过,2.8 GB 归档成了孤儿 | 先按 `::` 切分再处理引号 |
|
||||
| 归档名由路径拼出 | 加一条备份要自己算名字,名字随路径变动 | 清单写软件名,归档名就是软件名 |
|
||||
| 直接更新已有归档(7z `u`) | 固实归档下收益极小,且排除规则与"源里已删的文件"永远反映不到归档里 | 临时文件 → `7z t` 校验 → 原子替换 |
|
||||
| 没有校验、没有记录 | 中断留下的半个归档会被下次 `u` 续写;跳过/失败只有一行滚过去的 WARN | 校验 + 原子替换 + `manifest.json` + 日志文件 |
|
||||
| 结尾不 `exit` | 全部失败也返回 0,计划任务永远显示成功 | 有失败返回 1 |
|
||||
| 恢复用 `-Filter "$baseName.*"` | 含 `[` `]` 的路径会失配 | 精确比较 `BaseName`,且优先查 manifest |
|
||||
| tar 分支 `$LASTEXITCODE -ne 0 -and $proc.ExitCode -ne 0` | `$LASTEXITCODE` 是上一条原生命令的残留值,恰为 0 时把解压失败吞掉 | 三条分支统一走同一个取退出码的封装 |
|
||||
| 恢复没有干跑 | 直接覆盖 `E:\CodeSpace`、Edge User Data 这类真实目录 | `-WhatIf` / `-DryRun` / `-VerifyOnly` / `-Only` |
|
||||
| 没有 manifest、没有测试、没有 README、不是 git 仓库 | — | 都有 |
|
||||
| 没有名录、manifest、测试、README,不是 git 仓库 | — | 都有 |
|
||||
|
||||
## 设计取舍(有意为之,不是遗漏)
|
||||
|
||||
- **放弃 7z 的更新模式(`u`)。** 7z 默认固实压缩,`u` 本来就要重压大部分数据,收益很小,却让"排除规则改动"和"源里删掉的文件"永远进不了归档。现在每次都从零打包,代价是改动的条目会全量重压,换来的是归档与清单语义一致。
|
||||
- **不捕获压缩工具的输出。** 结构化记录交给日志与 `manifest.json`;捕获子进程 stdio 需要额外管道,在受限环境里会直接失败,而实时进度对交互式使用更有用。需要安静就跑 `-QuietTool`。
|
||||
- **有警告(退出码 1)时不覆盖完整的归档。** 被占用的文件(最典型的是正在运行的 Edge / 浏览器)会让 7z 返回 1,此时新归档是**不完整**的。实测:Edge 运行时打包,118 个文件读不到,其中包含 `Login Data`(密码)、`Cookies`、`History`、`Web Data` —— 恰恰是最不可再生的那部分。所以只要在位的归档是完整的(manifest `warnings=false`),脚本就**保留它、报失败、退出码 1**,不会用残缺归档把它换掉。确认可以接受再显式加 `-AcceptWarnings`。
|
||||
- **源路径不存在只算"跳过",不算失败。** 清单里留着已不存在的路径(例如换过盘的 `E:\CodeSpace`)是正常的,它会以 `missing-source` 记进 manifest。失败只统计真正打不开的条目。
|
||||
- **`DefaultExcludes` 只影响打包,不影响恢复。**
|
||||
- **放弃 7z 的更新模式(`u`)。** 7z 默认固实压缩,`u` 本来就要重压大部分数据,收益很小,却让"排除规则改动"和"源里删掉的文件"永远进不了归档。
|
||||
- **归档内部不套一层软件名目录。** 考虑过用暂存目录(硬链/复制)把归档根目录改成软件名,代价是多一次链接开销、实现复杂度上升,收益只是"解开包第一层好看"。归档名已经是软件名,包内保持源目录名也便于确认内容来源。顺带一提,7z 的 `-spf` 不是干这个的(它是 *use fully qualified file paths*)。
|
||||
- **不捕获压缩工具的输出。** 结构化记录交给日志与 `manifest.json`;捕获子进程 stdio 需要额外管道,在受限环境里会直接失败。
|
||||
- **有警告(退出码 1)时不覆盖完整的归档。** 被占用的文件会让 7z 返回 1,此时新归档是**不完整**的。实测 Edge 运行时打包,118 个文件读不到,其中包含 `Login Data`(密码)、`Cookies`、`History`、`Web Data`。所以在位归档完整时脚本**保留它、报失败、退出码 1**,确认可以接受再显式加 `-AcceptWarnings`。
|
||||
- **名录里的路径不存在时,恢复仍然可用。** 源被删掉正是要恢复的场景,所以解析器照旧给出 `Sources`,备份端则据此跳过。
|
||||
- **源路径不存在只算"跳过",不算失败。** 会以 `missing-source` 记进 manifest。失败只统计真正打不开的条目。
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 归档名与路径强耦合:改清单里的路径写法会生成新归档名。恢复时 manifest 能兜底,但**别轻易改已备份条目的路径写法**。
|
||||
- **改软件名等于换归档名。** 改名后旧归档不会被自动迁移,用 `tools/Rename-Archives.ps1` 或手动改名,并注意 manifest 里会留下旧键。
|
||||
- 路径里本来就含 `+` 或 `_from_` 时,仅靠文件名无法可靠反推路径,此时依赖 `manifest.json`。
|
||||
- `-Snapshot` 目前是"复制一份带时间戳的副本",不做自动轮转清理(`KeepCount` / `KeepDays` 已保留在配置里,清理逻辑尚未实现)。
|
||||
- `-Snapshot` 目前是"复制一份带时间戳的副本",不做自动轮转清理(`KeepCount` / `KeepDays` 尚未实现)。
|
||||
- 加密归档的常规备份/恢复不依赖 `RAR`;`RAR` 与内置 `ZIP` 分支仅作降级,未做加密支持(ZIP 明确拒绝加密请求)。
|
||||
- `Variants`(同名目录分散在多处)当前打包第一个位置,恢复时逐个位置各解压一份。
|
||||
Reference in new issue
Block a user