fix(init): refuse to copy the install tree onto itself

copyFilesFromLocal ran `rm -rf "$script_dir"` before ever reading the source. When
source and destination resolve to the same path — which is what happens if you
run `sudo ./init.sh init` from inside /libreportal-system/install, an easy
mistake when re-running to re-bake the footprint — the rm deletes the source too,
the copy then fails, and the script exits.

The damage is worse than a failed copy: it exits before initRootHelpers and
before the sudoers tightening, so the install tree is gone AND the manager is
left holding the install-phase grant (ALL=(ALL) NOPASSWD: ALL) instead of the
scoped allowlist. I did exactly this on a live box; the tree was recoverable from
git, but the loose sudo rule is the part that matters.

Now: resolve both paths and refuse if they match, and validate the source before
destroying the destination rather than after. Both checks run ahead of the rm.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
librelad 2026-08-19 01:54:16 +01:00
parent bd10a9ab55
commit 6861358809

27
init.sh
View File

@ -453,7 +453,32 @@ copyFilesFromLocal() {
fi
isHeader "Copying from Local Directory"
# Refuse to copy a directory onto itself. Everything below deletes the
# destination BEFORE reading the source, so when the two resolve to the same
# path the rm wipes the source too and the copy fails with the tree already
# gone — taking the install down and stopping short of the root-helper bake
# and the sudoers tightening, leaving install-phase sudo in place.
# Running `sudo ./init.sh init` from inside the install tree is enough to hit
# it, which is an easy mistake when re-running to re-bake the footprint.
local _src_real _dst_real
_src_real="$(readlink -f "$source_dir" 2>/dev/null || echo "$source_dir")"
_dst_real="$(readlink -f "$script_dir" 2>/dev/null || echo "$script_dir")"
if [[ "$_src_real" == "$_dst_real" ]]; then
isError "Source and destination are the same directory ($_dst_real)."
isNotice "Run init.sh from your source checkout, not from inside the install tree."
exit 1
fi
# Validate the source BEFORE destroying the destination, for the same reason:
# a bad source used to be discovered only after the install tree was gone.
# detectLocalLibrePortal derives the directory the same way source_dir is
# derived above, so it checks exactly what is about to be copied.
if ! detectLocalLibrePortal >/dev/null 2>&1; then
isError "$source_dir is not a valid LibrePortal source — refusing to replace $script_dir."
exit 1
fi
# Remove existing install directory
sudo rm -rf "$script_dir"