LibrePortal/docs/DEVELOPMENT.md
librelad a48a241fbe docs: add docs/USER.md + docs/DEVELOPMENT.md (+ README pointer)
Two guides covering what wasn't written down:
- USER.md: install (the install.sh one-liner), placing the three roots on separate
  disks/external drives, channels, updating, backups (REQUIRE_MOUNT), uninstall.
- DEVELOPMENT.md: the install-mode/roots/users model + key files; running a dev copy
  (local/git); cutting stable/edge releases (bump VERSION -> make_release.sh ->
  dist/<channel>/{tarball,.sha256,latest.json} -> publish); testing a release
  locally via LP_RELEASE_BASE_URL + python3 -m http.server (incl. checksum-refusal);
  how release updates work; conventions.

README Quick start updated to the release flow + a docs pointer. docs/ is
export-ignored so it doesn't bloat release tarballs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-25 18:27:59 +01:00

5.7 KiB

LibrePortal — Development & Releases

How to run a development copy, cut stable/edge releases, and test them before they go out. For installing/using LibrePortal, see USER.md.

Mental model (read this first)

Install modesCFG_INSTALL_MODE decides where the code comes from:

Mode Source Use
release (default) a checksum-verified .tar.gz over HTTPS end users / stable
git git clone of the repo contributors tracking a branch
local a copy of a local folder hacking on the code on the box

Three roots (each relocatable at install, then fixed): <system> (manager-owned: configs/db/logs/install) · <containers> (container user: app data) · <backups> (container user: repos). Defaults /libreportal-*.

Two users: the manager (sudo_user_name, default libreportal) owns the control plane and runs the runtime; the container user (CFG_DOCKER_INSTALL_USER, default dockerinstall) owns app data + runs rootless Docker. Genuine-root actions go through fixed, root-owned helpers in /usr/local/lib/libreportal/ (paths + manager name are baked into them at install — never read from runtime config).

Key files:

  • init.sh — the installer (self-contained; creates users/folders/helpers, bakes things).
  • install.sh — the thin bootstrap (download+verify+extract a release, then run init.sh).
  • scripts/source/paths.sh — resolves the three roots + manager user.
  • scripts/source/fetch.shlpFetchRelease / lpVersionGt (runtime fetch + version compare).
  • scripts/release/make_release.sh — builds release artifacts.
  • VERSION — the single source of the version number.

Run a development copy

From a clone of the repo, on a throwaway Debian/Ubuntu host (install is destructive — it creates system users and dirs):

Local mode (install from the working tree — best for hacking):

sudo ./init.sh --random-password --local init
# custom locations work in dev too:
sudo ./init.sh --random-password --local --system-dir=/srv/lp --manager-user=lpadmin init

Git mode (track a branch):

# via the bootstrap:
sudo ./install.sh --git-url=https://example.com/you/LibrePortal.git \
                  --git-user=USER --git-token=TOKEN
# or directly:  ./init.sh init <password> <git_user> <git_token> <git_url> true git

Iterating: re-run the installer to redeploy after changes (local mode re-copies the tree). To wipe and start over:

sudo ./init.sh uninstall                    # removes the three roots + users + footprint
sudo ./init.sh --skip-docker-images uninstall   # keep the Docker layer for a fast reinstall

This repo's CI/hook setup may auto-deploy on commit (commit on a branch → auto-merge → redeploy). That's environment-specific; the commands above are the portable way to stand up and refresh a dev box.

Cut a release (stable or edge)

  1. Bump the version in VERSION (semver, e.g. 0.2.00.3.0). Commit it.
  2. Build the artifact (uses git archive, so it ships only committed files and honours .gitattributes export-ignorescripts/unused, site, docs, .claude, the release tooling, etc. never ship):
    scripts/release/make_release.sh stable        # or: edge   [git-ref]
    
    Produces, under dist/<channel>/:
    • libreportal-<version>.tar.gz — the release
    • libreportal-<version>.tar.gz.sha256 — its checksum
    • latest.json{ version, channel, url, sha256, notes } (the channel pointer)
  3. Publish by copying dist/<channel>/* to the host so they're served at https://get.libreportal.org/<channel>/… (Phase E — the getlibreportal container; not built yet). latest.json is what makes a version "the latest".

Channels: stable is the default users get; edge is for early/testing builds. Same tooling, different <channel>. To promote an edge build to stable, rebuild with make_release.sh stable at that ref (or copy its artifacts into the stable/ path and update stable/latest.json).

Test a release locally before publishing

No hosting needed — serve dist/ and point an install at it:

# build, then serve the artifacts
scripts/release/make_release.sh stable
( cd dist && python3 -m http.server 8000 )

# on a throwaway host, install from your local server:
LP_RELEASE_BASE_URL=http://<your-ip>:8000 \
  sudo ./install.sh --channel=stable --system-dir=/libreportal-system

LP_RELEASE_BASE_URL overrides the release host everywhere (installer, updater, recovery). Quick non-destructive checks:

# fetch + verify + stage only (no install):
LP_RELEASE_BASE_URL=http://<ip>:8000 ./install.sh --dry-run
# tamper with the tarball and confirm it's refused:
echo x >> dist/stable/libreportal-*.tar.gz
LP_RELEASE_BASE_URL=http://<ip>:8000 ./install.sh --dry-run   # => CHECKSUM MISMATCH

How updates work (so you can reason about them)

In release mode the WebUI badge + libreportal update apply compare the local VERSION against the channel's latest.json (lpVersionGt); if newer, they lpFetchRelease the new tarball (verified) and redeploy. Because the install tree is code only (configs/logs/backups live in the other roots), the update just replaces it — no backup/restore dance. git/local modes keep their existing git-based update path.

Conventions

  • Versioning: semver in VERSION. Bump before building; latest.json carries it.
  • New runtime script? Add it under scripts/<area>/… and run scripts/source/files/generate_arrays.sh run so it's sourced (build/standalone tooling under scripts/release and scripts/system is intentionally excluded).
  • Don't make the OS footprint (/etc/*, /usr/local/*) relocatable — it's fixed by design for the privilege model.