feat(backup): 整仓离线备份上线 —— bundle 加密后传中兴 F50 上的 OpenList
背景:GitHub 被按 AUP 清空后,用户提出自己的中兴 F50(5G CPE + 内置 256GB)
上跑着 OpenList,想用它当第三层备份。实测可行,已落地并跑通。
为什么是 bundle 而不是直接推 git:
WebDAV 不支持原子的 rename/lock,bare repo 挂上去 push 会让对象写坏 ——
表面成功、实际随机损坏,可能几个月后才发现。bundle 是单文件顺序写,安全。
为什么加密(用户一度想省掉):
历史里含 .env、TLS 私钥、GITEA_SECRETS.md。介质是随身设备的内部存储,
明文 = 把密钥放在一台可能丢失/刷机/送修的机器上。
OpenList 的登录只保护「访问通道」,不保护「存储介质」——拆机就能读。
加密成本实测仅 1.9~2.9 秒、体积不变;且 CNB/Gitea 仍是明文副本,
口令丢失只是少一份备份,不构成单点。
实现(scripts/backup-bundle.mjs,零 npm 依赖):
- git bundle create --all → AES-256-GCM(Node 内置 crypto)
- 布局 magic(8)|salt(16)|iv(12)|密文|tag(16),scrypt(N=32768,r=8,p=1) 派生密钥
- 为什么不用 gpg:本机 gpg 2.4.9 在 Windows 下已损坏(反复 stale lockfile,
node spawn 直接 EBUSY);换内置 crypto 后零外部依赖且带认证标签
- 上传后可选 --verify:下载回来比对 sha256,端到端闭环
- --keep 控制远端保留份数;--decrypt 恢复;--list 盘点;--dry 不上传
定时任务(scripts/backup-task.cmd + Windows 计划任务 Blog-BundleBackup):
- 每天 03:30 本地时间,默认 --verify --keep 3
- InteractiveToken + LeastPrivilege、StartWhenAvailable、1h 超时、IgnoreNew 防重入
- 日志追加到 .workbuddy-backup/logs/backup.log,超 5MB 轮转
★ backup-task.cmd 内容必须全 ASCII:
cmd.exe 按当前代码页(zh-CN 是 GBK)解析批处理文件,而 node 输出 UTF-8。
UTF-8 中文注释会吞掉 CR/LF 并把下一行当命令执行 —— 实测踩到(一条 rem 被当命令跑)。
ASCII 是 UTF-8 子集,纯英文注释与 node 的中文输出混写不会乱。
同理不能用 %date%(含本地化星期),改用系统时间 API 取 ISO 格式时间。
日志轮转的 for 语句必须加 if exist 守卫,否则首次运行报「系统找不到指定的路径」。
实测(由计划任务实际拉起,非手工执行):
bundle 6.8~9.9s(605.5MB)/ 加密 1.9~2.9s / 上传 19.4~20.6s(29.4~31.3 MB/s)
/ 下载回读 sha256 一致,端到端退出码 0
恢复链路已演练:--decrypt → git bundle verify 报 "records a complete history"
→ 1008 提交完整一致
文档(架构总览.md):
- §5.3 从「Gitee 两条硬约束」扩写为「辅仓选型」,补入云效 Codeup 基础版对照
(Git 5GiB + 单文件命令行 200MB)—— 选它则 bin/linux/hugo 不必出库、
构建链路一行不用改
- 新增 §5.6 整仓离线备份(介质 / 为什么 bundle / 为什么加密 / 加密格式 / 用法 /
配置 / 定时任务 / 恢复流程 / 实测数据)
- §6 待办:#2 改为「辅仓选型未定」并说明 pushall 现状;#3 标注只有选 Gitee 才必须做;
#9 补记「不改写历史」的唯一障碍已随 GitHub 消失;新增 #10 备份已上线 + 三项安全待办
This commit is contained in:
1 parent
b39dc29753
commit
8abe93d17a
3 files changed
+501
-16
No files matched your search
@@ -0,0 +1,61 @@
|
||||
@echo off
|
||||
rem ============================================================================
|
||||
rem blog full-repo backup - entry point for Windows Task Scheduler
|
||||
rem
|
||||
rem scripts/backup-bundle.mjs does the real work:
|
||||
rem git bundle --all -> AES-256-GCM encrypt -> WebDAV upload to OpenList
|
||||
rem on the ZTE F50, then downloads it back and compares sha256.
|
||||
rem
|
||||
rem Notes:
|
||||
rem * This file is intentionally pure ASCII. cmd.exe parses a .bat/.cmd using
|
||||
rem the *current* code page (GBK on zh-CN) while node emits UTF-8. A UTF-8
|
||||
rem Chinese comment can swallow the CR/LF and break the following line
|
||||
rem (seen in practice: a rem line got executed as a command). ASCII is a
|
||||
rem subset of UTF-8, so plain-English text mixes safely with node output.
|
||||
rem * node resolution order: PATH -> WorkBuddy managed -> D:\nodejs
|
||||
rem * Log appends to .workbuddy-backup\logs\backup.log, rotated at 5 MB
|
||||
rem * .workbuddy-backup is git-ignored; logs and passphrase never enter git
|
||||
rem * Exit code is passed through (0 = success), visible in Task Scheduler
|
||||
rem ============================================================================
|
||||
setlocal
|
||||
|
||||
set "REPO=%~dp0.."
|
||||
if "%REPO:~-1%"=="\" set "REPO=%REPO:~0,-1%"
|
||||
set "LOG=%REPO%\.workbuddy-backup\logs"
|
||||
set "SCRIPT=%REPO%\scripts\backup-bundle.mjs"
|
||||
|
||||
if not exist "%LOG%" mkdir "%LOG%"
|
||||
|
||||
rem --- rotate log at 5 MB. Guarded by if exist: on the very first run the file
|
||||
rem does not exist yet and for/f would fail with a "path not found" error ---
|
||||
if exist "%LOG%\backup.log" for %%F in ("%LOG%\backup.log") do if %%~zF GTR 5242880 move /y "%LOG%\backup.log" "%LOG%\backup.prev.log" >nul 2>&1
|
||||
|
||||
rem --- locate node ---
|
||||
set "NODE="
|
||||
for %%P in (node.exe) do if not defined NODE set "NODE=%%~$PATH:P"
|
||||
if not defined NODE if exist "%USERPROFILE%\.workbuddy\binaries\node\versions\22.22.2-6\node.exe" set "NODE=%USERPROFILE%\.workbuddy\binaries\node\versions\22.22.2-6\node.exe"
|
||||
if not defined NODE if exist "D:\nodejs\node.exe" set "NODE=D:\nodejs\node.exe"
|
||||
|
||||
if not defined NODE call :fail "node not found in PATH, WorkBuddy managed dir, or D:\nodejs"
|
||||
if not exist "%SCRIPT%" call :fail "script not found: %SCRIPT%"
|
||||
|
||||
rem --- ASCII timestamp. Do NOT use %date%: on zh-CN it carries localized
|
||||
rem weekday text (e.g. Chinese "Tuesday") which would mix encodings ---
|
||||
set "STAMP=%TIME%"
|
||||
for /f "delims=" %%I in ('powershell -NoProfile -Command "[DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss')" 2^>nul') do set "STAMP=%%I"
|
||||
|
||||
rem --- arguments: use %* when given (handy for manual runs, e.g. --dry / --list) ---
|
||||
set "ARGS=%*"
|
||||
if "%ARGS%"=="" set "ARGS=--verify --keep 3"
|
||||
|
||||
echo. >> "%LOG%\backup.log"
|
||||
echo [%STAMP%] ===== backup start ===== >> "%LOG%\backup.log"
|
||||
"%NODE%" "%SCRIPT%" %ARGS% >> "%LOG%\backup.log" 2>&1
|
||||
set "RC=%ERRORLEVEL%"
|
||||
echo [%STAMP%] ===== backup end, exit=%RC% ===== >> "%LOG%\backup.log"
|
||||
|
||||
endlocal & exit /b %RC%
|
||||
|
||||
:fail
|
||||
echo [%TIME%] ERROR: %~1 >> "%LOG%\backup.log"
|
||||
endlocal & exit /b 1
|
||||
Reference in new issue
Block a user