feat(backup): kopia + borg system-config adapters (engine parity)
Mirror the restic system-config adapters for the other two engines, each in that engine's own convention, so system backup/restore/status/retention work on any location regardless of engine: - kopia: BackupSystemToLocation (--tags system:config), SystemSnapshotsJson (filter tag system:config), RestoreSystemLatest, ForgetSystem (per-source policy on $configs_dir + maintenance). - borg: BackupSystemToLocation (archive system-<host>-<ts>, comment system=config; no app is named "system" so the namespace can't collide), SystemSnapshotsJson (--glob-archives system-<host>-*), RestoreSystemLatest, ForgetSystem (prune the system-<host>-* glob). No dispatcher change needed — engineBackupSystem/SystemSnapshotsJson/ RestoreSystemLatest/ForgetSystem already resolve <engine><fn> per location. All three engines now define the full set; syntax clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
49aa5f01f3
commit
34bd6d7936
@ -50,3 +50,44 @@ borgBackupAppToLocation()
|
||||
borgEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
borgBackupSystemToLocation()
|
||||
{
|
||||
local idx="$1"
|
||||
local source_path="${configs_dir%/}"
|
||||
|
||||
if [[ ! -d "$source_path" ]]; then
|
||||
isError "System config path missing: $source_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
borgEnvExport "$idx" || return 1
|
||||
|
||||
local host_tag="${CFG_INSTALL_NAME:-libreportal}"
|
||||
# No app is named "system", so the "system-<host>-*" archive namespace can't
|
||||
# collide with an app's archives.
|
||||
local archive
|
||||
archive=$(borgArchiveName "system" "$host_tag")
|
||||
local comment="system=config host=$host_tag engine=libreportal"
|
||||
|
||||
local loc_name
|
||||
loc_name=$(resticLocationName "$idx")
|
||||
isNotice "Snapshotting system config → $loc_name (archive: $archive)" >&2
|
||||
|
||||
runBackupOp borg create \
|
||||
--comment "$comment" \
|
||||
--compression auto,zstd \
|
||||
"::$archive" \
|
||||
"$source_path"
|
||||
local rc=$?
|
||||
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
isSuccessful "System config backed up to $loc_name: $archive" >&2
|
||||
echo "$archive"
|
||||
else
|
||||
isError "System config backup to $loc_name failed" >&2
|
||||
fi
|
||||
|
||||
borgEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
@ -38,3 +38,41 @@ borgForgetApp()
|
||||
borgEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
borgForgetSystem()
|
||||
{
|
||||
local idx="$1"
|
||||
|
||||
if resticLocationAppendOnly "$idx"; then
|
||||
isNotice "$(resticLocationName "$idx") is append-only — skipping forget for system config"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local keep_last keep_daily keep_weekly keep_monthly keep_yearly
|
||||
keep_last=$(resticRetentionFor "$idx" KEEP_LAST)
|
||||
keep_daily=$(resticRetentionFor "$idx" KEEP_DAILY)
|
||||
keep_weekly=$(resticRetentionFor "$idx" KEEP_WEEKLY)
|
||||
keep_monthly=$(resticRetentionFor "$idx" KEEP_MONTHLY)
|
||||
keep_yearly=$(resticRetentionFor "$idx" KEEP_YEARLY)
|
||||
|
||||
borgEnvExport "$idx" || return 1
|
||||
|
||||
local host_tag="${CFG_INSTALL_NAME:-libreportal}"
|
||||
local args=(prune --glob-archives "system-${host_tag}-*")
|
||||
[[ -n "$keep_last" ]] && args+=(--keep-last "$keep_last")
|
||||
[[ -n "$keep_daily" ]] && args+=(--keep-daily "$keep_daily")
|
||||
[[ -n "$keep_weekly" ]] && args+=(--keep-weekly "$keep_weekly")
|
||||
[[ -n "$keep_monthly" ]] && args+=(--keep-monthly "$keep_monthly")
|
||||
[[ -n "$keep_yearly" ]] && args+=(--keep-yearly "$keep_yearly")
|
||||
|
||||
isNotice "Applying retention for system config on $(resticLocationName "$idx")"
|
||||
runBackupOp borg "${args[@]}"
|
||||
local rc=$?
|
||||
|
||||
if [[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" && $rc -eq 0 ]]; then
|
||||
runBackupOp borg compact
|
||||
fi
|
||||
|
||||
borgEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
@ -29,6 +29,26 @@ borgRestoreSnapshot()
|
||||
return $rc
|
||||
}
|
||||
|
||||
borgRestoreSystemLatest()
|
||||
{
|
||||
local idx="$1"
|
||||
local target_dir="$2"
|
||||
local host="${3:-$CFG_INSTALL_NAME}"
|
||||
|
||||
borgEnvExport "$idx" || return 1
|
||||
local snapshot_id
|
||||
snapshot_id=$(runBackupOp borg list --json --glob-archives "system-${host}-*" --last 1 2>/dev/null \
|
||||
| grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
borgEnvUnset
|
||||
|
||||
if [[ -z "$snapshot_id" ]]; then
|
||||
isError "No system-config archive found in $(resticLocationName "$idx") for host=$host"
|
||||
return 1
|
||||
fi
|
||||
# Whole-archive extract into staging (no include subpath).
|
||||
borgRestoreSnapshot "$idx" "$snapshot_id" "$target_dir"
|
||||
}
|
||||
|
||||
borgDumpFile()
|
||||
{
|
||||
local idx="$1"
|
||||
|
||||
@ -55,6 +55,50 @@ print(json.dumps(out))
|
||||
fi
|
||||
}
|
||||
|
||||
borgSystemSnapshotsJson()
|
||||
{
|
||||
local idx="$1"
|
||||
local host_filter="$2"
|
||||
|
||||
borgEnvExport "$idx" || return 1
|
||||
|
||||
local glob="system-${host_filter:-*}-*"
|
||||
local raw
|
||||
raw=$(runBackupOp borg list --json --glob-archives "$glob" 2>/dev/null)
|
||||
local rc=$?
|
||||
borgEnvUnset
|
||||
[[ $rc -ne 0 || -z "$raw" ]] && { echo "[]"; return $rc; }
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
echo "$raw" | jq -c '[.archives[] | {
|
||||
id: .id,
|
||||
short_id: .name,
|
||||
time: .time,
|
||||
hostname: .hostname,
|
||||
tags: (.comment | split(" ") | map(select(test("^[a-z_]+=")))),
|
||||
paths: []
|
||||
}]'
|
||||
else
|
||||
echo "$raw" | python3 -c '
|
||||
import json, sys
|
||||
raw = json.load(sys.stdin)
|
||||
out = []
|
||||
for a in raw.get("archives", []):
|
||||
comment = a.get("comment", "")
|
||||
tags = [t for t in comment.split() if "=" in t]
|
||||
out.append({
|
||||
"id": a.get("id"),
|
||||
"short_id": a.get("name"),
|
||||
"time": a.get("time"),
|
||||
"hostname": a.get("hostname"),
|
||||
"tags": tags,
|
||||
"paths": []
|
||||
})
|
||||
print(json.dumps(out))
|
||||
' 2>/dev/null || echo "[]"
|
||||
fi
|
||||
}
|
||||
|
||||
borgSnapshotLatestId()
|
||||
{
|
||||
local idx="$1"
|
||||
|
||||
@ -60,3 +60,41 @@ kopiaBackupAppToLocation()
|
||||
kopiaEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
kopiaBackupSystemToLocation()
|
||||
{
|
||||
local idx="$1"
|
||||
local source_path="${configs_dir%/}"
|
||||
|
||||
if [[ ! -d "$source_path" ]]; then
|
||||
isError "System config path missing: $source_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
kopiaEnvExport "$idx" || return 1
|
||||
|
||||
local host_tag="${CFG_INSTALL_NAME:-libreportal}"
|
||||
local tags=("--tags" "system:config" "--tags" "host:$host_tag" "--tags" "engine:libreportal")
|
||||
|
||||
local loc_name
|
||||
loc_name=$(resticLocationName "$idx")
|
||||
isNotice "Snapshotting system config → $loc_name (kopia)" >&2
|
||||
|
||||
local output
|
||||
output=$(runBackupOp kopia snapshot create "$source_path" "${tags[@]}" --json 2>&1)
|
||||
local rc=$?
|
||||
|
||||
local snapshot_id
|
||||
snapshot_id=$(echo "$output" | grep -oE '"id":\s*"[^"]+"' | head -1 | cut -d'"' -f4)
|
||||
|
||||
if [[ $rc -eq 0 ]]; then
|
||||
isSuccessful "System config backed up to $loc_name: ${snapshot_id:0:12}" >&2
|
||||
echo "$snapshot_id"
|
||||
else
|
||||
isError "Kopia system config backup to $loc_name failed" >&2
|
||||
echo "$output" | tail -10 >&2
|
||||
fi
|
||||
|
||||
kopiaEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
@ -41,3 +41,39 @@ kopiaForgetApp()
|
||||
kopiaEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
kopiaForgetSystem()
|
||||
{
|
||||
local idx="$1"
|
||||
|
||||
if resticLocationAppendOnly "$idx"; then
|
||||
isNotice "$(resticLocationName "$idx") is append-only — skipping forget for system config"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local keep_last keep_daily keep_weekly keep_monthly keep_yearly
|
||||
keep_last=$(resticRetentionFor "$idx" KEEP_LAST)
|
||||
keep_daily=$(resticRetentionFor "$idx" KEEP_DAILY)
|
||||
keep_weekly=$(resticRetentionFor "$idx" KEEP_WEEKLY)
|
||||
keep_monthly=$(resticRetentionFor "$idx" KEEP_MONTHLY)
|
||||
keep_yearly=$(resticRetentionFor "$idx" KEEP_YEARLY)
|
||||
|
||||
kopiaEnvExport "$idx" || return 1
|
||||
|
||||
local src="${configs_dir%/}"
|
||||
|
||||
local policy_args=(policy set --global=false "$src")
|
||||
[[ -n "$keep_last" ]] && policy_args+=(--keep-latest "$keep_last")
|
||||
[[ -n "$keep_daily" ]] && policy_args+=(--keep-daily "$keep_daily")
|
||||
[[ -n "$keep_weekly" ]] && policy_args+=(--keep-weekly "$keep_weekly")
|
||||
[[ -n "$keep_monthly" ]] && policy_args+=(--keep-monthly "$keep_monthly")
|
||||
[[ -n "$keep_yearly" ]] && policy_args+=(--keep-annual "$keep_yearly")
|
||||
|
||||
runBackupOp kopia "${policy_args[@]}" >/dev/null 2>&1
|
||||
|
||||
isNotice "Running Kopia maintenance for system config on $(resticLocationName "$idx")"
|
||||
runBackupOp kopia maintenance run --full
|
||||
local rc=$?
|
||||
kopiaEnvUnset
|
||||
return $rc
|
||||
}
|
||||
|
||||
@ -42,6 +42,61 @@ kopiaSnapshotsJson()
|
||||
fi
|
||||
}
|
||||
|
||||
kopiaSystemSnapshotsJson()
|
||||
{
|
||||
local idx="$1"
|
||||
local host_filter="$2"
|
||||
|
||||
kopiaEnvExport "$idx" || return 1
|
||||
|
||||
local raw
|
||||
raw=$(runBackupOp kopia snapshot list --all --json 2>/dev/null)
|
||||
local rc=$?
|
||||
kopiaEnvUnset
|
||||
[[ $rc -ne 0 || -z "$raw" ]] && { echo "[]"; return $rc; }
|
||||
|
||||
local jq_filter='[.[] | {
|
||||
id: .id,
|
||||
short_id: (.id[0:8]),
|
||||
time: .startTime,
|
||||
hostname: .source.host,
|
||||
tags: ((.tags // []) | map(sub(":"; "="))),
|
||||
paths: [.source.path]
|
||||
}]'
|
||||
jq_filter='[.[] | select(any(.tags[]?; . == "system:config"))] | '"$jq_filter"
|
||||
if [[ -n "$host_filter" ]]; then
|
||||
jq_filter='[.[] | select(.source.host == "'"$host_filter"'")] | '"$jq_filter"
|
||||
fi
|
||||
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
echo "$raw" | jq -c "$jq_filter"
|
||||
else
|
||||
echo "$raw"
|
||||
fi
|
||||
}
|
||||
|
||||
kopiaRestoreSystemLatest()
|
||||
{
|
||||
local idx="$1"
|
||||
local target_dir="$2"
|
||||
local host="${3:-$CFG_INSTALL_NAME}"
|
||||
|
||||
local json snapshot_id
|
||||
json=$(kopiaSystemSnapshotsJson "$idx" "$host")
|
||||
if command -v jq >/dev/null 2>&1; then
|
||||
snapshot_id=$(echo "$json" | jq -r 'sort_by(.time) | last | .id // empty')
|
||||
else
|
||||
snapshot_id=$(echo "$json" | grep -oE '"id":\s*"[^"]+"' | tail -1 | cut -d'"' -f4)
|
||||
fi
|
||||
|
||||
if [[ -z "$snapshot_id" ]]; then
|
||||
isError "No system-config snapshot found in $(resticLocationName "$idx") for host=$host"
|
||||
return 1
|
||||
fi
|
||||
# Whole-snapshot restore into staging (no include subpath).
|
||||
kopiaRestoreSnapshot "$idx" "$snapshot_id" "$target_dir"
|
||||
}
|
||||
|
||||
kopiaSnapshotLatestId()
|
||||
{
|
||||
local idx="$1"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user