# 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 the host's native scheduler. 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** or **runit**, with a polkit agent (the collector runs as root; 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: F937E81D2FB0736B15BC611884BEBD586DFAC010 ``` 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 / openSUSE Tumbleweed (RPM) 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 ``` On openSUSE Tumbleweed, add the same standard RPM repository file and install with zypper: ```bash sudo zypper addrepo --refresh \ https://git.bongbetic.com/xavierk/Fenris/raw/branch/main/packaging/fenris.repo fenris sudo zypper 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). ### Void Linux (XBPS) Void x86_64 with glibc and runit is the native target. Its signed XBPS channel is available from the permanent repository below. Add it, refresh its metadata, and install the released package: ```bash sudo install -d -m 0755 /etc/xbps.d echo 'repository=https://git.bongbetic.com/xavierk/Fenris-xbps/raw/branch/stable/x86_64' \ | sudo tee /etc/xbps.d/fenris.conf sudo xbps-install -M -S fenris ``` XBPS requires remote repositories to be signed. On the first refresh it displays the repository signing key embedded in the signed metadata; accept it only when its RSA SHA256 fingerprint is `SHA256:AvPMRlKMikPg75u0iKr8AUkxlfU/Ad4k/S4o2M9W4/w`. The public key is also available at `https://git.bongbetic.com/xavierk/Fenris-xbps/raw/branch/stable/keys/fenris-xbps-signing.pub`. For later updates, always refresh first so XBPS fetches the current index: ```bash sudo xbps-install -M -Syu ``` The `-M` flag bypasses XBPS's on-disk repodata cache. It is required when checking for a newly published package through Gitea's cached raw-file URL. The runit service remains dormant after installation. `fenris monitor resume` creates `/var/service/fenris-collect`; pause removes that link and records a deliberate disable in the observation history. Fenris keeps the observation store root-written and readable by the `fenris` group. Add each TUI user to that group, then start a new login session before running Fenris: ```bash sudo usermod -aG fenris "$USER" ``` ### 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. Its native scheduler is present but disabled; nothing runs. The only opt-in is the sanctioned toggle: ```bash fenris monitor resume # enable scheduling + open first monitoring period fenris monitor pause # close the period, disable scheduling ``` ## Development install (make install) For contributors building from source: ```bash sudo make install ``` This builds a wheel, installs its locked pure-Python runtime packages into `/opt/fenris/vendor`, 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 sudo xbps-install -Syu # Void Linux ``` ### Development upgrade ```bash sudo make upgrade ``` What it does: 1. Snapshots `observations.db` to a one-generation backup (`.bak`). 2. Replaces the locked runtime packages under `/opt/fenris/vendor`. 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`. On Void, pause monitoring first, copy the compatible snapshot back to `/var/lib/fenris/observations.db`, then force-install the matching older package version. If that version is no longer indexed, add its retained XBPS archive to a local repository with `xbps-rindex -a` and use `xbps-install -R -f fenris-`. Installing an older package over a newer observation store is unsupported. ## 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 sudo xbps-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 runtime packages, 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 **3 minutes** (`OnUnitInactiveSec=3min` 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. On Void, Fenris uses its native runit service instead: its initial collection is delayed by two minutes and later collections run three minutes after the previous run finishes. Inspect its state and diagnostics with: ```bash sv status fenris-collect sudo tail -n 50 /var/log/fenris-collect/current ``` `fenris status` also reports the separate boot-enabled, runtime-active, collection outcome, and observation-store freshness facts. A failed collection is retried at the next interval; it never fabricates missing observations. ## 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 native scheduling and closes the monitoring period. | | `fenris monitor resume` | Sanctioned enable β€” enables native scheduling 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. | ## Reading the dashboard ![Chalktone dashboard with a dotted activity plot](assets/dashboard-chalktone.png) Preview uses synthetic observations, not measurements from a real drive. The Chalktone dashboard opens with a large **Live** activity plot. **Day** shows hourly evidence for a selected date, labelled UTC, and **History** shows daily evidence. Local-day totals keep their recorded timezone. Dotted traces show measured read/write volumes, not transfer speed; missing evidence breaks the trace. `?` marks a gap, `~` a partial total, and `u` unallocated daily volume. Use the selected-point readout for exact values and evidence state. Local-day totals use measured intervals inside each recorded local date. An interval that crosses local midnight appears once as shared evidence and stays outside both known totals. The dashboard labels current totals as so far and shows incomplete or unavailable dates without treating them as zero. Older UTC-only summaries cannot establish exact local-day totals. - `v` cycles Live / Day / History; the tabs are also clickable. - `←` / `β†’` inspect points; `w` switches read/write volume in every view. - `[` / `]` browse dates, `g` enters a date, and `t` returns to today/live. - `Tab` / `Shift+Tab` move focus; `z` expands the focused panel, and `z` or `Esc` restores it. Monitoring status and controls remain visible. - `s` cycles Chalktone, Amber, Nord, and High Contrast; saved theme preferences survive upgrades. `m` toggles reduced motion. On smaller terminals, textual summaries and scrollable panels keep evidence accessible. Pause, resume, collect, disclosures, help, and quit remain available in the fixed control row. Run `fenris` as your normal user to open the TUI dashboard. The dashboard does not need `sudo`. Use `sudo` for package installation and system configuration; pause, resume, and collect-now actions normally authenticate through polkit. If the observation store is inaccessible, add your login user to the `fenris` group with `sudo usermod -aG fenris "$USER"`, then log out and back in. If polkit authentication is unavailable, quit the dashboard and run only the required administrative action in your terminal: ```bash sudo fenris monitor resume # Enable monitoring now and across reboots sudo fenris monitor pause # Confirm a deliberate monitoring pause sudo fenris sample # Request one collection run ``` Reopen the dashboard with `fenris` afterward. Press `?` for these instructions and keyboard controls at any time; use the arrow keys to scroll and `Esc` to close. The CLI and TUI share the same authenticated action path. Authentication and waiting for collection have no separate dashboard deadline; the native collector enforces its 90-second runtime limit. An interrupted or failed action is not automatically retriedβ€”check `fenris status` before retrying. - **Continuity** β€” the service strip's continuity line (and `fenris status`) reports whether monitoring survives reboots: `monitoring: active in background Β· persists across reboots`, or `monitoring: does not start on next boot`. - **Paused vs. quit** β€” a full-width `monitoring: paused β€” deliberate disable` block means collection is stopped (`fenris monitor pause`); resume with `fenris monitor resume`. Pressing `q` only leaves the screen β€” monitoring keeps running in the background. - **Auth banner** β€” the launch notice explains normal-user startup and polkit authentication, then clears on the first refresh. The `?` help screen remains available. Per-release notes live on the [releases page](https://git.bongbetic.com/xavierk/Fenris/releases): each entry is the version's `CHANGELOG.md` section β€” what was added, changed, and fixed β€” plus standing install and verification instructions. ## 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` | | Runtime packages | `/opt/fenris/vendor` | `/opt/fenris/vendor` | | Legacy history | β€” | `./data/history.jsonl` (auto-imported) | ---

Fenris 🐺 β€” by Bongbetic Β· Be kind to your SSD and it'll be kind to you.
Bongbetic icon