7.4 KiB
7. Package delivery: native deb + rpm packages, amending the installation lifecycle
Status
Accepted — resolves Task: Compose release spec + ADR amending 0004 on the Wayfinder map. This ADR amends ADR 0004 on delivery and file ownership only; every runtime semantic of 0004 — dormant install, polkit-only elevation, observation-store snapshot + forward-only migration, one-generation rollback — is inherited verbatim, restated below where the package delivery changes who performs it.
Context
ADR 0004 fixed delivery as sudo make install from a source checkout: wheel into a Fenris-owned runtime directory at /opt/fenris, a hand-rolled placement manifest, units in /etc/systemd/system. The release plan (map, decisions Lock channel + toolchain, Signing + key policy, Package ownership, Migration path, Release cadence) now ships Fenris as native deb + rpm packages built by nfpm and published to the self-hosted Gitea 1.27.1 package registry, for Debian 12, Ubuntu 22.04/24.04, Fedora 40+, and openSUSE Tumbleweed (x86_64), with locked pure-Python dependencies vendored at /opt/fenris/vendor. Packages become the primary delivery; ADR 0004's delivery model demotes to a dev fallback.
The implementation-ready operative contracts live in the release and packaging specification; this ADR records the decisions and their rationale.
Decision
Amendments to ADR 0004, section by section:
- Delivery (amended). Packages are primary: one deb per codename pool (
bookworm,jammy,noble) and one rpm (groupfenris, Fedora 40+ and openSUSE Tumbleweed), built by nfpm from a singlepackaging/nfpm.yamlover locked pure-Python runtime packages staged at/opt/fenris/vendor, published to the Gitea Debian/RPM registry and installed withapt,dnf, orzypper.sudo make installremains as the dev fallback for machines without packages; the two deliveries are mutually exclusive per machine. Version scheme<pyproject-version>-1, revision bump on rebuild. - Layout and manifest (amended). The hand-rolled manifest model is retired: the dpkg/rpm database is the manifest, and nothing like
manifest.txtships. Package-owned layout: units in/usr/lib/systemd/system(vendor placement;/etc/systemd/systemis admin-only for drop-ins and enable state); helpers stay in/usr/libexec/fenris(exactlyfenris-monitorandfenris-collect— no new polkit-reachable binaries); polkit policy in/usr/share/polkit-1/actions/; wrapper at/usr/bin/fenris(FHS;/usr/local/binremainsmake install's). Thefenrisgroup is declared in/usr/lib/sysusers.d/fenris.conf(g fenris -) and/var/lib/fenrisin/usr/lib/tmpfiles.d/fenris.conf(d /var/lib/fenris 2750 root fenris -), both invoked from the maintainer scripts. The package owns the/var/lib/fenrisdirectory only;observations.db, WAL sidecars, and.bakare never owned and never ghosted — ghost-erase would delete the store, violating 0004 §8. - Privilege (unchanged). Root acts through maintainer scripts at install/upgrade/removal time; at runtime, elevation is exclusively polkit, exactly as 0004 §3 and ADR 0003 §5 fix it.
- Dormant install (restated for packages). A fresh package install is fully dormant: postinst/%post performs
systemctl daemon-reload(plussystemd-sysusersandsystemd-tmpfiles --create) and nothing else — never enable, never preset, never start; no preset file ships. The sanctioned toggle (fenris monitor resume) remains the only opt-in. - Legacy import (narrowed). Auto-detection of
./data/history.jsonlis scoped tomake installonly — a package install has no checkout to inspect.fenris import <path>remains available as the only import path from packages. - Upgrade (inherited, maintainer-script mechanics). Upgrades arrive as packages from the single registry channel. postinst/%post on upgrade: snapshot
observations.db→ one-generation.bak, run forward-only schema migrations through the targetpython3with/opt/fenris/vendoron its import path (no new binaries),daemon-reload, and restartfenris-collect.timeronly if unit contents changed and it is active./var/lib/fenrisis never rebuilt; a running oneshot finishes on its old interpreter. - Rollback (unchanged, plus one hard edge). One-generation
.baksemantics are unchanged. Package downgrade is additionally unsupported: forward-only store-version refusal means installing an older package over a newer store fails by design; documented rollback = restore the snapshot, then install the old release. - Removal (mapped). deb
remove≈make uninstall(conffile and store survive); debpurge≈make purge(plus.bakand group cleanup); rpm erase ≈make uninstall(unmodified config removed, modified survives as.rpmsave; purge is a documented manual command). prerm/%preun performs the sanctioned disable —fenris-monitor disable --now, closing the perioduser_disabled— on remove/erase only, never on upgrade (deb prerm upgrade case is a no-op; rpm%preungated on$1 -eq 0). - Conffile semantics (new).
/etc/fenris/fenris.confships as a placeholder-commented default with no active device selector — deb conffile, rpm%config(noreplace). The device selector is entered by hand (root edits the file), as in both prior deliveries; no configuration verb is added tofenris-monitor, and ADR 0003 §3's read-and-validate-at-collection-time semantics are untouched. On upgrade, local edits survive as-is; a changed package default lands beside them as.dpkg-new/.rpmnew. - Migration from make-install systems (new). Remove-then-install via runbook only — no migration script, no auto-clean. preinst/%pre aborts with a pointer to the runbook if make-install remnants are detected (
/var/lib/fenris/manifest.txtor/etc/systemd/system/fenris-collect.timer). Store and config survive by path continuity; the migration resets the system to dormant and the user opts back in withfenris monitor resume.
Consequences
- Package installs, upgrades, and removals carry dpkg/rpm-native semantics; nothing in Fenris's own tooling duplicates them.
- The manifest was 0004's answer to "what did the installer place"; the package database answers it better, and uninstall-keeps-store now holds by package ownership rather than by manifest discipline.
make installand packages are mutually exclusive per machine; over-install is blocked, not repaired (stale/etcunits would silently shadow vendor units).- Hand-edited configuration remains the model: the device selector is a root-edited file in every delivery, keeping the polkit surface at exactly one binary.
- Release mechanics — channel, signing, cadence, rollback documentation — are fixed in the release and packaging specification and the tickets it cites; this ADR deliberately stops at lifecycle semantics.