#!/bin/bash # Install LibrePortal across the combinations of relocatable roots and check # each one landed where it was asked to. # # sudo scripts/dev/lp-install-matrix up # loopback disks first # sudo scripts/dev/lp-install-matrix 1|2|3|4|all # sudo scripts/dev/lp-install-matrix verify N # re-check without reinstalling # # The three roots are independently relocatable, and the interesting failures # are the ones where only ONE of them moves: paths get baked into root-owned # helpers, a systemd unit and the CLI wrapper at install time, so a root that is # resolved at runtime instead of baked works on a default install and silently # points at the wrong disk on a relocated one. Testing only "everything default" # or only "everything moved" misses exactly that. # # 1 system / apps / baseline # 2 system / apps disk2 app data on its own disk # 3 system disk1 apps / control plane on its own disk # 4 system disk1 apps disk2 all three roots moved (backups too) set -u REPO="$(cd "$(dirname "$0")/../.." && pwd)" D1="${LP_TESTDISK_MNT:-/mnt/lptest}1" D2="${LP_TESTDISK_MNT:-/mnt/lptest}2" LOG_DIR=/var/tmp/lp-matrix mkdir -p "$LOG_DIR" [[ $EUID -eq 0 ]] || { echo "lp-install-matrix: run with sudo" >&2; exit 1; } case_dirs() { # $1 = case -> sets SYS/CON/BAK case "$1" in 1) SYS=/libreportal-system; CON=/libreportal-containers; BAK=/libreportal-backups ;; 2) SYS=/libreportal-system; CON="$D2/libreportal-containers"; BAK=/libreportal-backups ;; 3) SYS="$D1/libreportal-system"; CON=/libreportal-containers; BAK=/libreportal-backups ;; 4) SYS="$D1/libreportal-system"; CON="$D2/libreportal-containers"; BAK="$D2/libreportal-backups" ;; *) echo "unknown case '$1'" >&2; return 2 ;; esac } fail=0 chk() { if [[ "$2" == "$3" ]]; then printf ' ok %-42s %s\n' "$1" "$2" else printf ' FAIL %-42s got %s want %s\n' "$1" "$2" "$3"; fail=1; fi; } chk_true() { if eval "$2" >/dev/null 2>&1; then printf ' ok %s\n' "$1" else printf ' FAIL %s\n' "$1"; fail=1; fi; } # Which filesystem a path actually sits on. The point of the whole exercise: # a root that resolved to the right STRING but the wrong DEVICE is the bug. devof() { stat -c '%d' "$1" 2>/dev/null || echo "-"; } verify() { local c="$1"; case_dirs "$c" || return 2 fail=0 echo "── case $c ─────────────────────────────────────────────" printf ' system=%s\n apps=%s\n backups=%s\n\n' "$SYS" "$CON" "$BAK" chk_true "system root exists" "[[ -d '$SYS' ]]" chk_true "containers root exists" "[[ -d '$CON' ]]" chk_true "backups root exists" "[[ -d '$BAK' ]]" # On the intended DEVICE, not merely at the intended path. chk "system on expected fs" "$(devof "$SYS")" "$(devof "$(dirname "$SYS")")" chk "containers on expected fs" "$(devof "$CON")" "$(devof "$(dirname "$CON")")" chk "system owner" "$(stat -c '%U' "$SYS" 2>/dev/null)" "libreportal" chk "containers owner" "$(stat -c '%U' "$CON" 2>/dev/null)" "dockerinstall" # Baked, not resolved: the whole trust model rests on these being fixed at # install so the manager cannot redirect a root operation. local w=/usr/local/lib/libreportal/libreportal chk "wrapper LP_SYSTEM_DIR" "$(grep -oP '(?<=^LP_SYSTEM_DIR=")[^"]+' "$w" 2>/dev/null | head -1)" "$SYS" chk "wrapper LP_CONTAINERS_DIR" "$(grep -oP '(?<=^LP_CONTAINERS_DIR=")[^"]+' "$w" 2>/dev/null | head -1)" "$CON" local o=/usr/local/lib/libreportal/libreportal-ownership chk "ownership helper SYSTEM_DIR" "$(grep -oP '(?<=^SYSTEM_DIR=")[^"]+' "$o" 2>/dev/null | head -1)" "$SYS" chk "ownership helper CONTAINERS_DIR" "$(grep -oP '(?<=^CONTAINERS_DIR=")[^"]+' "$o" 2>/dev/null | head -1)" "$CON" chk_true "no unbaked __PLACEHOLDER__ left" "! grep -q '__[A-Z_]*__' $o" chk_true "systemd unit installed" "[[ -f /etc/systemd/system/libreportal.service ]]" chk_true "CLI on PATH" "[[ -x /usr/local/bin/libreportal ]]" chk_true "scoped sudoers present" "[[ -f /etc/sudoers.d/libreportal ]]" # The WebUI is the thing a person actually opens. local port port=$(su -c 'docker ps --filter name=libreportal-service --format "{{.Ports}}"' dockerinstall 2>/dev/null \ | grep -oE '0\.0\.0\.0:[0-9]+' | head -1 | cut -d: -f2) if [[ -n "$port" ]]; then printf ' ok %-42s http://127.0.0.1:%s\n' "WebUI container up" "$port" local code; code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "http://127.0.0.1:$port/" 2>/dev/null) chk "WebUI responds" "$code" "200" echo "$port" > "$LOG_DIR/case$c.port" else echo " FAIL WebUI container not running"; fail=1 fi echo "" if (( fail )); then echo " case $c: FAILED"; else echo " case $c: passed"; fi return $fail } install_case() { local c="$1"; case_dirs "$c" || return 2 echo "== case $c: uninstalling ==" ( cd "$REPO" && bash init.sh --unattended --skip-rootless uninstall ) \ > "$LOG_DIR/case$c-uninstall.log" 2>&1 echo " uninstall rc=$? (log: $LOG_DIR/case$c-uninstall.log)" echo "== case $c: installing ==" printf ' system=%s\n apps=%s\n backups=%s\n' "$SYS" "$CON" "$BAK" ( cd "$REPO" && bash init.sh --random-password --local --unattended \ --skip-os-update --skip-prereqs \ --system-dir="$SYS" --containers-dir="$CON" --backups-dir="$BAK" init ) \ > "$LOG_DIR/case$c-install.log" 2>&1 echo " install rc=$? (log: $LOG_DIR/case$c-install.log)" echo "" verify "$c" } case "${1:-}" in up) bash "$REPO/scripts/dev/lp-testdisk" up 2 30G ;; verify) verify "${2:?case number}" ;; 1|2|3|4) install_case "$1" ;; all) rc=0; for c in 1 2 3 4; do install_case "$c" || rc=1; echo; done; exit $rc ;; *) sed -n '2,20p' "$0"; exit 2 ;; esac