来源:halo-kb/备份运维与安全.md## A. 备份与恢复(原文 7072 字符)

A. 备份与恢复

A-1 方案设计

三层,缺一层就恢复不出可用站点:

内容手段为什么必须
数据Halo 全部业务数据(extensions 391 行 + 电商等 34 张表)pg_dump -Fc(容器内本地 socket)内容、设置、用户、PAT、插件 configmap 全在库里
文件halo2/plugins/(22 个 jar)、themes/(theme-earth、Ethereal)、keys/(PAT 签名密钥、keystore)、indices/plugins/configs/tar -czf(排除 backups/logs/插件 jar 与主题不在库里keys/ 丢了所有 PAT 失效
配置docker-compose.yaml(含 DB 口令 + --halo.external-url)、容器环境变量快照cp -p + 脱敏 docker inspect没有它无法重建等价容器

halo2/attachments/ 不存在(实测)。原因:本实例至今没有上传过附件(附件数为 0), 该目录由 Halo 首次写入时惰性创建。不是配置错误,但意味着: ① 归档里自然也没有它;② 一旦开始上传附件,它会成为归档的主要体积来源, 需要复核 BACKUP_DIR 所在卷的剩余空间(当前 /vol1 剩 870 G,充裕)。

一致性说明(必须知情):备份期间 Halo 不停机,因此 DB dump 与文件归档之间不是同一个时间点。 在备份窗口内(实测 < 6 秒)若有人上传附件或安装插件,两侧可能不一致。 需要严格一致的场景只有两条路:① 短暂 docker compose stop halo 后再备份;② 启用备份插件。 本方案默认选「不停机」,因为窗口只有几秒,且本实例当前没有附件。

A-2 演练:实际执行记录

部署位置/vol1/1000/docker/halo/scripts/(NAS),本地源码在 H:\Works\halo-kb\scripts\nas\

cd /vol1/1000/docker/halo
./scripts/halo-backup.sh --dry-run     # 先演练,确认将执行什么
./scripts/halo-backup.sh               # 正式执行

真实输出(完整):

2026-09-13 21:13:21 [190144] ================ 开始备份 20260913-211321 ================
2026-09-13 21:13:21 [190144] 项目目录=/vol1/1000/docker/halo 备份目录=/vol1/1000/docker/halo/backups KEEP=7 INCLUDE_LOGS=0
2026-09-13 21:13:21 [190144] 1/6 pg_dump -Fc halo ...
2026-09-13 21:13:21 [190144]     -> 220K  /vol1/1000/docker/halo/backups/halo-db-20260913-211321.dump
2026-09-13 21:13:21 [190144] 2/6 pg_dumpall --globals-only ...
2026-09-13 21:13:21 [190144]     -> 4.0K  /vol1/1000/docker/halo/backups/halo-pg-globals-20260913-211321.sql
2026-09-13 21:13:21 [190144] 3/6 tar 归档 halo2/ ...
2026-09-13 21:13:25 [190144]     -> 61M  /vol1/1000/docker/halo/backups/halo2-20260913-211321.tar.gz
2026-09-13 21:13:25 [190144] 4/6 复制 compose 与 env ...
2026-09-13 21:13:25 [190144]     -> 未发现 /vol1/1000/docker/halo/.env(本部署的口令内联在 compose 里,属预期)
2026-09-13 21:13:25 [190144] 5/6 完整性自检 ...
2026-09-13 21:13:25 [190144]     dump TOC 条目数=319  归档条目数=220
2026-09-13 21:13:25 [190144] 6/6 生成校验和与清单 ...
2026-09-13 21:13:26 [190144] 保留策略:每类保留最近 7 份
2026-09-13 21:13:26 [190144] 备份完成 ✅  DB=10085 kB TOC=319 归档条目=220
2026-09-13 21:13:26 [190144] 产物目录:/vol1/1000/docker/halo/backups
2026-09-13 21:13:26 [190144] ================ 结束 20260913-211321 ================

real    0m5.519s

产物体积ls -la /vol1/1000/docker/halo/backups/):

1526         compose-20260913-211321.yaml
701          container-env-20260913-211321.txt
63749761     halo2-20260913-211321.tar.gz
224949       halo-db-20260913-211321.dump
511          halo-pg-globals-20260913-211321.sql
387          MANIFEST-20260913-211321.sha256
1401         MANIFEST-20260913-211321.txt
2397         backup.log

A-3 完整性验证(真实输出)

cd /vol1/1000/docker/halo/backups
sha256sum -c MANIFEST-*.sha256
halo-db-20260913-211321.dump: OK
halo-pg-globals-20260913-211321.sql: OK
halo2-20260913-211321.tar.gz: OK
compose-20260913-211321.yaml: OK
head -c 5 halo-db-20260913-211321.dump                     # → PGDMP
docker exec -i PostgreSQL pg_restore -l < halo-db-*.dump | head -12
; Archive created at 2026-09-13 13:13:21 UTC
;     dbname: halo
;     TOC Entries: 323
;     Compression: -1
;     Dump Version: 1.14-0
;     Format: CUSTOM
;     Dumped from database version: 15.4 (Debian 15.4-2.pgdg120+1)
;     Dumped by pg_dump version: 15.4 (Debian 15.4-2.pgdg120+1)
tar -tzf halo2-*.tar.gz | head -8
tar -tzf halo2-*.tar.gz | wc -l                                    # → 220
tar -tzf halo2-*.tar.gz | grep -cE '^\./halo2/(plugins|themes|keys)/'   # → 212
./halo2/keys/halo2.jks
./halo2/keys/pat_id_rsa
./halo2/keys/pat_id_rsa.pub
./halo2/plugins/configs/.device_id
./halo2/plugins/disabled.txt
./halo2/plugins/app-store-integration-1.18.1.jar
...
cmp compose-*.yaml /vol1/1000/docker/halo/docker-compose.yaml       # → 无输出(字节一致)

MANIFEST-20260913-211321.txt(全文):

# Halo 备份清单
备份时间戳        : 20260913-211321
主机              : FNOS
项目目录          : /vol1/1000/docker/halo
数据库            : halo(用户 halo)
数据库大小        : 10085 kB
public 表数量     : 34
extensions 行数   : 391
Halo 镜像         : registry.fit2cloud.com/halo/halo-pro:2.26
镜像 digest       : registry.fit2cloud.com/halo/halo-pro@sha256:982c894493238bc778b9629c7c672c287805c671ddd85702c1aeae20edde85a7
pg_dump 版本      : pg_dump (PostgreSQL) 15.4 (Debian 15.4-2.pgdg120+1)
Halo 容器启动于   : 2026-09-13T11:02:58.388412457Z
tar 归档排除      : ./halo2/backups ./halo2/logs
dump TOC 条目数   : 319
归档条目数        : 220

镜像 digest 落进清单是刻意的:恢复时「用哪个镜像」和「用哪份数据」同等重要, 而 tag 2.26 会滚动(见 B-2),只有 digest 能唯一确定版本。

A-4 恢复验证(非破坏性,已实际执行)

./scripts/halo-restore-verify.sh

真实输出:

== 1. 归档可解析性(pg_restore -l)==
   TOC 条目数 = 319
== 2. 来源库基线(生产库 halo)==
   表数=34  extensions=391
== 3. 建临时库 halo_rv_20260913211332 并恢复 ==
   pg_restore 报告 error 行数 = 0(仅作参考,判据见下一步)
== 4. 计数比对 ==
   表数量      生产=34  恢复后=34
   extensions 行 生产=391  恢复后=391

✅ 恢复验证通过:/vol1/1000/docker/halo/backups/halo-db-20260913-211321.dump
   临时库 halo_rv_20260913211332 已被删除,生产库 halo 全程未被写入。

real    0m43.866s

生产库未被触碰的反证

docker exec PostgreSQL psql -U halo -d postgres -Atc "select datname from pg_database order by 1"
# → halo / postgres / template0 / template1     (临时库已消失)
docker exec PostgreSQL psql -U halo -d halo -Atc "select count(*) from extensions"
# → 391                                          (计数未变)

判据(三条同时成立才算通过)

  1. pg_restore -l 能解析出 > 0 条 TOC;
  2. 恢复后 public 表数 == 生产库表数;
  3. 恢复后 extensions 行数 == 生产库行数。

⚠️ 只看「pg_restore 没报 error」不算验证。原理:--no-owner --no-privileges 下很多差异不会报错(对象缺失、权限丢失都可能静默),必须做计数比对当判据。 本次 error 行数 = 0 只是旁证,真正的判据是两列数字相等。

A-5 恢复流程(未执行,仅设计)

第 0 步不可省:先给「恢复前的现状」再做一份备份,否则唯一的退路被自己覆盖。

cd /vol1/1000/docker/halo
./scripts/halo-backup.sh                       # 0) 现状兜底
# 1) 停 Halo(不动数据库容器)
docker compose stop halo
# 2) 恢复数据库
docker exec -i PostgreSQL pg_restore -U halo -d halo --clean --if-exists --no-owner \
  < backups/halo-db-<ts>.dump
# 3) 恢复文件
tar -xzf backups/halo2-<ts>.tar.gz -C /vol1/1000/docker/halo
# 4) 恢复 compose(仅当 compose 本身也坏了)
cp -p backups/compose-<ts>.yaml ./docker-compose.yaml
# 5) 起服务并验证
docker compose up -d halo
./scripts/halo-healthcheck.sh

配套脚本:halo-restore.sh(双重确认:必须带 --confirm-restore 且交互输入 RESTORE)。 本次未运行——它会覆盖正在使用的站点。

已知会踩的坑(都是实测结论)

  • 宿主有独立 PostgreSQL 与 /usr/bin/psql → 裸 psql 连的是宿主库,不是 Halo 库。
  • pg_restore --clean --if-exists 会 DROP 目标库对象 → -d 后面的库名必须逐字核对为 halo
  • 恢复 compose 会覆盖其中的 DB 口令 → 若口令已轮换,要先改回现用值再 up

A-6 本方案自身的边界(诚实声明)

边界说明
非时间点一致见 A-1 末尾。要严格一致性需停 Halo 或启用备份插件。
未演练真实覆盖恢复明确按任务要求回避(会破坏在用的站点)。已用「临时库」替代验证。
未做异地副本产物全部在 /vol1 同一块卷上。整卷损坏 = 备份一起没。这是本方案最大的缺口,见 E-1。
归档排除 logs/默认排除(可用 INCLUDE_LOGS=1 打回)。理由:它无轮转、会无界增长,不适合塞进每份备份。
保留策略是删除行为只删脚本自己产生的、超出 KEEP(默认 7)份的旧产物,不碰任何其它文件。


来源:halo-kb/备份运维与安全.md## D. 运维检查清单(原文 2888 字符)

D. 运维检查清单

每个时段都给可复制粘贴的命令明确判据。涉及 root 的项已标注。

D-1 每日

#检查命令判据(不满足即需处理)
1健康总览/vol1/1000/docker/halo/scripts/halo-healthcheck.sh末行为 ✅ 全部通过,退出码 0
2备份是否真的跑了`ls -lt /vol1/1000/docker/halo/backups/ \head -5`最新 halo-db-*.dump 的时间戳是今天
3备份新鲜度halo-healthcheck.sh --quiet无输出(有失败项才会有输出)
4容器状态docker ps --filter name=Halo --filter name=PostgreSQL --format '{{.Names}}\t{{.Status}}'两行都含 (healthy)
5站点可达curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:28090/200
6备份运行日志tail -20 /vol1/1000/docker/halo/backups/backup.log末次为 备份完成 ✅,无 错误:
7新增 ERRORgrep -c ERROR /vol1/1000/docker/halo/halo2/logs/halo.log与昨日相比不新增(当前基线:5)

D-2 每周

#检查命令判据
1恢复验证(最重要)/vol1/1000/docker/halo/scripts/halo-restore-verify.sh输出 ✅ 恢复验证通过,且「表数量」「extensions 行」两侧相等(当前 34 / 391)
2产物校验和cd /vol1/1000/docker/halo/backups && sha256sum -c MANIFEST-*.sha256全部 OK(当前会检查最新一份;多份需逐个指定)
3保留策略是否生效`ls -1 /vol1/1000/docker/halo/backups/halo-db-*.dump \wc -l`<= 7(默认 KEEP=7)
4磁盘余量`df -h /vol1 \tail -1`使用率 < 85%(当前 77%,剩 870 G)
5日志体积du -sh /vol1/1000/docker/halo/halo2/logs/< 100 MB(轮转生效后应稳定在 ~几十 MB)
6备份目录总体积du -sh /vol1/1000/docker/halo/backups/< 1 GB(当前 ~61 MB / 份)
7插件状态控制台 → 插件;或核对 halo2/plugins/disabled.txt启用数 12、禁用数 10;有非预期变更需查证
8Docker 日志占用du -sh /vol1/docker/containers不应持续增长(当前 415 MB,上限 5×100 MB/容器)
9匿名暴露面复核curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:28090/apis/api.console.halo.run/v1alpha1/users必须是 302;出现 200 = 严重问题,立即排查

D-3 每月

#检查命令判据
1异地副本(当前缺失,见 E-1)自行选定目标;参考:rsync -av --delete /vol1/1000/docker/halo/backups/ <异地>:/path/异地存在当日 dump 且 sha256 一致
2恢复演练(完整)另一套环境按 A-5 全流程恢复一次站点能起来、内容与插件齐全
3版本与 digest 复核docker image inspect registry.fit2cloud.com/halo/halo-pro:2.26 --format '{{index .RepoDigests 0}}'digest 仍为 sha256:982c8944…变了说明 tag 被上游移动过(见 B-2)
4插件更新复核控制台 → 插件(逐个看是否有新版本)有更新先读更新日志,确认 requires 与 Halo 主体兼容
5凭据轮换(按需)见 C-3 表;至少每年一次轮换后立即 halo-backup.sh 并验证健康检查全绿
6权限复核stat -c '%a %U:%G %n' /vol1/1000/docker/halo/halo2/keys/* /vol1/1000/docker/halo/halo2 /vol1/1000/docker/halo/docker-compose.yamlkeys/ 下应为 600(当前 755,待收紧)、compose700
7PG 角色权限复核docker exec PostgreSQL psql -U halo -d halo -Atc "select rolsuper,rolbypassrls,rolreplication from pg_roles where rolname='halo'"建议目标为 `f\f\f(当前 t\t\t`,待收紧
8匿名文档内容扫描见 C-6 的 grep 判据无凭据/内网地址命中
9宿主监听面复核`ss -tln \grep -E '0\.0\.0\.0'`无新增的非预期 0.0.0.0 服务(当前含 28090、18080、18443、13001… 见 C-1)

技术文档库 / 数据备份与恢复演练 0 0 sushike
2026-09-13T12:41:59.152613063Z 2026-09-13T13:37:22.757254750Z