Implement the signing and consumer-repo trust infrastructure: - Makefile: add generate-test-key, sign-rpm, checksums, clearsign targets; make release now automates the full build→sign→checksum→clearsign flow - Key ceremony: document the import→sign→delete lifecycle, key rotation outline, and private-key-in-password-manager policy - Public key: update placeholder with raw URL, algorithm, and ceremony ref - Consumer docs: README now covers apt signed-by keyring flow, dnf repo file setup, signature verification commands, and migration runbook link - Release spec: updated to reference ceremony doc and rpmsign workflow - Tests: 36 structural signing tests (nfpm config, Makefile targets, repo file, key publication, ceremony doc, consumer docs, spec refs) plus throwaway-key RPM signature and clearsign mechanics; no network or real key required Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
225 lines
8.2 KiB
Markdown
225 lines
8.2 KiB
Markdown
# Fenris 🐺
|
|
|
|
*Observes an NVMe drive's real-world use and translates that history into an understandable endurance outlook.*
|
|
|
|
Fenris is a persistent TUI monitor backed by a short-lived privileged collector on a systemd timer. It reads SMART data every few minutes, stores compact observation history in SQLite, and recomputes a usage-adjusted theoretical lifespan on every screen render — no fairy dust, just your actual bytes.
|
|
|
|
---
|
|
|
|
## Requirements
|
|
|
|
- **Python ≥ 3.9** (verified at install time; ≥ 3.10 for packages)
|
|
- **smartmontools** (`smartctl` — verified at install time)
|
|
- **systemd** with a polkit agent (the collector runs as root oneshot; elevation is exclusively polkit)
|
|
|
|
No other OS packages or Python dependencies beyond [Textual](https://textual.textualize.io/) (pinned in the lockfile).
|
|
|
|
## Install from package (recommended)
|
|
|
|
### Debian / Ubuntu (apt)
|
|
|
|
The Gitea instance Debian registry signs metadata with its own key. Verify the
|
|
instance key fingerprint (TOFU hardening):
|
|
|
|
```text
|
|
Fingerprint: <print after first release — paste beside the curl one-liner>
|
|
```
|
|
|
|
Add the instance key and repository:
|
|
|
|
```bash
|
|
sudo mkdir -p /etc/apt/keyrings
|
|
sudo curl -fsSL -o /etc/apt/keyrings/gitea-xavierk.asc \
|
|
https://git.bongbetic.com/api/packages/xavierk/debian/repository.key
|
|
|
|
echo "deb [signed-by=/etc/apt/keyrings/gitea-xavierk.asc] \
|
|
https://git.bongbetic.com/api/packages/xavierk/debian bookworm main" \
|
|
| sudo tee /etc/apt/sources.list.d/fenris.list
|
|
|
|
sudo apt update && sudo apt install fenris
|
|
```
|
|
|
|
Replace `bookworm` with your distribution codename (`bookworm`, `jammy`, or
|
|
`noble`).
|
|
|
|
### Fedora (dnf)
|
|
|
|
Use the Fenris-owned repo file (not Gitea's auto-generated one):
|
|
|
|
```bash
|
|
sudo dnf config-manager --add-repo \
|
|
https://git.bongbetic.com/xavierk/Fenris/raw/branch/main/packaging/fenris.repo
|
|
|
|
sudo dnf install fenris
|
|
```
|
|
|
|
The repo file sets `gpgcheck=1` against the Fenris packaging key (downloaded
|
|
from the raw URL in `gpgkey`) and `repo_gpgcheck=0` (metadata check left to
|
|
TLS).
|
|
|
|
### Package signature verification
|
|
|
|
The RPM payload is signed with the Fenris packaging key (RSA 3072).
|
|
Verification happens automatically via dnf's `gpgcheck=1`. For manual
|
|
verification of downloaded assets:
|
|
|
|
```bash
|
|
rpm -Kv fenris-*.x86_64.rpm # RPM payload signature
|
|
gpg --verify SHA256SUMS.asc SHA256SUMS # Clearsigned checksum manifest
|
|
sha256sum -c SHA256SUMS # Checksum match
|
|
```
|
|
|
|
The packaging public key is published in-repo — no keyservers. See
|
|
`packaging/keys/fenris-packaging.asc` and
|
|
`docs/install/signing-key-ceremony.md` for key lifecycle details.
|
|
|
|
### Dormant install
|
|
|
|
A fresh package install is fully dormant. Units are present but disabled;
|
|
nothing runs. The only opt-in is the sanctioned toggle:
|
|
|
|
```bash
|
|
fenris monitor resume # enable timer + open first monitoring period
|
|
fenris monitor pause # close the period, disable timer
|
|
```
|
|
|
|
## Development install (make install)
|
|
|
|
For contributors building from source:
|
|
|
|
```bash
|
|
sudo make install
|
|
```
|
|
|
|
This builds a wheel, installs it into `/opt/fenris` with pinned dependencies,
|
|
and places helpers, units, and the polkit policy. Units are dormant by default.
|
|
|
|
```bash
|
|
sudo make upgrade # re-sync wheel, units, schema
|
|
make uninstall # removes artifacts, preserves config and store
|
|
make purge # also removes /etc/fenris and /var/lib/fenris
|
|
```
|
|
|
|
## Upgrade
|
|
|
|
### Package upgrade
|
|
|
|
```bash
|
|
sudo apt update && sudo apt upgrade fenris # Debian/Ubuntu
|
|
sudo dnf upgrade fenris # Fedora
|
|
```
|
|
|
|
### Development upgrade
|
|
|
|
```bash
|
|
sudo make upgrade
|
|
```
|
|
|
|
What it does:
|
|
1. Snapshots `observations.db` to a one-generation backup (`.bak`).
|
|
2. Installs the new wheel into the same venv with pinned dependencies.
|
|
3. Syncs units and polkit against the manifest; runs `daemon-reload`.
|
|
4. Restarts the timer **only** if unit contents changed **and** it is active — a running collection run finishes on its mapped interpreter; the next run uses the new code.
|
|
5. Applies forward-only schema migrations (the store directory is never rebuilt; automatic downgrade does not exist).
|
|
|
|
Rollback: reinstall the previous version and restore `observations.db.bak`.
|
|
|
|
## Migration from make install
|
|
|
|
If Fenris was previously installed with `sudo make uninstall` first, then
|
|
installed from the package, existing config, store, and group survive by path
|
|
continuity. Over-installing the package over a `make install` is
|
|
**forbidden** — stale units shadow vendor placement. See
|
|
[docs/install/migrate-from-makeinstall.md](docs/install/migrate-from-makeinstall.md).
|
|
|
|
## Uninstall and purge
|
|
|
|
### Package removal
|
|
|
|
```bash
|
|
sudo apt remove fenris # preserves config and store
|
|
sudo apt purge fenris # also removes config and store
|
|
sudo dnf remove fenris # preserves config and store
|
|
```
|
|
|
|
### Development removal
|
|
|
|
```bash
|
|
make uninstall # removes artifacts, preserves config and observation history
|
|
make purge # also removes /etc/fenris and /var/lib/fenris
|
|
```
|
|
|
|
Uninstall performs the sanctioned disable first (`fenris-monitor disable --now`) — an open period closes `user_disabled` — then removes the venv, helpers, units, polkit policy, and wrapper while keeping `/etc/fenris` and the observation store. Reinstalling resumes from the preserved store.
|
|
|
|
## Cadence drop-ins
|
|
|
|
The default collection cadence is **5 minutes** (`OnUnitInactiveSec=5min` in the timer unit). To change it, place a systemd drop-in:
|
|
|
|
```bash
|
|
sudo systemctl edit fenris-collect.timer
|
|
# Add:
|
|
# [Timer]
|
|
# OnUnitInactiveSec=10min
|
|
```
|
|
|
|
No interval key exists in `/etc/fenris/fenris.conf`. Cadence is a systemd concern, not a Fenris configuration key.
|
|
|
|
## CLI reference
|
|
|
|
| Command | Behavior |
|
|
|---|---|
|
|
| `fenris` | Opens the TUI (no arguments). |
|
|
| `fenris status` | Projection facts, enabled/active state, last collect outcome, journal hint on failure or staleness. Never auto-samples. |
|
|
| `fenris sample` | On-demand collection via the privileged helper. Blocks until the run completes. |
|
|
| `fenris monitor pause` | Sanctioned disable — asks for confirmation, then disables the timer and closes the monitoring period. |
|
|
| `fenris monitor resume` | Sanctioned enable — enables the timer and opens a monitoring period. No confirmation. |
|
|
| `fenris baseline set <json>` | CLI-side validation, then polkit-guarded persistence. |
|
|
| `fenris baseline clear` | Remove the endurance baseline. |
|
|
| `fenris import <path>` | Idempotent single-transaction legacy import. |
|
|
| `fenris start` / `stop` / `run` | Rejected with a one-line migration pointer — never aliased. |
|
|
| `fenris --device` | Rejected with a pointer to the configuration file. |
|
|
|
|
## Retired menu options
|
|
|
|
The legacy `fenris.sh` menu script and the `fenris.py` monolith have been removed. Here's where the old options went:
|
|
|
|
| Legacy option | Successor |
|
|
|---|---|
|
|
| 1) Start monitoring | `fenris monitor resume` |
|
|
| 2) Stop monitoring | `fenris monitor pause` |
|
|
| 3) Status / current wear stats | `fenris status` |
|
|
| 4) Take one sample right now | `fenris sample` |
|
|
| 5) Open dashboard URL | Removed — the HTML dashboard and HTTP server are gone; the TUI is the primary interface. |
|
|
|
|
## Configuration
|
|
|
|
`/etc/fenris/fenris.conf` holds exactly one key — the device selector:
|
|
|
|
```
|
|
device = /dev/disk/by-id/nvme-Samsung_SSD_980_PRO_2TB_S6BENS0Txxxxx
|
|
```
|
|
|
|
Use a stable `/dev/disk/by-id/` path. Raw `/dev/nvmeX` paths are warned against. The file is re-read every collection run.
|
|
|
|
## Where's my stuff?
|
|
|
|
| Artifact | Package install | make install |
|
|
|---|---|---|
|
|
| Wrapper | `/usr/bin/fenris` | `/usr/local/bin/fenris` |
|
|
| Helpers | `/usr/libexec/fenris/` | `/usr/libexec/fenris/` |
|
|
| Units | `/usr/lib/systemd/system/` (vendor) | `/etc/systemd/system/` |
|
|
| Polkit policy | `/usr/share/polkit-1/actions/` | `/usr/share/polkit-1/actions/` |
|
|
| sysusers/tmpfiles | `/usr/lib/{sysusers,tmpfiles}.d/fenris.conf` | managed by Makefile |
|
|
| Configuration | `/etc/fenris/fenris.conf` | `/etc/fenris/fenris.conf` |
|
|
| Observation store | `/var/lib/fenris/observations.db` | `/var/lib/fenris/observations.db` |
|
|
| Venv | `/opt/fenris` | `/opt/fenris` |
|
|
| Legacy history | — | `./data/history.jsonl` (auto-imported) |
|
|
|
|
---
|
|
|
|
<p align="center">
|
|
<sub>Fenris 🐺 — by <a href="https://bongbetic.com">Bongbetic</a> · Be kind to your SSD and it'll be kind to you.</sub>
|
|
<br>
|
|
<img src="assets/bongbetic-brand/icon-dark-512.png" width="64" alt="Bongbetic icon">
|
|
</p>
|