restore inspect answers "what would a restore from here bring?" without writing
anything: hosts, apps with sizes, and the domains — read out of the
system-config snapshot with engineDumpFile, the same way the preflight reads an
app manifest. Knowing a backup hands you six domains of which four point
elsewhere, before committing, is the difference between a rebuild and a
surprise.
restore connect is the WebUI entry point: creates the location from a base64
payload, redeems the repository password from the single-use secret channel,
inspects. Deliberately does not engineInitLocation — every other path that
creates a location initialises it because it is about to write there; this one
reads a repository that already exists. This is what unblocks the constraint
app_portable.sh records: a .lpapp could live in the WebUI because nothing
secret crosses from browser to host, and the repository restore could not. The
secret:<ref> channel is that missing piece.
A wrong password is the ordinary case and the user retries, so a failed connect
removes the location it just made. Otherwise every attempt left another
half-configured destination behind.
Three things found by using it:
- locationRemove never worked. It unlinked as the container user, but
configs/ is manager-owned, so it was always denied — and the result was
never checked, so isSuccessful printed anyway and a "removed" location came
back on the next listing. Now runInstallOp, and the directory is checked.
- webuiSecretSweep had no callers. An abandoned flow left its repository
password on disk forever. The sweep now runs in /api/setup/secret before
each write, tied to the one event guaranteed to happen.
- Adoption took the WebUI down. config-adopt chowned every adopted file to
manager:manager 0640, and webui_logins is bind-mounted into the container,
which then could not read its own credentials: exit 137 with no log line.
It also clamped every parent directory it passed through, closing
configs/webui and configs/backup to the container user.
The fix is a principle, not a special case: a restore replaces the CONTENT
of a config file and nothing else. The live install already knows who may
read each one. Adoption preserves the destination's ownership and mode,
defaults closed only for a file that did not exist, and never
re-permissions a directory it passes through.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
48 lines
1.6 KiB
Bash
48 lines
1.6 KiB
Bash
#!/bin/bash
|
|
|
|
locationRemove()
|
|
{
|
|
local idx="$1"
|
|
local drop_password="${2:-no}" # legacy flag kept for backwards compat
|
|
|
|
if [[ -z "$idx" || ! "$idx" =~ ^[0-9]+$ ]]; then
|
|
isError "locationRemove requires a numeric index"
|
|
return 1
|
|
fi
|
|
|
|
local dir
|
|
dir=$(backupLocationDir "$idx")
|
|
if [[ ! -d "$dir" ]]; then
|
|
isError "Location $idx does not exist at $dir"
|
|
return 1
|
|
fi
|
|
|
|
local name_var="CFG_BACKUP_LOC_${idx}_NAME"
|
|
local label="${!name_var:-Location $idx}"
|
|
|
|
# runInstallOp, not runFileOp: configs/ is manager-owned and the container
|
|
# user this used to run as cannot unlink inside it. The removal failed
|
|
# every time while the success message below printed anyway, so a location
|
|
# "removed" from the WebUI came straight back on the next listing.
|
|
runInstallOp rm -rf "$dir"
|
|
|
|
# And check. An unconditional isSuccessful after an unchecked command is
|
|
# what hid this: the only evidence a person ever saw was the message.
|
|
if [[ -d "$dir" ]]; then
|
|
isError "Location $idx '$label' could not be removed — $dir is still there."
|
|
return 1
|
|
fi
|
|
|
|
# Best-effort unset of the now-orphaned env vars so other code in this
|
|
# process doesn't see stale values.
|
|
local var
|
|
while IFS= read -r var; do
|
|
unset "$var"
|
|
done < <(compgen -v 2>/dev/null | grep -E "^CFG_BACKUP_LOC_${idx}_")
|
|
|
|
isSuccessful "Location $idx '$label' removed (directory deleted: $dir)"
|
|
if declare -f webuiGenerateBackupLocations >/dev/null 2>&1; then
|
|
webuiGenerateBackupLocations
|
|
fi
|
|
}
|