From 864059ab830e8e55d07d87a43cd77ac71b287615 Mon Sep 17 00:00:00 2001 From: librelad Date: Fri, 28 Aug 2026 06:31:02 +0100 Subject: [PATCH] paths: recover the roots from the baked unit when nothing exported them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three roots reach running code three ways — the CLI wrapper exports them, the task-processor unit carries them as Environment=, and anything those start inherits them. An @reboot crontab entry is started by none of the three: it runs a script by absolute path, so paths.sh fell through to : "${LP_CONTAINERS_DIR:=/libreportal-containers}" That entry is crontab_boot_app_reconcile.sh, which brings every installed app up at boot. On a relocated install it therefore reconciled against the DEFAULT root, and that does not fail — docker creates the bind-mount directories it does not find, so every app comes back empty while the real data sits untouched on the other disk. Nothing logs an error; the only symptom is opening an app and finding it blank (storage-locations §10.1 calls this the top data-integrity risk). Verified on a case-2 install (apps on /mnt/lptest2): a bare environment resolved containers_dir to /libreportal-containers/ where the real root was /mnt/lptest2/libreportal-containers. Recover them from the systemd unit, which is the authoritative baked record — init.sh already reads it back the same way, libreportal-relocate rewrites it, and it is root-owned, so this is not the manager reading a config it can edit. An explicit environment still wins; with no unit the defaults are unchanged. scripts/dev/lp-paths-roots-test pins all four cases; verified it fails when the recovery is removed. Co-Authored-By: Claude Opus 5 --- scripts/dev/lp-paths-roots-test | 66 +++++++++++++++++++++++++++++++++ scripts/source/paths.sh | 29 +++++++++++++++ 2 files changed, 95 insertions(+) create mode 100755 scripts/dev/lp-paths-roots-test diff --git a/scripts/dev/lp-paths-roots-test b/scripts/dev/lp-paths-roots-test new file mode 100755 index 0000000..0863eca --- /dev/null +++ b/scripts/dev/lp-paths-roots-test @@ -0,0 +1,66 @@ +#!/bin/bash +# Where do the three roots come from when nothing exported them? +# +# scripts/dev/lp-paths-roots-test +# +# The roots reach running code three ways: the CLI wrapper exports them, the +# task-processor unit carries them as Environment=, and anything those start +# inherits them. An @reboot crontab entry is started by none of the three — it +# runs a script by absolute path — so paths.sh fell through to the +# /libreportal-* defaults. +# +# That job is crontab_boot_app_reconcile.sh, which brings every installed app up +# at boot. Pointed at the wrong root it does not fail: docker creates the +# bind-mount directories it does not find, so every app comes back EMPTY while +# the real data sits untouched on the other disk. Nothing reports an error, and +# the damage is only visible by opening an app (storage-locations §10.1). +# +# So the case that matters is the second one: no environment, relocated install. + +REPO="$(cd "$(dirname "$0")/../.." && pwd)" +BASE="$(mktemp -d "${TMPDIR:-/tmp}/lp-paths-test-XXXXXX")" +trap 'rm -rf "$BASE"' EXIT + +fail=0 +chk(){ if [[ "$2" == "$3" ]]; then echo " ok $1"; else echo " FAIL $1: got '$2' want '$3'"; fail=1; fi; } + +# A stand-in for the baked task-processor unit. +cat > "$BASE/unit" < [env assignments...] + local var="$1"; shift + env -i PATH=/usr/bin:/bin "$@" bash -c \ + 'source "$0" >/dev/null 2>&1; eval echo "\$$1"' "$REPO/scripts/source/paths.sh" "$var" +} + +echo "--- no environment, unit present (the @reboot cron case) ---" +chk "system" "$(ask LP_SYSTEM_DIR LP_UNIT_FILE="$BASE/unit")" "/mnt/d1/libreportal-system" +chk "containers" "$(ask LP_CONTAINERS_DIR LP_UNIT_FILE="$BASE/unit")" "/mnt/d2/libreportal-containers" +chk "backups" "$(ask LP_BACKUPS_DIR LP_UNIT_FILE="$BASE/unit")" "/mnt/d2/libreportal-backups" +chk "derived containers_dir" \ + "$(ask containers_dir LP_UNIT_FILE="$BASE/unit")" "/mnt/d2/libreportal-containers/" +chk "derived configs_dir" \ + "$(ask configs_dir LP_UNIT_FILE="$BASE/unit")" "/mnt/d1/libreportal-system/configs/" + +echo "--- an explicit environment still wins over the unit ---" +chk "containers" \ + "$(ask LP_CONTAINERS_DIR LP_UNIT_FILE="$BASE/unit" LP_CONTAINERS_DIR=/explicit)" "/explicit" + +echo "--- no unit: unchanged default behaviour ---" +chk "system" "$(ask LP_SYSTEM_DIR LP_UNIT_FILE=/nonexistent)" "/libreportal-system" +chk "containers" "$(ask LP_CONTAINERS_DIR LP_UNIT_FILE=/nonexistent)" "/libreportal-containers" + +echo "--- a unit that names only the system root ---" +printf '[Service]\nEnvironment=LP_SYSTEM_DIR=/mnt/d1/libreportal-system\n' > "$BASE/partial" +chk "system" "$(ask LP_SYSTEM_DIR LP_UNIT_FILE="$BASE/partial")" "/mnt/d1/libreportal-system" +chk "containers -> default" "$(ask LP_CONTAINERS_DIR LP_UNIT_FILE="$BASE/partial")" "/libreportal-containers" + +echo "" +if (( fail )); then echo "FAILED"; exit 1; fi +echo "All root-resolution checks passed." diff --git a/scripts/source/paths.sh b/scripts/source/paths.sh index d7790b2..635f437 100644 --- a/scripts/source/paths.sh +++ b/scripts/source/paths.sh @@ -24,6 +24,35 @@ # derivations in sync. # --- Resolve the three roots ------------------------------------------------ +# Nothing in the environment? Recover them from the record root baked at install +# before falling back to the defaults below. +# +# The roots reach running code three ways: the CLI wrapper exports them, the +# task-processor unit carries them as Environment=, and everything started by +# those inherits them. An @reboot crontab entry is started by none of the three +# — it invokes a script by absolute path — so it fell through to the /libreportal-* +# defaults. On a relocated install that silently pointed the BOOT APP RECONCILE +# at the wrong disk, and that job brings every installed app up: docker creates +# the bind-mount directories it does not find, so the apps come back EMPTY while +# the real data sits untouched on the other disk (storage-locations §10.1). +# +# The unit is the authoritative record — init.sh reads it back the same way, and +# libreportal-relocate rewrites it — and it is root-owned, so this is not the +# manager reading a config it can edit. +if [[ -z "${LP_SYSTEM_DIR:-}" ]]; then + _lp_unit="${LP_UNIT_FILE:-/etc/systemd/system/libreportal.service}" + if [[ -r "$_lp_unit" ]]; then + _lp_v=$(grep -m1 -oE '^Environment=LP_SYSTEM_DIR=\S+' "$_lp_unit" 2>/dev/null); _lp_v="${_lp_v#*LP_SYSTEM_DIR=}" + [[ -n "$_lp_v" ]] && LP_SYSTEM_DIR="$_lp_v" + _lp_v=$(grep -m1 -oE '^Environment=LP_CONTAINERS_DIR=\S+' "$_lp_unit" 2>/dev/null); _lp_v="${_lp_v#*LP_CONTAINERS_DIR=}" + [[ -n "$_lp_v" && -z "${LP_CONTAINERS_DIR:-}" ]] && LP_CONTAINERS_DIR="$_lp_v" + _lp_v=$(grep -m1 -oE '^Environment=LP_BACKUPS_DIR=\S+' "$_lp_unit" 2>/dev/null); _lp_v="${_lp_v#*LP_BACKUPS_DIR=}" + [[ -n "$_lp_v" && -z "${LP_BACKUPS_DIR:-}" ]] && LP_BACKUPS_DIR="$_lp_v" + unset _lp_v + fi + unset _lp_unit +fi + # Transitional compat: an EXISTING install (the legacy single /docker tree, # identified by its config marker) keeps using /docker until a deliberate # reinstall to the split layout — so deploying new code never strands a running