# 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.10** (verified at install time) - **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: ``` 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 ` | CLI-side validation, then polkit-guarded persistence. | | `fenris baseline clear` | Remove the endurance baseline. | | `fenris import ` | 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) | ---

Fenris ๐Ÿบ โ€” by Bongbetic ยท Be kind to your SSD and it'll be kind to you.
Bongbetic icon