Ship Fenris as native deb + rpm packages (execute the release plan) #44

Closed
opened 2026-09-02 20:18:15 +00:00 by xavierk · 1 comment
Owner

Problem Statement

Fenris can only be installed by cloning the repository and running sudo make install: the user needs git, a build toolchain, and root on their own machine, system updates never reach Fenris, upgrades require re-running a make target, and there is no signed, native artifact for Debian, Ubuntu, or Fedora users to install with the package manager they already trust. A fresh install also has no package-manager-native story for the fenris group, the observation-store directory, the configuration file, or removal that preserves observation history.

Solution

Fenris ships as native, signed deb and rpm packages built from one configuration by nfpm and published to the self-hosted Gitea package registry — a single channel. A Debian 12 / Ubuntu 22.04 / Ubuntu 24.04 user adds one apt source and installs with apt install fenris; a Fedora 40+ user adds the Fenris-owned repo file and installs with dnf install fenris. Installs are dormant by default (nothing runs until the sanctioned toggle), upgrades arrive as ordinary package upgrades that snapshot and forward-migrate the observation store while preserving local configuration edits, removal keeps observation history and configuration, and systems previously installed via make install migrate by a short remove-then-install runbook that the package enforces with a guard. The maintainer drives releases with one command: build both formats, sign, upload to the registry, and attach artifacts plus a clearsigned checksum manifest to a Gitea release entry.

User Stories

  1. As a Debian 12 user, I want to install Fenris with apt install fenris, so that I get a working install without cloning a repository or building anything.
  2. As an Ubuntu 22.04 user, I want the same one-command install, so that Fenris works on my LTS release without dependency gymnastics.
  3. As an Ubuntu 24.04 user, I want the same one-command install, so that I am not forced to wait for a distro package of the UI framework.
  4. As a Fedora 40+ user, I want to add the Fenris repo file and install with dnf install fenris, so that dnf verifies the package natively and updates it alongside my system.
  5. As any of these users, I want the package to carry the UI framework vendored inside it, so that my distro's outdated packaged copy never matters.
  6. As any of these users, I want the install to be dormant — units present but disabled, nothing running — so that monitoring starts only when I opt in through the sanctioned toggle, exactly like the documented lifecycle.
  7. As a user, I want the package to depend only on the system interpreter, smartmontools, and systemd, so that installation is small and predictable.
  8. As a user, I want apt upgrade / dnf upgrade to carry Fenris, so that fixes reach me through the update flow I already run.
  9. As a user upgrading, I want my observation store snapshotted to a one-generation backup and then forward-migrated, so that an upgrade can never silently destroy history.
  10. As a user upgrading, I want a collection run in flight to finish on the code that started it, so that no sample is lost mid-upgrade.
  11. As a user upgrading, I want my hand-edited configuration preserved and any changed package default offered alongside as a .dpkg-new/.rpmnew file, so that my device selector never gets clobbered.
  12. As a user upgrading, I want an active timer restarted only when its unit contents actually changed, so that upgrades do not disturb my monitoring cadence needlessly.
  13. As a user removing the package, I want my observation store and configuration kept, so that history survives reinstall.
  14. As a user purging (deb), I want configuration and store removed too, so that purge means a clean slate.
  15. As a user removing or purging, I want an open monitoring period closed as a deliberate disable, so that the removed span is excluded from my usage habit rather than left ambiguous.
  16. As a user removing the package on upgrade, I want removal scripts to do nothing, so that an upgrade never interrupts monitoring.
  17. As a user, I want installing an older package over a newer store to fail loudly, so that rollback is an explicit, documented act rather than silent corruption.
  18. As a user downloading artifacts manually, I want a clearsigned SHA256SUMS, so that I can verify integrity without trusting the transport alone.
  19. As an rpm user, I want the payload signed with Fenris's own key, so that dnf's signature check actually verifies the thing I install.
  20. As a user setting up the repo, I want the exact key fingerprint documented beside the setup command, so that I know I am trusting the right signer.
  21. As a user on a fresh install, I want a placeholder-commented configuration file and an empty observation store, so that the system honestly presents "not yet configured / no observations yet" instead of fabricated data.
  22. As a user, I want the service group and store directory created declaratively with correct permissions, so that the store is readable exactly as the observation-store ADR fixes.
  23. As a user of a system previously installed via make install, I want a short migration runbook, so that I can move to packages without losing observation history or configuration.
  24. As a migrating user, I want the package to abort installation with a pointer to the runbook when make-install remnants are detected, so that stale unit files can never silently shadow the package's units.
  25. As a migrating user, I want my store, group, directory permissions, and hand-written configuration to survive the migration in place, so that migration costs at most one short sample gap.
  26. As a developer, I want make install kept as a fallback, so that machines outside the package matrix remain installable.
  27. As a developer, I want the existing test suite untouched in its behavior, so that packaging work cannot regress the application.
  28. As the maintainer, I want both formats built from one packaging configuration, so that the deb and rpm can never drift apart.
  29. As the maintainer, I want the package version derived from the project version plus a revision field, so that a rebuild of the same version never collides with the registry's duplicate rejection.
  30. As the maintainer, I want file lists generated from a staged tree, so that no hand-maintained manifest can go stale.
  31. As the maintainer, I want the packaging private key fetched from the password manager, used, and deleted within a release, so that no machine permanently holds signing material.
  32. As the maintainer, I want one release command that builds, signs, uploads, and attaches everything, so that a Release is atomic rather than a sequence of error-prone manual steps.
  33. As the maintainer, I want every version tag to come with packages, a release entry, notes, and checksums, so that a bare tag can never masquerade as a Release.
  34. As the maintainer, I want a dormant tag-triggered CI workflow committed, so that automation can take over when a runner exists, without changing the manual flow today.
  35. As an auditor, I want package ownership recorded entirely in the dpkg/rpm database, so that there is no second, hand-rolled manifest to disagree with it.
  36. As an auditor, I want the observation store and its sidecars never owned by the package, so that package removal provably cannot delete observation history.

Implementation Decisions

  • Toolchain: nfpm builds both formats from a single packaging configuration with per-format overrides; two invocations (one per format). No fpm, no hand-written rpm spec, no debhelper stack. A staging script produces the file tree the config consumes.
  • Staging: a dedicated build step creates a --copies virtualenv at the fixed install root /opt/fenris containing the built wheel with pinned dependencies, plus the CLI wrapper, the two privileged helpers (fenris-monitor, fenris-collect), the systemd units, the polkit policy, and sysusers.d/tmpfiles.d fragments. The packaging config consumes the staged tree as one content entry — no hand-maintained file lists.
  • Package identity: name fenris, architecture x86_64, version <project version>-1 with the revision bumped on rebuilds of the same version. Declared dependencies exactly: system python3 (>= 3.10), smartmontools, systemd. The UI framework and all Python dependencies are vendored in the bundled venv; no distro Python package dependency is ever declared.
  • Layout and ownership (package-owned; the package database is the manifest — the hand-rolled placement manifest model is retired): venv at /opt/fenris; wrapper at /usr/bin/fenris; helpers in /usr/libexec/fenris (exactly the existing two — no new polkit-reachable binaries); units in /usr/lib/systemd/system (vendor placement; /etc/systemd/system is admin-only); polkit policy in /usr/share/polkit-1/actions/; sysusers fragment declaring the fenris group; tmpfiles fragment declaring /var/lib/fenris (mode, owner, group per the observation-store ADR); configuration at /etc/fenris/fenris.conf shipped as a placeholder-commented default marked conffile (deb) / noreplace (rpm). The package owns the store directory only — the database, WAL sidecars, and backups are never owned and never ghosted.
  • Maintainer scripts:
    • Install guard: abort with a pointer to the migration runbook if either make-install marker exists (the legacy placement manifest, or a unit file under /etc/systemd/system). Never auto-clean.
    • Install: apply sysusers, apply tmpfiles, daemon-reload. Never enable, preset, or start; no preset file ships.
    • Upgrade: snapshot the observation store to its one-generation backup, run forward-only schema migration via an inline interpreter invocation from the bundled venv, daemon-reload, and restart the collect timer only if unit contents changed and it is active. Never rebuild /var/lib/fenris.
    • Removal: perform the sanctioned disable (closing any open monitoring period as a deliberate disable) on remove/erase only — never on upgrade (deb upgrade case is a no-op; rpm scriptlet gated on the erase-count convention).
    • Removal mapping: deb remove ≈ uninstall (config and store kept); deb purge ≈ purge (plus backup and group cleanup); rpm erase ≈ uninstall (unmodified config removed, modified kept as .rpmsave); rpm purge is a documented manual command.
  • Configuration semantics: the device selector is entered by hand (root edits the file) in every delivery; no configuration verb is added to the privileged helper, keeping the polkit surface at exactly one binary. Upgrades preserve local edits and land changed defaults beside them.
  • Channel: the self-hosted Gitea package registry, single channel, owner public for anonymous consumers. One deb artifact published to each codename pool (bookworm, jammy, noble); one rpm artifact published to a single group serving Fedora 40+ collectively. Duplicate filenames are rejected by the registry — revision bumps handle rebuilds.
  • Signing: the rpm payload is signed at build time with a dedicated RSA-3072 packaging key (single key, no subkeys, two-year expiry); the deb is unsigned (apt trust flows from the instance-signed archive metadata over TLS); the SHA256SUMS manifest is clearsigned with the packaging key. The public key is published in-repo (its raw URL doubles as the dnf gpgkey target), in release notes, and in the docs. Private key lives only in the password manager; each release performs import → sign → delete. Rotation, when needed, is a dual-key window over one release cycle.
  • Consumer setup docs: apt instructions use a signed-by keyring with the instance archive-key fingerprint printed beside the setup command; dnf instructions add a Fenris-owned repo file (gpgkey pointing at the published packaging key, metadata check left to TLS). The registry's auto-generated repo file is never referenced in docs — it configures verification against a key that never signed our payload.
  • Release mechanics: a Release is a version tag, its packages in the channel, a release entry with notes, and a clearsigned checksum manifest — all together; bare tags are forbidden. The release make target performs build, signing, registry upload, and asset attachment in one step. Cadence is on-demand with plain semver; rollback is documented as restore-snapshot-then-install-old-release; automatic downgrade is unsupported by design.
  • CI: a dormant tag-triggered workflow is committed replicating the manual release; no runners exist today, so the manual flow is primary and the workflow queues harmlessly. Provisioning a runner is a separate, optional future step.
  • Dev fallback: make install remains as-is for machines outside the package matrix; packages and make-installs are mutually exclusive per machine, enforced by the install guard.

Testing Decisions

  • What makes a good test here: assert only externally observable behavior — the state of a system after real package-manager operations (which files exist and are owned, whether the timer is enabled or running, whether the store and config survived, what the guard printed) — never the internals of the staging script, the packaging config, or the maintainer scripts. The packages are treated as black boxes installed by real apt/dnf.
  • Single test seam (as agreed): the built package artifacts, exercised in throwaway per-distro containers matching the compatibility matrix. A containerized matrix drives install → assert dormant layout and ownership; upgrade → assert snapshot, forward migration, conffile survival (.dpkg-new/.rpmnew), and conditional timer restart; remove/purge → assert the store/config survival-or-removal mapping; and the migration guard → assert abort on make-install remnants. Both formats and every scriptlet contract are covered by this one seam.
  • Release/publish path: the release target grows a dry-run mode whose constructed commands are asserted at the same boundary; the live registry upload is verified once, manually, with a throwaway package (publish, verify metadata and signature checks, delete) — the same live-probe pattern the registry research used. No repeatable automated seam against the live Gitea instance.
  • Prior art: the existing pytest suite (store, migration, monitoring-period, status, TUI tests) is the model for behavioral assertions; the acceptance-criteria register's evidence classes (automated test / scripted system probe / manual checklist) map cleanly onto this plan — container matrix as automated tests, live registry probe as a scripted system probe, key ceremony as the manual checklist.
  • No CI dependency: the matrix runs locally via the standard test entry point; packaging tests must not require the registry, the signing key, or network access.

Out of Scope

  • Stable/testing channel split, COPR/PPA fallback, arm64 builds — deferred until demand appears (recorded as map fog on the release-plan effort).
  • Snap, Flatpak, AppImage, Homebrew, and PyPI as install paths.
  • Key-rotation procedure details beyond the dual-key outline.
  • A dev-local install mode for machines carrying both a checkout and packages.
  • Provisioning a Gitea Actions runner (documented as an optional future upgrade only).
  • Instance archive-key lifecycle concerns (migration/reinstall of the Gitea host).
  • Operating an ongoing release cadence — this effort delivers the machinery and documentation; releases then happen on demand.

Further Notes

The decision-complete rationale lives in the release and packaging specification (decision record of the "Fenris deb + rpm release plan" Wayfinder effort) and in the package-delivery ADR amending the installation-lifecycle ADR; those documents are normative for this work and should be read before implementation. Runtime semantics — dormant install, polkit-only elevation, snapshot plus forward-only migration, one-generation rollback — are inherited verbatim from the existing ADRs and redesign specification; nothing in this effort may weaken them. Terminology follows the project glossary, in particular Release, Rollback, observation store, monitoring period, and deliberate disable. The first production Release is the natural end-to-end acceptance of this effort, gated on the human-held key ceremony.

## Problem Statement Fenris can only be installed by cloning the repository and running `sudo make install`: the user needs git, a build toolchain, and root on their own machine, system updates never reach Fenris, upgrades require re-running a make target, and there is no signed, native artifact for Debian, Ubuntu, or Fedora users to install with the package manager they already trust. A fresh install also has no package-manager-native story for the fenris group, the observation-store directory, the configuration file, or removal that preserves observation history. ## Solution Fenris ships as native, signed deb and rpm packages built from one configuration by nfpm and published to the self-hosted Gitea package registry — a single channel. A Debian 12 / Ubuntu 22.04 / Ubuntu 24.04 user adds one apt source and installs with `apt install fenris`; a Fedora 40+ user adds the Fenris-owned repo file and installs with `dnf install fenris`. Installs are dormant by default (nothing runs until the sanctioned toggle), upgrades arrive as ordinary package upgrades that snapshot and forward-migrate the observation store while preserving local configuration edits, removal keeps observation history and configuration, and systems previously installed via `make install` migrate by a short remove-then-install runbook that the package enforces with a guard. The maintainer drives releases with one command: build both formats, sign, upload to the registry, and attach artifacts plus a clearsigned checksum manifest to a Gitea release entry. ## User Stories 1. As a Debian 12 user, I want to install Fenris with `apt install fenris`, so that I get a working install without cloning a repository or building anything. 2. As an Ubuntu 22.04 user, I want the same one-command install, so that Fenris works on my LTS release without dependency gymnastics. 3. As an Ubuntu 24.04 user, I want the same one-command install, so that I am not forced to wait for a distro package of the UI framework. 4. As a Fedora 40+ user, I want to add the Fenris repo file and install with `dnf install fenris`, so that dnf verifies the package natively and updates it alongside my system. 5. As any of these users, I want the package to carry the UI framework vendored inside it, so that my distro's outdated packaged copy never matters. 6. As any of these users, I want the install to be dormant — units present but disabled, nothing running — so that monitoring starts only when I opt in through the sanctioned toggle, exactly like the documented lifecycle. 7. As a user, I want the package to depend only on the system interpreter, smartmontools, and systemd, so that installation is small and predictable. 8. As a user, I want `apt upgrade` / `dnf upgrade` to carry Fenris, so that fixes reach me through the update flow I already run. 9. As a user upgrading, I want my observation store snapshotted to a one-generation backup and then forward-migrated, so that an upgrade can never silently destroy history. 10. As a user upgrading, I want a collection run in flight to finish on the code that started it, so that no sample is lost mid-upgrade. 11. As a user upgrading, I want my hand-edited configuration preserved and any changed package default offered alongside as a `.dpkg-new`/`.rpmnew` file, so that my device selector never gets clobbered. 12. As a user upgrading, I want an active timer restarted only when its unit contents actually changed, so that upgrades do not disturb my monitoring cadence needlessly. 13. As a user removing the package, I want my observation store and configuration kept, so that history survives reinstall. 14. As a user purging (deb), I want configuration and store removed too, so that purge means a clean slate. 15. As a user removing or purging, I want an open monitoring period closed as a deliberate disable, so that the removed span is excluded from my usage habit rather than left ambiguous. 16. As a user removing the package on upgrade, I want removal scripts to do nothing, so that an upgrade never interrupts monitoring. 17. As a user, I want installing an older package over a newer store to fail loudly, so that rollback is an explicit, documented act rather than silent corruption. 18. As a user downloading artifacts manually, I want a clearsigned SHA256SUMS, so that I can verify integrity without trusting the transport alone. 19. As an rpm user, I want the payload signed with Fenris's own key, so that dnf's signature check actually verifies the thing I install. 20. As a user setting up the repo, I want the exact key fingerprint documented beside the setup command, so that I know I am trusting the right signer. 21. As a user on a fresh install, I want a placeholder-commented configuration file and an empty observation store, so that the system honestly presents "not yet configured / no observations yet" instead of fabricated data. 22. As a user, I want the service group and store directory created declaratively with correct permissions, so that the store is readable exactly as the observation-store ADR fixes. 23. As a user of a system previously installed via `make install`, I want a short migration runbook, so that I can move to packages without losing observation history or configuration. 24. As a migrating user, I want the package to abort installation with a pointer to the runbook when make-install remnants are detected, so that stale unit files can never silently shadow the package's units. 25. As a migrating user, I want my store, group, directory permissions, and hand-written configuration to survive the migration in place, so that migration costs at most one short sample gap. 26. As a developer, I want `make install` kept as a fallback, so that machines outside the package matrix remain installable. 27. As a developer, I want the existing test suite untouched in its behavior, so that packaging work cannot regress the application. 28. As the maintainer, I want both formats built from one packaging configuration, so that the deb and rpm can never drift apart. 29. As the maintainer, I want the package version derived from the project version plus a revision field, so that a rebuild of the same version never collides with the registry's duplicate rejection. 30. As the maintainer, I want file lists generated from a staged tree, so that no hand-maintained manifest can go stale. 31. As the maintainer, I want the packaging private key fetched from the password manager, used, and deleted within a release, so that no machine permanently holds signing material. 32. As the maintainer, I want one release command that builds, signs, uploads, and attaches everything, so that a Release is atomic rather than a sequence of error-prone manual steps. 33. As the maintainer, I want every version tag to come with packages, a release entry, notes, and checksums, so that a bare tag can never masquerade as a Release. 34. As the maintainer, I want a dormant tag-triggered CI workflow committed, so that automation can take over when a runner exists, without changing the manual flow today. 35. As an auditor, I want package ownership recorded entirely in the dpkg/rpm database, so that there is no second, hand-rolled manifest to disagree with it. 36. As an auditor, I want the observation store and its sidecars never owned by the package, so that package removal provably cannot delete observation history. ## Implementation Decisions - **Toolchain**: nfpm builds both formats from a single packaging configuration with per-format overrides; two invocations (one per format). No fpm, no hand-written rpm spec, no debhelper stack. A staging script produces the file tree the config consumes. - **Staging**: a dedicated build step creates a `--copies` virtualenv at the fixed install root `/opt/fenris` containing the built wheel with pinned dependencies, plus the CLI wrapper, the two privileged helpers (`fenris-monitor`, `fenris-collect`), the systemd units, the polkit policy, and sysusers.d/tmpfiles.d fragments. The packaging config consumes the staged tree as one content entry — no hand-maintained file lists. - **Package identity**: name `fenris`, architecture x86_64, version `<project version>-1` with the revision bumped on rebuilds of the same version. Declared dependencies exactly: system `python3 (>= 3.10)`, `smartmontools`, `systemd`. The UI framework and all Python dependencies are vendored in the bundled venv; no distro Python package dependency is ever declared. - **Layout and ownership** (package-owned; the package database is the manifest — the hand-rolled placement manifest model is retired): venv at `/opt/fenris`; wrapper at `/usr/bin/fenris`; helpers in `/usr/libexec/fenris` (exactly the existing two — no new polkit-reachable binaries); units in `/usr/lib/systemd/system` (vendor placement; `/etc/systemd/system` is admin-only); polkit policy in `/usr/share/polkit-1/actions/`; sysusers fragment declaring the `fenris` group; tmpfiles fragment declaring `/var/lib/fenris` (mode, owner, group per the observation-store ADR); configuration at `/etc/fenris/fenris.conf` shipped as a placeholder-commented default marked conffile (deb) / `noreplace` (rpm). The package owns the store *directory* only — the database, WAL sidecars, and backups are never owned and never ghosted. - **Maintainer scripts**: - Install guard: abort with a pointer to the migration runbook if either make-install marker exists (the legacy placement manifest, or a unit file under `/etc/systemd/system`). Never auto-clean. - Install: apply sysusers, apply tmpfiles, `daemon-reload`. Never enable, preset, or start; no preset file ships. - Upgrade: snapshot the observation store to its one-generation backup, run forward-only schema migration via an inline interpreter invocation from the bundled venv, `daemon-reload`, and restart the collect timer only if unit contents changed and it is active. Never rebuild `/var/lib/fenris`. - Removal: perform the sanctioned disable (closing any open monitoring period as a deliberate disable) on remove/erase only — never on upgrade (deb upgrade case is a no-op; rpm scriptlet gated on the erase-count convention). - Removal mapping: deb remove ≈ uninstall (config and store kept); deb purge ≈ purge (plus backup and group cleanup); rpm erase ≈ uninstall (unmodified config removed, modified kept as `.rpmsave`); rpm purge is a documented manual command. - **Configuration semantics**: the device selector is entered by hand (root edits the file) in every delivery; no configuration verb is added to the privileged helper, keeping the polkit surface at exactly one binary. Upgrades preserve local edits and land changed defaults beside them. - **Channel**: the self-hosted Gitea package registry, single channel, owner public for anonymous consumers. One deb artifact published to each codename pool (bookworm, jammy, noble); one rpm artifact published to a single group serving Fedora 40+ collectively. Duplicate filenames are rejected by the registry — revision bumps handle rebuilds. - **Signing**: the rpm payload is signed at build time with a dedicated RSA-3072 packaging key (single key, no subkeys, two-year expiry); the deb is unsigned (apt trust flows from the instance-signed archive metadata over TLS); the SHA256SUMS manifest is clearsigned with the packaging key. The public key is published in-repo (its raw URL doubles as the dnf gpgkey target), in release notes, and in the docs. Private key lives only in the password manager; each release performs import → sign → delete. Rotation, when needed, is a dual-key window over one release cycle. - **Consumer setup docs**: apt instructions use a `signed-by` keyring with the instance archive-key fingerprint printed beside the setup command; dnf instructions add a Fenris-owned repo file (gpgkey pointing at the published packaging key, metadata check left to TLS). The registry's auto-generated repo file is never referenced in docs — it configures verification against a key that never signed our payload. - **Release mechanics**: a Release is a version tag, its packages in the channel, a release entry with notes, and a clearsigned checksum manifest — all together; bare tags are forbidden. The release make target performs build, signing, registry upload, and asset attachment in one step. Cadence is on-demand with plain semver; rollback is documented as restore-snapshot-then-install-old-release; automatic downgrade is unsupported by design. - **CI**: a dormant tag-triggered workflow is committed replicating the manual release; no runners exist today, so the manual flow is primary and the workflow queues harmlessly. Provisioning a runner is a separate, optional future step. - **Dev fallback**: `make install` remains as-is for machines outside the package matrix; packages and make-installs are mutually exclusive per machine, enforced by the install guard. ## Testing Decisions - **What makes a good test here**: assert only externally observable behavior — the state of a system after real package-manager operations (which files exist and are owned, whether the timer is enabled or running, whether the store and config survived, what the guard printed) — never the internals of the staging script, the packaging config, or the maintainer scripts. The packages are treated as black boxes installed by real apt/dnf. - **Single test seam (as agreed)**: the built package artifacts, exercised in throwaway per-distro containers matching the compatibility matrix. A containerized matrix drives install → assert dormant layout and ownership; upgrade → assert snapshot, forward migration, conffile survival (`.dpkg-new`/`.rpmnew`), and conditional timer restart; remove/purge → assert the store/config survival-or-removal mapping; and the migration guard → assert abort on make-install remnants. Both formats and every scriptlet contract are covered by this one seam. - **Release/publish path**: the release target grows a dry-run mode whose constructed commands are asserted at the same boundary; the live registry upload is verified once, manually, with a throwaway package (publish, verify metadata and signature checks, delete) — the same live-probe pattern the registry research used. No repeatable automated seam against the live Gitea instance. - **Prior art**: the existing pytest suite (store, migration, monitoring-period, status, TUI tests) is the model for behavioral assertions; the acceptance-criteria register's evidence classes (automated test / scripted system probe / manual checklist) map cleanly onto this plan — container matrix as automated tests, live registry probe as a scripted system probe, key ceremony as the manual checklist. - **No CI dependency**: the matrix runs locally via the standard test entry point; packaging tests must not require the registry, the signing key, or network access. ## Out of Scope - Stable/testing channel split, COPR/PPA fallback, arm64 builds — deferred until demand appears (recorded as map fog on the release-plan effort). - Snap, Flatpak, AppImage, Homebrew, and PyPI as install paths. - Key-rotation procedure details beyond the dual-key outline. - A dev-local install mode for machines carrying both a checkout and packages. - Provisioning a Gitea Actions runner (documented as an optional future upgrade only). - Instance archive-key lifecycle concerns (migration/reinstall of the Gitea host). - Operating an ongoing release cadence — this effort delivers the machinery and documentation; releases then happen on demand. ## Further Notes The decision-complete rationale lives in the release and packaging specification (decision record of the "Fenris deb + rpm release plan" Wayfinder effort) and in the package-delivery ADR amending the installation-lifecycle ADR; those documents are normative for this work and should be read before implementation. Runtime semantics — dormant install, polkit-only elevation, snapshot plus forward-only migration, one-generation rollback — are inherited verbatim from the existing ADRs and redesign specification; nothing in this effort may weaken them. Terminology follows the project glossary, in particular *Release*, *Rollback*, *observation store*, *monitoring period*, and *deliberate disable*. The first production Release is the natural end-to-end acceptance of this effort, gated on the human-held key ceremony.
xavierk added the ready-for-agent label 2026-09-02 20:18:21 +00:00
xavierk self-assigned this 2026-09-03 09:49:07 +00:00
Author
Owner

Resolution

The full packaging, signing, release, and testing infrastructure is implemented and committed across issues 45-52. All 310 non-containerized tests pass.

Implementation summary

  • Build: nfpm-based deb + rpm from a single packaging/nfpm.yaml with packaging/stage.sh
  • Maintainer scripts: deb (preinst/postinst/prerm/postrm) + rpm (pre/post/preun/postun) with correct upgrade/removal/migration semantics
  • Signing: RPM payload signed via rpmsign, SHA256SUMS clearsigned, public key published in-repo
  • Release: one-command scripts/release.sh (build, sign, upload, attach) with dry-run mode
  • CI: dormant .gitea/workflows/release.yml ready for a self-hosted runner
  • Tests: containerized acceptance matrix (dormant install, upgrade, removal, migration guard, Python 3.10 floor) + structural tests for release flow, signing mechanics, and consumer docs
  • Docs: README consumer setup, migration runbook, signing key ceremony

Review fixes applied

  1. nfpm.yaml: type config changed to config_noreplace (RPM preserves user edits on upgrade)
  2. nfpm.yaml: type ghost changed to type dir for /var/lib/fenris (deb compatibility)
  3. Timer restart: capture running unit before daemon-reload so the diff detects changes
  4. README: Python floor corrected to 3.10
  5. Signing key ceremony: corrected RPM signing path (post-build rpmsign, not nfpm)
  6. Test helpers extracted to shared conftest.py
  7. CI workflow: VERSION extracted once via GITHUB_OUTPUT
## Resolution The full packaging, signing, release, and testing infrastructure is implemented and committed across issues 45-52. All 310 non-containerized tests pass. ### Implementation summary - **Build**: nfpm-based deb + rpm from a single packaging/nfpm.yaml with packaging/stage.sh - **Maintainer scripts**: deb (preinst/postinst/prerm/postrm) + rpm (pre/post/preun/postun) with correct upgrade/removal/migration semantics - **Signing**: RPM payload signed via rpmsign, SHA256SUMS clearsigned, public key published in-repo - **Release**: one-command scripts/release.sh (build, sign, upload, attach) with dry-run mode - **CI**: dormant .gitea/workflows/release.yml ready for a self-hosted runner - **Tests**: containerized acceptance matrix (dormant install, upgrade, removal, migration guard, Python 3.10 floor) + structural tests for release flow, signing mechanics, and consumer docs - **Docs**: README consumer setup, migration runbook, signing key ceremony ### Review fixes applied 1. nfpm.yaml: type config changed to config_noreplace (RPM preserves user edits on upgrade) 2. nfpm.yaml: type ghost changed to type dir for /var/lib/fenris (deb compatibility) 3. Timer restart: capture running unit before daemon-reload so the diff detects changes 4. README: Python floor corrected to 3.10 5. Signing key ceremony: corrected RPM signing path (post-build rpmsign, not nfpm) 6. Test helpers extracted to shared conftest.py 7. CI workflow: VERSION extracted once via GITHUB_OUTPUT
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: xavierk/Fenris#44