Merge claude/1

This commit is contained in:
librelad 2026-05-26 00:56:00 +01:00
commit eb7060f450
7 changed files with 272 additions and 0 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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"

View File

@ -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"

View File

@ -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
}

View File

@ -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
}

View File

@ -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"