LibrePortal/docs/architecture/system-footprint.md
librelad 1e2674adc2 docs: fix drifts found in content audit (crowdsec helper, site, license link)
Audited every doc against the code. Three fixes:
- system-footprint.md: add the libreportal-crowdsec root helper row
  (init.sh installs 8 helpers; the table listed 7). appcfg row clarified
  to 'CrowdSec-bouncer' config since the new helper does the host install.
- .gitattributes: add 'site export-ignore' — development.md documents the
  website as never-shipping, but the rule was missing, so site/ was landing
  in release tarballs. No runtime refs to site/; hosting lives in the Infra repo.
- promise.md: fix LICENSE link (../../LICENSE) after the docs/ reshuffle.

Everything else (install-and-use, development, contributing) verified current:
all install/uninstall/update flags, release scripts, fetch fns, footprint_version,
service name, and config keys check out against the code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-31 00:55:19 +01:00

4.7 KiB

LibrePortal system footprint (outside the data roots)

LibrePortal's own data lives in three independently-relocatable roots, chosen at install (defaults shown) and owned by exactly one principal each:

Root (default) Owner Holds
/libreportal-system the manager user configs, logs, the install tree, the database, ssl/ssh
/libreportal-containers the container user live app data (one dir per app)
/libreportal-backups the container user backup repositories

This file catalogues the few things LibrePortal must place outside those roots to integrate with the host. The OS dictates where most of these live — sudoers, systemd units, sysctl, and $PATH entries can only be read from their fixed locations, so they can't sit inside a relocatable root. What we control, we keep together in /usr/local/lib/libreportal/; everything else is named libreportal* so the whole footprint is greppable and removable.

The three roots and the manager username are chosen at install and baked into the helpers / systemd unit / CLI wrapper below (sed placeholders, like the manager name has always been) — never read at runtime from a manager-writable config. That immutability is the privilege boundary: a root helper can't be redirected at, say, /etc by editing config. This footprint itself stays at fixed paths by design; only the data roots relocate.

Executables — /usr/local/lib/libreportal/ (root:root, our own dir)

File Owner Purpose
libreportal root the CLI wrapper (symlinked onto $PATH, see below)
uninstall.sh root the uninstaller (symlinked onto $PATH as libreportal-uninstall)
libreportal-ownership root reconcile the three-root ownership model
libreportal-dns root edit /etc/resolv.conf (nameservers)
libreportal-ssh-access root manage admin authorized_keys + sshd PasswordAuthentication
libreportal-socket root docker-socket read perms (type switcher)
libreportal-svc root generate/install the task-processor systemd unit
libreportal-bininstall root install the restic/kopia backup-engine binaries
libreportal-appcfg root rewrite AdGuard/CrowdSec-bouncer/ownCloud config files
libreportal-crowdsec root install/manage the host-side CrowdSec agent + firewall bouncer

These are the scoped-sudoers trust boundary: root-owned in a root-owned dir, so the manager can sudo them but can't modify them. Each bakes the three roots + the manager name at install. Source of truth: scripts/system/ in the repo; installed by init.shinitRootHelpers (re-installed only on a reinstall, not by the quick-deploy).

OS-mandated locations (must live where the OS reads them)

Path Owner Purpose
/usr/local/bin/libreportal root symlink/usr/local/lib/libreportal/libreportal (puts the CLI on $PATH)
/usr/local/bin/libreportal-uninstall root symlink/usr/local/lib/libreportal/uninstall.sh (location-agnostic uninstall command)
/etc/sudoers.d/<manager> root scoped least-privilege grant for the manager (drop-in named after the manager user)
/etc/systemd/system/libreportal.service root the task-processor service (User=<manager>; bakes the roots as Environment=LP_*_DIR; also drives the periodic regen poll)
/etc/sysctl.d/99-libreportal-hardening.conf root kernel LPE-surface hardening
/etc/sysctl.d/99-libreportal-rootless.conf root rootless sysctl settings + "rootless configured" marker

Third-party tools we install (not ours, conventional home)

/usr/local/bin/{restic,kopia,ufw-docker,docker-compose} — installed on demand (restic/kopia via the libreportal-bininstall helper). /usr/local/bin is the correct home for these; left under their own names.

System users

The manager (default libreportal, configurable via --manager-user= / LP_MANAGER_USER) and the container user (default dockerinstall, from CFG_DOCKER_INSTALL_USER) — each with a home under /home/. The rootless daemon config lives at ~<container-user>/.config/docker/daemon.json.

Uninstall sketch

init.sh uninstall does all of this; the sketch (with default roots/manager):

sudo systemctl disable --now libreportal.service
sudo rm -f /etc/systemd/system/libreportal.service /etc/sudoers.d/libreportal
sudo rm -f /etc/sysctl.d/99-libreportal-*.conf
sudo rm -rf /usr/local/lib/libreportal /usr/local/bin/libreportal /usr/local/bin/libreportal-uninstall
sudo rm -rf /libreportal-system /libreportal-containers /libreportal-backups
# optional: the backup-engine binaries and the two users