Your NVMe drive has been keeping secrets. Fenris makes it confess โ in real time. How much did you write today? How long until it taps out? No fairy dust โ just your actual bytes.
---
Fenris is a tiny, stubborn daemon that eavesdrops on your NVMe drive's SMART gossip, writes it down every few minutes, and serves you a live dashboard that actually means something. Not "vibes" โ **real GB written in the last 24 hours, real GB/hour, and a real countdown in hours, days, and years until your drive's endurance runs out**.
> Think of it as a Fitbit for your SSD. Except it doesn't nag you to drink water.
## What it actually does (no hand-waving)
- **Listens** โ polls `smartctl -j` on your NVMe device (default every 5 minutes, you pick).
- **Remembers** โ appends every sample to `data/history.jsonl` and rolls up per-hour totals into `data/hourly.jsonl` (survives restarts, rebuilds itself if you yank the power).
- **Calculates** โ rolling 24-hour window: *exact* bytes written in the last 24h, GB/h, GB/day, implied total TBW from `percentage_used`, remaining TB, and a projected life-remaining breakdown. Warming-up badge until it has 24h of coverage โ no fake confidence.
- **Shows off** โ dense, live dashboard with wear-over-time + trailing-24h per-hour bars, sticky header, live countdown, and stale warnings if the daemon dozes off.
## You need
- **Python 3.7+**
- **smartmontools** (`smartctl`)
- Root-ish access to read NVMe SMART (passwordless `smartctl` or just run with `sudo` โ your call)
### The sudo dance (one time)
Fenris runs `sudo -n smartctl ...` so it doesn't get stuck asking for a password mid-nap:
```bash
sudo visudo
# add this line (swap in your username):
youruser ALL=(root) NOPASSWD: /usr/sbin/smartctl
```
No sudo? Run the whole thing with `sudo` and it'll still behave.
## Get it running โ 30 seconds
### The cozy way
```bash
./fenris.sh
# pick 1) Start monitoring โ choose device / interval / port โ done
```
### The no-nonsense way
```bash
python3 fenris.py start # defaults: /dev/nvme0, every 300s, port 8420
python3 fenris.py start --interval 60 --port 9000 # if you're impatient
python3 fenris.py status # "are we live? how's the drive?"
python3 fenris.py sample # one sneaky sample right now
python3 fenris.py stop # tuck it back in
```
Dashboard lives at **http://localhost:8420** (or whatever port you chose).
## The menu, demystified
Run `./fenris.sh` and you'll get:
```
1) Start monitoring (background daemon + dashboard)
2) Stop monitoring
3) Status / current wear stats
4) Take one sample right now
5) Open dashboard URL
---
h) Help / how this works
q) Exit (go touch grass)
```
## What Fenris jots down
| Field | What's the gossip? |
|-------|---------------------|
| `percentage_used` | The drive's own wear-o-meter (0โ100%) |
| `bytes_written` / `bytes_read` | Lifetime totals โ the receipts |
| `available_spare` | Spare blocks left (%) |
| `media_errors` | Uncorrectable boo-boos |
| `power_on_hours` | How long it's been awake |
| `temperature_c` | Is it sweating? |
| `critical_warning` | NVMe's panic flags |
Hourly rollups also stash `bytes_written` per hour, `pct_start`/`pct_end`, and temp peaks โ so the 24h math stays honest.
## The dashboard โ what's on screen
- **Hero card: Projected life remaining** โ big, friendly `361 d 2 h` (plus `โ 361 days ยท โ 8666 hours ยท โ 0.99 years`), backed by `~280 GB/day` and `~101 TB left of ~202 TB total` on the test box.
- **Data written (24h)** โ exact GB in the rolling window + coverage (`10.4h of 24h` until warmed up).
- **Write rate** โ GB/h and GB/day, live.
- **Wear, spare, temp, errors, power-on** โ the usual suspects, with progress bars and polite color-coding.
- **Two charts, side by side:** wear over time + trailing-24h hourly write bars (with a cheeky "now" bar for the current partial hour).
- **Live plumbing:** polling synced to your interval, ETag-cached, countdown to next sample, warming-up + stale banners, pauses when you hide the tab (saves your battery, you're welcome).
**API for the tinkerers:** `GET /api/data` ยท `/api/hourly` ยท `/api/summary` ยท `/api/config` ยท `/api/status` โ all JSON, all friendly.
## Where's my stuff?
```
fenris/
โโโ fenris.py # the whole show โ daemon + server + math
โโโ fenris.sh # the cozy menu
โโโ README.md # hi โ you're here
โโโ assets/bongbetic-brand/ # Bongbetic wordmarks & glyphs (for Gitea + dashboard)
โโโ data/
โโโ history.jsonl # raw samples (JSONL, append-only)
โโโ hourly.jsonl # per-hour rollups (auto-rebuilt on restart)
โโโ fenris.pid # daemon PID
โโโ fenris.log # daemon chatter
```
## CLI cheat sheet
```bash
python3 fenris.py start [--device /dev/nvme0] [--interval 300] [--port 8420]
python3 fenris.py stop
python3 fenris.py status
python3 fenris.py sample [--device /dev/nvme0]
python3 fenris.py run # foreground mode โ what `start` spawns internally
```
## Oops โ troubleshooting without the tears
**"smartctl not found"**
```bash
sudo apt install smartmontools # Debian/Ubuntu
sudo pacman -S smartmontools # Arch โ you already knew
```
**"needs root" / permission denied**
Set up the passwordless line above, or just `sudo ./fenris.sh`.
**Dashboard says "stale"**
Daemon napped or crashed. `python3 fenris.py status` will tell you. Kick it again with `start`.
**Only 10 hours of data and it says "preliminary"?**
That's honesty, not a bug. It needs 24h of real writes to give a tight estimate. Let it simmer โ the number gets sharper every hour.
---
Fenris ๐บ โ by Bongbetic ยท Be kind to your SSD and it'll be kind to you.