- Upgrade path restarts only fenris-collect.timer, not fenris-collect.service (spec §7: "a running oneshot finishes on its old interpreter") - Remove redundant deb depends override in nfpm.yaml (top-level is sufficient) - Remove common.sh sourcing — scripts are self-contained to avoid path dependency when dpkg/rpm run them from /var/lib/dpkg/info/ Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
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)
- 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 (pinned in the lockfile).
Install
sudo make install
What it does:
- Builds a wheel from the checkout and installs it — with pinned dependencies — into the dedicated venv at
/opt/fenris. - Places the
fenriswrapper in/usr/local/bin, helpers in/usr/libexec/fenris, systemd units in/etc/systemd/system, and the polkit policy in/usr/share/polkit-1/actions/. - Creates
/var/lib/fenris(root-written, group-readable) — the observation store is created lazily by the first collection run. - Records every placed file in a manifest consumed by upgrade and uninstall.
- Detects
./data/history.jsonlbeside the source checkout and runs the idempotent legacy import if present.
A fresh install is fully dormant. Units are present but disabled; nothing runs. The only opt-in is the sanctioned toggle:
fenris monitor resume # enable timer + open first monitoring period
fenris monitor pause # close the period, disable timer
Upgrade
sudo make upgrade
What it does:
- Snapshots
observations.dbto a one-generation backup (.bak). - Installs the new wheel into the same venv with pinned dependencies.
- Syncs units and polkit against the manifest; runs
daemon-reload. - 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.
- 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.
Uninstall and purge
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:
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 | Location |
|---|---|
| Wrapper | /usr/local/bin/fenris |
| Helpers | /usr/libexec/fenris/fenris-collect, fenris-monitor |
| Units | /etc/systemd/system/fenris-collect.{timer,service} |
| Polkit policy | /usr/share/polkit-1/actions/com.bongbetic.fenris.monitor.policy |
| Configuration | /etc/fenris/fenris.conf |
| Observation store | /var/lib/fenris/observations.db |
| Venv | /opt/fenris |
| Manifest | /var/lib/fenris/manifest.txt |
| Legacy history | ./data/history.jsonl (auto-imported on install if present) |
Fenris 🐺 — by Bongbetic · Be kind to your SSD and it'll be kind to you.