The wizard's first question is now "is this a new server, or a replacement for
one?", which §2 of the roadmap described and nothing implemented. Start asks,
and the answer selects one of two disjoint step sets:
new Start > Experience > Identity > Domains > Storage > Backups
> Import > Recommended > (Metrics)
restore Start > Source > Contents > Rebuild
Disjoint deliberately. A restore is never asked for an install name, domains or
an app list — the backup answers all three, and asking invites someone to type
an answer that is about to be written over. The test asserts non-overlap in
both directions, not just that the restore steps appear.
Source collects the repository the way the Backup page does, minus everything
that only means something for a place you write TO: no retention, no schedule,
no enable toggle. The password leaves through the one-shot secret:<ref> channel
and is cleared from the DOM, and the test asserts the value never appears in
the payload — that payload reaches a task command line, and tasks are recorded
world-readable.
Contents is the reconciliation, rendered: apps with sizes, and each domain with
a verdict, checked through the same /api/setup/dns-check the Domains step uses
rather than adding a second way to ask. Plus the offer to leave the strays out
until DNS is repointed.
Rebuild runs `restore rebuild`: settings first (they carry every other
repository's credentials), then domains, then apps with no explicit list so
bulk discovers and re-preflights them itself.
Inserting Start shifted every step index by one. validateStep was a chain of
idx === 1 … idx === 6, carrying a comment that already explained which earlier
insertions had moved them — it is keyed on the step name now.
lp-storage-step-test had the same pin and did not survive: it called
validateStep(3) for Storage, which had become Domains, and reported that
nothing blocked. That reads exactly like validation being broken. Tests look
their step up by name now too.
Also: locationRemove's fix means a failed connect can finally clean up after
itself, so a wrong password no longer leaves a dead destination behind on every
retry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
154 lines
6.0 KiB
Bash
154 lines
6.0 KiB
Bash
#!/bin/bash
|
|
|
|
restoreFirstRunDiscover()
|
|
{
|
|
local idx="$1"
|
|
|
|
if ! resticLocationEnabled "$idx"; then
|
|
isError "Location $idx is not enabled"
|
|
return 1
|
|
fi
|
|
|
|
resticEnvExport "$idx" || return 1
|
|
# Via runBackupOp rather than its own sudo: this was the one backup-engine
|
|
# call bypassing that funnel, so it silently missed the -E fix for sudo-rs
|
|
# (and the -H that puts restic's cache under the backup user's HOME).
|
|
runBackupOp restic snapshots --tag engine=libreportal --json --no-lock 2>/dev/null
|
|
local rc=$?
|
|
resticEnvUnset
|
|
return $rc
|
|
}
|
|
|
|
# Restore a host's apps onto this machine.
|
|
#
|
|
# With no app list this is a WHOLE-HOST restore: the apps are discovered from
|
|
# the repository and filtered through the preflight. Both halves matter.
|
|
#
|
|
# Discovery, because an explicit list has to survive the CLI wrapper's fixed
|
|
# positional slots to get here — a 13-app restore arrived as four, restored
|
|
# those, and reported success. The wrapper now forwards the real argv, but a
|
|
# whole-host restore that never builds a list cannot be truncated at all.
|
|
#
|
|
# The preflight, because the installer prints its report in a separate process,
|
|
# so the decision it made there is gone by the time this runs. Without
|
|
# re-applying it, an app the user was told would be skipped — one this version
|
|
# no longer ships, or one too big for the disk — gets restored anyway.
|
|
restoreFirstRunBulk()
|
|
{
|
|
local idx="$1"
|
|
local source_host="$2"
|
|
shift 2
|
|
local apps_to_restore=("$@")
|
|
local -i preflighted=0
|
|
|
|
if [[ ${#apps_to_restore[@]} -eq 0 ]]; then
|
|
restorePreflightReport "$idx" "$source_host" >/dev/null 2>&1
|
|
apps_to_restore=("${RESTORE_PREFLIGHT_OK[@]}")
|
|
preflighted=1
|
|
fi
|
|
|
|
if [[ ${#apps_to_restore[@]} -eq 0 ]]; then
|
|
isError "No apps to restore for '$source_host' in this repository"
|
|
return 1
|
|
fi
|
|
|
|
isHeader "First-run bulk restore from $(resticLocationName "$idx") (host=$source_host)"
|
|
(( preflighted )) && isNotice "Restoring ${#apps_to_restore[@]} apps the preflight approved."
|
|
|
|
# Count what actually landed. A per-app failure must not be reported as a
|
|
# complete restore — that is how "4 apps restored" read as success when
|
|
# nine had gone missing.
|
|
#
|
|
# An app can also come back only half-running: continue-on-error (the
|
|
# default) lets a failed compose-up log and carry on, so restoreAppStart
|
|
# still returns 0. That is how a restore reported thirteen successes while
|
|
# stoat's livekit had lost a port race and four containers that depended on
|
|
# it exited 101. checkSuccess appends every such failure to error_report.log,
|
|
# so watch that file grow across each app and name the noisy ones.
|
|
local _errlog="${logs_dir%/}/error_report.log"
|
|
_restoreErrLines() { wc -l < "$_errlog" 2>/dev/null || echo 0; }
|
|
|
|
local app
|
|
local -i ok=0 bad=0 before=0 after=0
|
|
local -a failed=() noisy=()
|
|
for app in "${apps_to_restore[@]}"; do
|
|
before=$(_restoreErrLines)
|
|
if restoreAppStart "$app" "latest" "$idx" "$source_host"; then
|
|
ok=$(( ok + 1 ))
|
|
after=$(_restoreErrLines)
|
|
(( after > before )) && noisy+=("$app")
|
|
else
|
|
bad=$(( bad + 1 )); failed+=("$app")
|
|
fi
|
|
done
|
|
unset -f _restoreErrLines
|
|
|
|
if (( bad > 0 )); then
|
|
isError "First-run restore finished with failures — $ok of ${#apps_to_restore[@]} restored"
|
|
isNotice "Failed: ${failed[*]}"
|
|
(( ${#noisy[@]} )) && isNotice "Restored but reported errors: ${noisy[*]}"
|
|
return 1
|
|
fi
|
|
|
|
if (( ${#noisy[@]} )); then
|
|
isSuccessful "First-run restore complete — $ok apps restored"
|
|
isNotice "${#noisy[@]} reported errors while starting: ${noisy[*]}"
|
|
isNotice "They are restored, but check them: $_errlog"
|
|
return 0
|
|
fi
|
|
|
|
isSuccessful "First-run restore complete — $ok apps restored"
|
|
return 0
|
|
}
|
|
|
|
# The WebUI's rebuild: adopt the settings, reconcile the domains, restore the
|
|
# apps. Same order the installer uses and for the same reason — the system
|
|
# config carries every other backup location's credentials, so one password the
|
|
# user remembers unlocks the rest, and only then are apps worth restoring.
|
|
#
|
|
# restoreWebuiRebuild <location-idx> <host> [drop-domains]
|
|
#
|
|
# Called from a task, so its output is the progress the user watches.
|
|
restoreWebuiRebuild()
|
|
{
|
|
local idx="${1:-}" host="${2:-}" drop="${3:-no}"
|
|
if [[ -z "$idx" ]]; then
|
|
isError "restoreWebuiRebuild requires a backup location"
|
|
return 1
|
|
fi
|
|
|
|
isHeader "Rebuilding from backup"
|
|
|
|
# --- settings first ------------------------------------------------------
|
|
isNotice "Restoring settings and credentials…"
|
|
if backupRestoreSystemConfig "$idx" >/dev/null 2>&1; then
|
|
# --force: the WebUI is only reachable at all because this machine has
|
|
# a working install on it, so restoreAdoptIsFirstRun will say no. The
|
|
# user asked for this explicitly on the Rebuild step, which is the
|
|
# confirmation the guard exists to require.
|
|
if restoreSystemAdopt "" --force; then
|
|
isSuccessful "Settings and backup repositories restored"
|
|
else
|
|
isNotice "Settings were staged but could not be adopted — apps will still be restored."
|
|
fi
|
|
else
|
|
isNotice "No system config in this backup — apps will still be restored."
|
|
fi
|
|
|
|
# --- domains -------------------------------------------------------------
|
|
restoreDomainReport || true
|
|
if [[ "$drop" == "yes" || "$drop" == "true" ]]; then
|
|
restoreDomainsDropElsewhere || true
|
|
fi
|
|
|
|
# --- apps ----------------------------------------------------------------
|
|
# No app list, deliberately: bulk discovers the host's apps and re-applies
|
|
# the preflight itself. Passing a list here is what let a 13-app restore
|
|
# arrive as four and still report success.
|
|
isNotice "Restoring apps — this takes a while."
|
|
restoreFirstRunBulk "$idx" "$host"
|
|
|
|
isSuccessful "Rebuild complete"
|
|
return 0
|
|
}
|