Compare commits
No commits in common. "178a5487f28b8eb68e7036d5f9a4d2436781e0fd" and "f81bcbbd212e0e02e6daaed517cbc187b3667477" have entirely different histories.
178a5487f2
...
f81bcbbd21
@ -8,4 +8,3 @@ CFG_BACKUP_STRATEGY=auto # Backup Strategy
|
|||||||
CFG_BACKUP_VERIFY_AFTER=true # Verify After Backup - Run integrity check after each backup
|
CFG_BACKUP_VERIFY_AFTER=true # Verify After Backup - Run integrity check after each backup
|
||||||
CFG_BACKUP_VERIFY_DATA_PERCENT=5 # Verify Data Sample % - Percentage of repo data to checksum-verify weekly
|
CFG_BACKUP_VERIFY_DATA_PERCENT=5 # Verify Data Sample % - Percentage of repo data to checksum-verify weekly
|
||||||
CFG_BACKUP_PARALLEL_REPOS=true # Parallel Repos - Push to all enabled locations in parallel
|
CFG_BACKUP_PARALLEL_REPOS=true # Parallel Repos - Push to all enabled locations in parallel
|
||||||
CFG_BACKUP_SSH_MULTIPLEX=true # SSH Connection Reuse - Share one SSH connection across the restic calls to each SFTP location (faster on high-latency links). Disable if your SSH server rejects multiplexing.
|
|
||||||
|
|||||||
@ -62,7 +62,7 @@ Object.assign(BackupPage.prototype, {
|
|||||||
</div>
|
</div>
|
||||||
<div class="backup-repo-row-meta">
|
<div class="backup-repo-row-meta">
|
||||||
${this.formatBytes(parseInt(r.total_size_bytes) || 0)}<br>
|
${this.formatBytes(parseInt(r.total_size_bytes) || 0)}<br>
|
||||||
<span class="backup-card-hint">${(() => { const n = this.snapshotsByLoc[r.idx]?.snapshots?.length ?? 0; return `${n} snapshot${n === 1 ? '' : 's'}`; })()}</span>
|
<span class="backup-card-hint">${r.total_files || 0} files</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
@ -24,21 +24,7 @@ backupSshCommand()
|
|||||||
pass=$(resticLocationField "$idx" SSH_PASS)
|
pass=$(resticLocationField "$idx" SSH_PASS)
|
||||||
[[ -z "$auth" ]] && auth=key
|
[[ -z "$auth" ]] && auth=key
|
||||||
|
|
||||||
# Connection multiplexing: a single WebUI refresh (repo stats + snapshot
|
local base="ssh -p $port -o StrictHostKeyChecking=accept-new"
|
||||||
# list) and each backup push run several restic subprocesses against the
|
|
||||||
# same location; ControlMaster lets them share one authenticated SSH
|
|
||||||
# connection instead of a fresh handshake each — the dominant per-call cost
|
|
||||||
# on a high-latency link. auto = reuse an existing master or open one; the
|
|
||||||
# master self-reaps ControlPersist seconds after its last channel closes.
|
|
||||||
# %C keeps the socket path short and unique per (host,port,user). Opt out
|
|
||||||
# with CFG_BACKUP_SSH_MULTIPLEX=false.
|
|
||||||
local mux=""
|
|
||||||
if [[ "${CFG_BACKUP_SSH_MULTIPLEX:-true}" != "false" ]]; then
|
|
||||||
local mux_dir="${TMPDIR:-/tmp}"; mux_dir="${mux_dir%/}"
|
|
||||||
mux=" -o ControlMaster=auto -o ControlPath=${mux_dir}/lp-bkmux-%C -o ControlPersist=30"
|
|
||||||
fi
|
|
||||||
|
|
||||||
local base="ssh -p $port -o StrictHostKeyChecking=accept-new${mux}"
|
|
||||||
[[ "$mode" == "sftp" ]] && local suffix=" -s sftp" || local suffix=""
|
[[ "$mode" == "sftp" ]] && local suffix=" -s sftp" || local suffix=""
|
||||||
|
|
||||||
if [[ "$auth" == "password" ]]; then
|
if [[ "$auth" == "password" ]]; then
|
||||||
|
|||||||
@ -34,28 +34,6 @@ engineDispatch()
|
|||||||
"$fn" "$@"
|
"$fn" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Transparent per-refresh memoiser for read-only remote pulls. The WebUI backup
|
|
||||||
# refresh reads the same restic data from several generators per location — the
|
|
||||||
# snapshot list (dashboard/snapshots/app-status/migrate) and repo stats
|
|
||||||
# (locations + dashboard) — which on a remote (SSH) repo is one round-trip each.
|
|
||||||
# When LP_SNAP_CACHE_DIR is set (webui_updater wraps the refresh chain with it),
|
|
||||||
# the first successful pull for a cache key is written to a file the siblings
|
|
||||||
# reuse; empty/failed pulls fall through so a transient error is never cached.
|
|
||||||
# Unset dir → every call runs live.
|
|
||||||
_engineCachedPull() {
|
|
||||||
local _key="$1"; shift
|
|
||||||
if [[ -n "${LP_SNAP_CACHE_DIR:-}" ]]; then
|
|
||||||
local _cf="${LP_SNAP_CACHE_DIR}/${_key}"
|
|
||||||
[[ -s "$_cf" ]] && { cat "$_cf"; return 0; }
|
|
||||||
local _out _rc
|
|
||||||
_out=$("$@"); _rc=$?
|
|
||||||
[[ $_rc -eq 0 && -n "$_out" ]] && printf '%s' "$_out" > "$_cf" 2>/dev/null
|
|
||||||
printf '%s' "$_out"
|
|
||||||
return $_rc
|
|
||||||
fi
|
|
||||||
"$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ---- Idx-scoped dispatchers ----------------------------------------------------
|
# ---- Idx-scoped dispatchers ----------------------------------------------------
|
||||||
|
|
||||||
# Local/removable-drive safety guard runs before init, readiness, and any backup
|
# Local/removable-drive safety guard runs before init, readiness, and any backup
|
||||||
@ -65,7 +43,7 @@ engineInitLocation() { local i="$1"; backupLocationLocalGuard "$i" || re
|
|||||||
engineEnsureLocationReady() { local i="$1"; backupLocationLocalGuard "$i" || return 1; engineDispatch "$(engineForLocation "$i")EnsureLocationReady" "$i"; }
|
engineEnsureLocationReady() { local i="$1"; backupLocationLocalGuard "$i" || return 1; engineDispatch "$(engineForLocation "$i")EnsureLocationReady" "$i"; }
|
||||||
enginePasswordEnsure() { local i="$1"; engineDispatch "$(engineForLocation "$i")PasswordEnsure" "$i"; }
|
enginePasswordEnsure() { local i="$1"; engineDispatch "$(engineForLocation "$i")PasswordEnsure" "$i"; }
|
||||||
engineLocationUri() { local i="$1"; engineDispatch "$(engineForLocation "$i")LocationUri" "$i"; }
|
engineLocationUri() { local i="$1"; engineDispatch "$(engineForLocation "$i")LocationUri" "$i"; }
|
||||||
engineLocationStats() { local i="$1"; _engineCachedPull "stats_${i}.json" engineDispatch "$(engineForLocation "$i")LocationStats" "$i"; }
|
engineLocationStats() { local i="$1"; engineDispatch "$(engineForLocation "$i")LocationStats" "$i"; }
|
||||||
engineEnvExport() { local i="$1"; engineDispatch "$(engineForLocation "$i")EnvExport" "$i"; }
|
engineEnvExport() { local i="$1"; engineDispatch "$(engineForLocation "$i")EnvExport" "$i"; }
|
||||||
engineEnvUnset() { local i="$1"; engineDispatch "$(engineForLocation "${i:-1}")EnvUnset"; }
|
engineEnvUnset() { local i="$1"; engineDispatch "$(engineForLocation "${i:-1}")EnvUnset"; }
|
||||||
|
|
||||||
@ -74,14 +52,23 @@ engineBackupSystem() { local i="$1"; shift; backupLocationLocalGuard "$i
|
|||||||
engineRestoreSystemLatest() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")RestoreSystemLatest" "$i" "$@"; }
|
engineRestoreSystemLatest() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")RestoreSystemLatest" "$i" "$@"; }
|
||||||
engineRestoreSnapshot() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")RestoreSnapshot" "$i" "$@"; }
|
engineRestoreSnapshot() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")RestoreSnapshot" "$i" "$@"; }
|
||||||
engineSnapshotLatestId() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")SnapshotLatestId" "$i" "$@"; }
|
engineSnapshotLatestId() { local i="$1"; shift; engineDispatch "$(engineForLocation "$i")SnapshotLatestId" "$i" "$@"; }
|
||||||
# Whole-repo snapshot list. Only the unfiltered pull (no extra args) is memoised
|
# Whole-repo snapshot list. The WebUI backup refresh pulls this the same way
|
||||||
# — the four generators that read it that way per location share one round-trip;
|
# (no filters) from four generators per location — dashboard, snapshots,
|
||||||
# filtered/parameterised calls always run live.
|
# app-status and migrate — which on a remote (SSH) repo is four identical restic
|
||||||
|
# round-trips. When LP_SNAP_CACHE_DIR is set (webui_updater wraps the refresh
|
||||||
|
# chain with it), memoise the first unfiltered pull per location to a file the
|
||||||
|
# sibling generators reuse; filtered/parameterised calls (extra args) and any
|
||||||
|
# failed/empty pull always fall through to a live restic call.
|
||||||
engineSnapshotsJson() {
|
engineSnapshotsJson() {
|
||||||
local i="$1"; shift
|
local i="$1"; shift
|
||||||
if [[ $# -eq 0 ]]; then
|
if [[ $# -eq 0 && -n "${LP_SNAP_CACHE_DIR:-}" ]]; then
|
||||||
_engineCachedPull "snapshots_${i}.json" engineDispatch "$(engineForLocation "$i")SnapshotsJson" "$i"
|
local _cf="${LP_SNAP_CACHE_DIR}/snapshots_${i}.json"
|
||||||
return $?
|
[[ -s "$_cf" ]] && { cat "$_cf"; return 0; }
|
||||||
|
local _out _rc
|
||||||
|
_out=$(engineDispatch "$(engineForLocation "$i")SnapshotsJson" "$i"); _rc=$?
|
||||||
|
[[ $_rc -eq 0 && -n "$_out" ]] && printf '%s' "$_out" > "$_cf" 2>/dev/null
|
||||||
|
printf '%s' "$_out"
|
||||||
|
return $_rc
|
||||||
fi
|
fi
|
||||||
engineDispatch "$(engineForLocation "$i")SnapshotsJson" "$i" "$@"
|
engineDispatch "$(engineForLocation "$i")SnapshotsJson" "$i" "$@"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,13 +42,7 @@ resticLocationStats()
|
|||||||
{
|
{
|
||||||
local idx="$1"
|
local idx="$1"
|
||||||
resticEnvExport "$idx" || return 1
|
resticEnvExport "$idx" || return 1
|
||||||
# raw-data mode reads the index only and reports the repository's actual
|
runBackupOp restic stats --json --no-lock 2>/dev/null
|
||||||
# deduplicated on-disk size — fast, and the number the dashboard already
|
|
||||||
# labels "deduplicated, encrypted". The default restore-size mode instead
|
|
||||||
# walks every snapshot's tree to sum logical file sizes: the slowest restic
|
|
||||||
# op, and pointless for a size readout. (raw-data omits total_file_count;
|
|
||||||
# the dashboard shows snapshot count for that slot instead.)
|
|
||||||
runBackupOp restic stats --json --no-lock --mode raw-data 2>/dev/null
|
|
||||||
local rc=$?
|
local rc=$?
|
||||||
resticEnvUnset
|
resticEnvUnset
|
||||||
return $rc
|
return $rc
|
||||||
|
|||||||
@ -21,11 +21,13 @@ webuiGenerateBackupDashboard()
|
|||||||
uri=$(resticLocationUri "$idx")
|
uri=$(resticLocationUri "$idx")
|
||||||
append_only="false"; resticLocationAppendOnly "$idx" && append_only="true"
|
append_only="false"; resticLocationAppendOnly "$idx" && append_only="true"
|
||||||
|
|
||||||
local stats_json total_size="0"
|
local stats_json total_size="0" total_files="0"
|
||||||
stats_json=$(engineLocationStats "$idx" 2>/dev/null)
|
stats_json=$(engineLocationStats "$idx" 2>/dev/null)
|
||||||
if [[ -n "$stats_json" ]]; then
|
if [[ -n "$stats_json" ]]; then
|
||||||
total_size=$(echo "$stats_json" | grep -o '"total_size":[0-9]*' | head -1 | cut -d':' -f2)
|
total_size=$(echo "$stats_json" | grep -o '"total_size":[0-9]*' | head -1 | cut -d':' -f2)
|
||||||
|
total_files=$(echo "$stats_json" | grep -o '"total_file_count":[0-9]*' | head -1 | cut -d':' -f2)
|
||||||
[[ -z "$total_size" ]] && total_size="0"
|
[[ -z "$total_size" ]] && total_size="0"
|
||||||
|
[[ -z "$total_files" ]] && total_files="0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local name_esc uri_esc
|
local name_esc uri_esc
|
||||||
@ -40,7 +42,8 @@ webuiGenerateBackupDashboard()
|
|||||||
locations_json+="\"type\":\"$(resticLocationType "$idx")\","
|
locations_json+="\"type\":\"$(resticLocationType "$idx")\","
|
||||||
locations_json+="\"uri\":\"$uri_esc\","
|
locations_json+="\"uri\":\"$uri_esc\","
|
||||||
locations_json+="\"append_only\":$append_only,"
|
locations_json+="\"append_only\":$append_only,"
|
||||||
locations_json+="\"total_size_bytes\":$total_size"
|
locations_json+="\"total_size_bytes\":$total_size,"
|
||||||
|
locations_json+="\"total_files\":$total_files"
|
||||||
locations_json+="}"
|
locations_json+="}"
|
||||||
done < <(resticEnabledLocations)
|
done < <(resticEnabledLocations)
|
||||||
locations_json+="]"
|
locations_json+="]"
|
||||||
|
|||||||
@ -40,13 +40,15 @@ webuiGenerateBackupLocations()
|
|||||||
pass_exists="true"
|
pass_exists="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local total_size="0"
|
local total_size="0" total_files="0"
|
||||||
if [[ "$enabled" == "true" && "$pass_exists" == "true" ]]; then
|
if [[ "$enabled" == "true" && "$pass_exists" == "true" ]]; then
|
||||||
local stats_json
|
local stats_json
|
||||||
stats_json=$(engineLocationStats "$idx" 2>/dev/null)
|
stats_json=$(engineLocationStats "$idx" 2>/dev/null)
|
||||||
if [[ -n "$stats_json" ]]; then
|
if [[ -n "$stats_json" ]]; then
|
||||||
total_size=$(echo "$stats_json" | grep -o '"total_size":[0-9]*' | head -1 | cut -d':' -f2)
|
total_size=$(echo "$stats_json" | grep -o '"total_size":[0-9]*' | head -1 | cut -d':' -f2)
|
||||||
|
total_files=$(echo "$stats_json" | grep -o '"total_file_count":[0-9]*' | head -1 | cut -d':' -f2)
|
||||||
[[ -z "$total_size" ]] && total_size="0"
|
[[ -z "$total_size" ]] && total_size="0"
|
||||||
|
[[ -z "$total_files" ]] && total_files="0"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -78,6 +80,7 @@ webuiGenerateBackupLocations()
|
|||||||
content+="\"custom_retention\":$custom_retention,"
|
content+="\"custom_retention\":$custom_retention,"
|
||||||
content+="\"password_exists\":$pass_exists,"
|
content+="\"password_exists\":$pass_exists,"
|
||||||
content+="\"total_size_bytes\":$total_size,"
|
content+="\"total_size_bytes\":$total_size,"
|
||||||
|
content+="\"total_files\":$total_files,"
|
||||||
content+="\"path\":\"$(printf '%s' "$(resticLocationField "$idx" PATH)" | sed 's/\\/\\\\/g; s/"/\\"/g')\","
|
content+="\"path\":\"$(printf '%s' "$(resticLocationField "$idx" PATH)" | sed 's/\\/\\\\/g; s/"/\\"/g')\","
|
||||||
content+="\"path_mode\":\"$(resticLocationField "$idx" PATH_MODE)\","
|
content+="\"path_mode\":\"$(resticLocationField "$idx" PATH_MODE)\","
|
||||||
content+="\"ssh_user\":\"$(printf '%s' "$(resticLocationField "$idx" SSH_USER)" | sed 's/\\/\\\\/g; s/"/\\"/g')\","
|
content+="\"ssh_user\":\"$(printf '%s' "$(resticLocationField "$idx" SSH_USER)" | sed 's/\\/\\\\/g; s/"/\\"/g')\","
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user