来源:
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 (计数未变)
判据(三条同时成立才算通过):
pg_restore -l能解析出 > 0 条 TOC;- 恢复后
public表数 == 生产库表数; - 恢复后
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 | 新增 ERROR | grep -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;有非预期变更需查证 | |
| 8 | Docker 日志占用 | 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.yaml | keys/ 下应为 600(当前 755,待收紧)、compose 为 700 ✓ | ||||
| 7 | PG 角色权限复核 | 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) |