LibrePortal/scripts/backup/locations/location_remove.sh
librelad 5dd763713d Read a backup repository from the WebUI, and stop adoption breaking things
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>
2026-08-29 04:33:07 +01:00

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
}