The three roots are independently relocatable, and the failures that matter are
the ones where only ONE of them moves: paths are baked into root-owned helpers,
the systemd unit and the CLI wrapper at install time, so anything that resolves
a root at runtime instead works on a default install and points at the wrong
disk on a relocated one. Testing "all default" or "all moved" misses that.
scripts/dev/lp-testdisk loopback ext4 disks — a real superblock, its own
st_dev and free space, thrown away between runs
scripts/dev/lp-install-matrix installs across the four root combinations and
checks each landed on the intended DEVICE, that
the helpers were baked (no __PLACEHOLDER__ left)
and that the WebUI answers
First thing the harness turned up: lp-shot hardcoded /libreportal-containers for
both the compose file it reads the published port from and the .auth.json it
signs a session with. On an install whose app data is on another disk it fell
back to a default port and a missing auth file — which looks exactly like a
WebUI that failed to boot. It now reads the baked LP_CONTAINERS_DIR back out of
the CLI wrapper.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
99 lines
3.4 KiB
Bash
Executable File
99 lines
3.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# Loopback disks for exercising the multi-disk install and storage paths.
|
|
#
|
|
# scripts/dev/lp-testdisk up [count] [size] # default 2 disks, 30G sparse
|
|
# scripts/dev/lp-testdisk status
|
|
# scripts/dev/lp-testdisk down
|
|
#
|
|
# Why loopback rather than a spare partition: the code paths that matter care
|
|
# that a location sits on a DIFFERENT filesystem — appDir resolution, the
|
|
# storage registry's device numbers, `st_dev` comparisons in the installer's
|
|
# disk picker, and app-adopt's same-device mv vs cross-device copy. A loop
|
|
# device gives all of that with a real ext4 superblock, its own st_dev and its
|
|
# own free-space figures, and can be thrown away between runs.
|
|
#
|
|
# Sparse-allocated, so a 30G disk costs what is written to it, not 30G.
|
|
set -u
|
|
|
|
BACKING_DIR="${LP_TESTDISK_DIR:-/var/lib/lp-testdisks}"
|
|
MNT_PREFIX="${LP_TESTDISK_MNT:-/mnt/lptest}"
|
|
|
|
_need_root() { [[ $EUID -eq 0 ]] || { echo "lp-testdisk: run with sudo" >&2; exit 1; }; }
|
|
|
|
up() {
|
|
_need_root
|
|
local count="${1:-2}" size="${2:-30G}"
|
|
mkdir -p "$BACKING_DIR"
|
|
local i img mnt
|
|
for (( i=1; i<=count; i++ )); do
|
|
img="$BACKING_DIR/disk$i.img"
|
|
mnt="${MNT_PREFIX}$i"
|
|
if ! [[ -f "$img" ]]; then
|
|
truncate -s "$size" "$img"
|
|
mkfs.ext4 -q -L "lptest$i" "$img"
|
|
echo " created $img ($size, sparse)"
|
|
fi
|
|
mkdir -p "$mnt"
|
|
if mountpoint -q "$mnt"; then
|
|
echo " already mounted: $mnt"
|
|
else
|
|
mount -o loop "$img" "$mnt" || { echo " FAILED to mount $img" >&2; return 1; }
|
|
# An install writes here as root and then hands ownership over, so
|
|
# the mount point itself only needs to be traversable.
|
|
chmod 0755 "$mnt"
|
|
echo " mounted $img -> $mnt"
|
|
fi
|
|
done
|
|
status
|
|
}
|
|
|
|
status() {
|
|
local mnt
|
|
printf ' %-16s %-10s %-8s %-8s %s\n' MOUNT DEV SIZE AVAIL "st_dev"
|
|
for mnt in "${MNT_PREFIX}"*; do
|
|
[[ -d "$mnt" ]] || continue
|
|
if mountpoint -q "$mnt"; then
|
|
printf ' %-16s %-10s %-8s %-8s %s\n' "$mnt" \
|
|
"$(findmnt -no SOURCE "$mnt" 2>/dev/null | xargs -r basename)" \
|
|
"$(findmnt -no SIZE "$mnt" 2>/dev/null)" \
|
|
"$(findmnt -no AVAIL "$mnt" 2>/dev/null)" \
|
|
"$(stat -c '%d' "$mnt" 2>/dev/null)"
|
|
else
|
|
printf ' %-16s %s\n' "$mnt" "(not mounted)"
|
|
fi
|
|
done
|
|
printf ' %-16s %-10s %-8s %-8s %s\n' "/" \
|
|
"$(findmnt -no SOURCE --target / | tail -1 | xargs -r basename)" \
|
|
"$(findmnt -no SIZE --target / | tail -1)" \
|
|
"$(findmnt -no AVAIL --target / | tail -1)" \
|
|
"$(stat -c '%d' / 2>/dev/null)"
|
|
}
|
|
|
|
down() {
|
|
_need_root
|
|
local mnt
|
|
for mnt in "${MNT_PREFIX}"*; do
|
|
[[ -d "$mnt" ]] || continue
|
|
if mountpoint -q "$mnt"; then
|
|
umount "$mnt" 2>/dev/null || umount -l "$mnt" 2>/dev/null \
|
|
|| { echo " busy, still mounted: $mnt" >&2; continue; }
|
|
echo " unmounted $mnt"
|
|
fi
|
|
rmdir "$mnt" 2>/dev/null
|
|
done
|
|
}
|
|
|
|
destroy() {
|
|
_need_root
|
|
down
|
|
[[ -d "$BACKING_DIR" ]] && { rm -rf "$BACKING_DIR"; echo " removed $BACKING_DIR"; }
|
|
}
|
|
|
|
case "${1:-status}" in
|
|
up) shift; up "$@" ;;
|
|
status) status ;;
|
|
down) down ;;
|
|
destroy) destroy ;;
|
|
*) echo "usage: lp-testdisk {up [count] [size]|status|down|destroy}" >&2; exit 2 ;;
|
|
esac
|