Add Unraid flash config to Borg preflight

This commit is contained in:
2026-05-25 19:36:16 +02:00
parent 09eeac51e1
commit d50b11784d
10 changed files with 88 additions and 9 deletions
+5 -1
View File
@@ -11,16 +11,18 @@ Use Borg as the single backup system for:
- critical file-backed application data
- secrets, keys, and reverse-proxy state
- database dumps generated before each Borg backup
- Unraid flash configuration artifacts generated before each Borg backup
Do not back up raw live database storage directories as the primary recovery artifact.
## Strategy
1. A pre-backup dump script runs on the host and writes fresh dumps to `/mnt/user/backups/borg/dumps/latest`.
1. A pre-backup dump script runs on the host and writes fresh dumps plus `unraid-flash-config.tar.gz` to `/mnt/user/backups/borg/dumps/latest`.
2. Borg backs up `/local/borg-dumps` plus the critical mounted paths below.
3. Borg retention handles history; the dump directory itself keeps only the latest artifacts.
The inclusion of `/local/secrets` is intentional: Borg is expected to cover disaster recovery for selected secret material as part of the current homelab restore strategy.
The Unraid flash configuration archive is intentional as well and must be treated as secret backup material.
## Service Inventory
@@ -41,6 +43,7 @@ The inclusion of `/local/secrets` is intentional: Borg is expected to cover disa
| Borg UI | SQLite dump + self-backup | `/local/borg-dumps`, `/local/appdata/borg-ui/data` |
| Komodo | config + Mongo dump | `/local/borg-dumps`, `/local/appdata/komodo/periphery`, `/local/appdata/komodo/core` |
| GitOps host automation | repo clone + Komodo workspaces + host-check state | `/local/services/homelab-infra`, `/local/services/stacks`, `/local/services/posture-check` |
| Unraid OS flash | generated config archive | `/local/borg-dumps/unraid-flash-config.tar.gz` plus checksum and manifest |
| Nextcloud | DB dump + file data | `/local/borg-dumps`, `/local/appdata/nextcloud/html`, `/local/nextcloud/data` |
| Grafana | SQLite dump + file data | `/local/borg-dumps`, `/local/appdata/grafana` |
| Filebrowser | file-backed state dump + file data | `/local/borg-dumps`, `/local/appdata/filebrowser` |
@@ -83,6 +86,7 @@ The live Unraid User Scripts execute repo scripts from `/mnt/user/services/homel
- Komodo MongoDB
- SQLite: `gitea`, `vaultwarden`, `speedtest-tracker`, `borg-ui`, `grafana`
- File-backed state: `filebrowser.bolt.dump`
- Unraid flash config: `unraid-flash-config.tar.gz` plus `unraid-flash-config.tar.gz.sha256`
## Explicitly Not Backed Up as Raw Live DB Files
+4
View File
@@ -14,6 +14,10 @@ Fresh dump artifacts are written to:
Borg UI should include `/local/borg-dumps` as a backup source.
The dump set also includes `unraid-flash-config.tar.gz`, a host-generated
archive of `/boot/config` plus checksum and manifest. Treat this archive as
secret backup material.
## Notes
- The script is written for host execution where `docker` is available.
+5 -4
View File
@@ -17,7 +17,7 @@ It should **not** be implemented as a Borg UI inline hook in the current design.
`pre-borg.sh` currently chains the host-side checks:
- `services/posture-check/posture-check.sh`
- `ops/borg-ui/scripts/pre-backup-dumps.sh`
- `ops/borg-ui/scripts/pre-backup-dumps.sh` including the Unraid flash config archive
- `ops/restore-tests/check-restore-freshness.sh`
The dump step assumes:
@@ -56,9 +56,10 @@ The intended sequence is:
1. Host wrapper checks posture.
2. Host script refreshes `latest` dump artifacts.
3. Freshness check verifies expected dumps.
4. Borg UI backs up `/local/borg-dumps` together with the rest of `critical_infra`.
5. Borg history preserves dump history, so the host only needs to keep the most recent dump set.
3. Host script writes `unraid-flash-config.tar.gz` plus checksum and manifest into the same dump set.
4. Freshness check verifies expected dumps and the flash config archive.
5. Borg UI backs up `/local/borg-dumps` together with the rest of `critical_infra`.
6. Borg history preserves dump history, so the host only needs to keep the most recent dump set.
## Current dump target
+56
View File
@@ -155,6 +155,56 @@ dump_file_copy() {
atomic_write "$output" "$tmp"
}
backup_unraid_flash_config() {
output="$LATEST_DIR/unraid-flash-config.tar.gz"
checksum="$LATEST_DIR/unraid-flash-config.tar.gz.sha256"
manifest="$LATEST_DIR/unraid-flash-config.manifest.txt"
tmp="$TMP_DIR/unraid-flash-config.tar.gz.tmp"
tmp_checksum="$TMP_DIR/unraid-flash-config.tar.gz.sha256.tmp"
tmp_manifest="$TMP_DIR/unraid-flash-config.manifest.txt.tmp"
if [ ! -d /boot/config ]; then
warn "Skipping Unraid flash config backup because /boot/config is missing"
return 1
fi
log "Backing up Unraid flash configuration from /boot/config"
rm -f "$tmp" "$tmp_checksum" "$tmp_manifest"
tar -C /boot \
--exclude='config/plugins/*/*.txz' \
--exclude='config/plugins/*/*.tgz' \
--exclude='config/plugins/*/*.tar' \
--exclude='config/plugins/*/*.tar.*' \
--exclude='config/plugins/*/*.zip' \
--exclude='config/plugins/*/*.md5' \
-czf "$tmp" config
chmod 600 "$tmp"
atomic_write "$output" "$tmp"
(
cd "$LATEST_DIR"
sha256sum "$(basename "$output")"
) > "$tmp_checksum"
chmod 600 "$tmp_checksum"
atomic_write "$checksum" "$tmp_checksum"
{
printf 'created_utc=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
printf 'host=%s\n' "$(hostname)"
if [ -f /etc/unraid-version ]; then
sed 's/^/unraid_/' /etc/unraid-version
fi
printf 'source=/boot/config\n'
printf 'archive=%s\n' "$(basename "$output")"
printf 'checksum=%s\n' "$(basename "$checksum")"
printf 'note=%s\n' 'Contains Unraid configuration and must be treated as secret backup material.'
printf 'excluded=%s\n' 'downloadable plugin package archives under /boot/config/plugins/*/'
} > "$tmp_manifest"
chmod 600 "$tmp_manifest"
atomic_write "$manifest" "$tmp_manifest"
}
dump_optional_pg_db() {
container="$1"
password="$2"
@@ -219,6 +269,8 @@ dump_mongo_container() {
main() {
need_cmd docker
need_cmd sqlite3
need_cmd tar
need_cmd sha256sum
ensure_dirs
# Shared PostgreSQL 17
@@ -272,6 +324,10 @@ main() {
# MongoDB
dump_mongo_container "komodo-mongo" "$LATEST_DIR/komodo-mongo.archive.gz"
# Unraid USB flash configuration. This is generated into the existing dump
# set so Borg carries it off-site together with the database artifacts.
backup_unraid_flash_config
log "Finished refreshing dump set in $LATEST_DIR"
}
@@ -14,7 +14,8 @@ $checks = @(
@{ Name = "gitea.sqlite.dump"; Path = Join-Path $DumpRoot "gitea.sqlite.dump" },
@{ Name = "vaultwarden.sqlite.dump"; Path = Join-Path $DumpRoot "vaultwarden.sqlite.dump" },
@{ Name = "speedtest-tracker.sqlite.dump"; Path = Join-Path $DumpRoot "speedtest-tracker.sqlite.dump" },
@{ Name = "filebrowser.bolt.dump"; Path = Join-Path $DumpRoot "filebrowser.bolt.dump" }
@{ Name = "filebrowser.bolt.dump"; Path = Join-Path $DumpRoot "filebrowser.bolt.dump" },
@{ Name = "unraid-flash-config.tar.gz"; Path = Join-Path $DumpRoot "unraid-flash-config.tar.gz" }
)
$reportChecks = @(
+2 -1
View File
@@ -34,7 +34,8 @@ for dump in \
gitea.sqlite.dump \
vaultwarden.sqlite.dump \
speedtest-tracker.sqlite.dump \
filebrowser.bolt.dump; do
filebrowser.bolt.dump \
unraid-flash-config.tar.gz; do
path="$DUMP_ROOT/$dump"
if [ ! -f "$path" ]; then
critical+=("DUMP_MISSING $dump")