portFindNextAvailablePort consulted LibrePortal's own network_resources table plus a hardcoded list (8080, 6060) and CFG_RESERVED_PORTS_EXTRA — while the comment above it claimed a picked port "can never collide with a host service at compose-up time". It can: the list only covers what someone thought to write down. Ask the kernel instead, via ss, read once per allocation rather than per candidate. No ss => empty set => exactly the old behaviour. Found while restoring 13 apps onto a desktop, though not the cause there: stoat's livekit publishes a FIXED udp range (50000-50100, which it advertises to clients and so cannot be re-rolled), and kdeconnectd held 50016. That collision needs its own answer; this fixes the randomly-allocated ports, which had the same exposure with no reason to. Also make the bulk restore stop reporting a half-running app as a clean success. continue-on-error lets a failed compose-up log and carry on, so restoreAppStart returns 0 either way — which is how that run printed "13 apps restored" while four of stoat's containers had exited 101. checkSuccess already appends every failure to error_report.log, so watch it grow across each app and name the ones that were noisy. scripts/dev/lp-port-host-test binds a real socket and asserts the allocator refuses that port; verified it fails when the check is removed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
3.9 KiB
Bash
103 lines
3.9 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
|
|
}
|