357 lines
15 KiB
Markdown
357 lines
15 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 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: <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 / 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 <local-repository> -f fenris-<version>`. 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 <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. |
|
|
|
|
## Reading the dashboard
|
|
|
|

|
|
|
|
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) |
|
|
|
|
---
|
|
|
|
<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>
|