129 lines
4.1 KiB
Bash
Executable File
129 lines
4.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Fenris — interactive menu for the NVMe wear monitor & dashboard.
|
|
# Created by Bongbetic.
|
|
|
|
set -euo pipefail
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PY="$SCRIPT_DIR/fenris.py"
|
|
PORT_DEFAULT=8420
|
|
INTERVAL_DEFAULT=300
|
|
|
|
banner() {
|
|
cat <<'EOF'
|
|
_____ _
|
|
| __|___ ___ _| |___
|
|
| __| -_| | . | _|
|
|
|__| |___|_|_|_|___|_|
|
|
|
|
NVMe wear monitor & live dashboard
|
|
Created by Bongbetic
|
|
EOF
|
|
}
|
|
|
|
pause() { read -rp "Press Enter to continue..." _; }
|
|
|
|
detect_device() {
|
|
# Query Python's auto-detect for the default device.
|
|
python3 -c "import sys; sys.path.insert(0,'$SCRIPT_DIR'); from fenris import detect_device; print(detect_device())" 2>/dev/null || echo /dev/nvme0
|
|
}
|
|
|
|
menu() {
|
|
clear
|
|
banner
|
|
echo
|
|
echo " 1) Start monitoring (background daemon + dashboard)"
|
|
echo " 2) Stop monitoring"
|
|
echo " 3) Status / current wear stats"
|
|
echo " 4) Take one sample right now"
|
|
echo " 5) Open dashboard URL"
|
|
echo " ---"
|
|
echo " h) Help / how this works"
|
|
echo " q) Exit"
|
|
echo
|
|
read -rp "Choose an option: " choice
|
|
echo
|
|
case "$choice" in
|
|
1) start_flow ;;
|
|
2) python3 "$PY" stop; pause ;;
|
|
3) python3 "$PY" status; pause ;;
|
|
4) read -rp "Device [default: auto-detect]: " dev
|
|
if [ -z "$dev" ]; then python3 "$PY" sample; else python3 "$PY" sample --device "$dev"; fi
|
|
pause ;;
|
|
5) show_url; pause ;;
|
|
h|H) help_text; pause ;;
|
|
q|Q) echo "Bye. — Fenris, by Bongbetic"; exit 0 ;;
|
|
*) echo "Invalid choice."; pause ;;
|
|
esac
|
|
}
|
|
|
|
start_flow() {
|
|
read -rp "NVMe device [Enter = auto-detect]: " dev
|
|
read -rp "Sample interval in seconds [Enter = ${INTERVAL_DEFAULT}]: " interval
|
|
read -rp "Dashboard port [Enter = ${PORT_DEFAULT}]: " port
|
|
interval="${interval:-$INTERVAL_DEFAULT}"
|
|
port="${port:-$PORT_DEFAULT}"
|
|
|
|
args=(start --interval "$interval" --port "$port")
|
|
if [ -n "${dev:-}" ]; then args+=(--device "$dev"); fi
|
|
|
|
echo
|
|
echo "Note: reading NVMe SMART data needs root."
|
|
echo "Fenris runs 'sudo -n smartctl ...' (no-prompt sudo). If this fails,"
|
|
echo "either run this menu with sudo, or allow passwordless smartctl via:"
|
|
echo " sudo visudo -> youruser ALL=(root) NOPASSWD: /usr/sbin/smartctl"
|
|
echo
|
|
|
|
python3 "$PY" "${args[@]}"
|
|
pause
|
|
}
|
|
|
|
show_url() {
|
|
if [ -f "$SCRIPT_DIR/data/fenris.pid" ]; then
|
|
# Try to read actual port from running process cmdline, else guess default.
|
|
local pid port
|
|
pid=$(<"$SCRIPT_DIR/data/fenris.pid")
|
|
port=$(tr '\0' '\n' < /proc/"$pid"/cmdline 2>/dev/null | grep -A1 -- '--port' | tail -1 || true)
|
|
port="${port:-$PORT_DEFAULT}"
|
|
echo "Dashboard: http://localhost:${port}"
|
|
else
|
|
echo "Fenris is not currently running. Start it first (option 1)."
|
|
fi
|
|
}
|
|
|
|
help_text() {
|
|
cat <<EOF
|
|
What Fenris does:
|
|
- Periodically reads your NVMe drive's SMART health data (via smartctl),
|
|
including "percentage_used" (the drive's own wear indicator), total
|
|
bytes written/read, temperature, spare capacity, and error counts.
|
|
- Logs every sample to: $SCRIPT_DIR/data/history.jsonl
|
|
and per-hour aggregates to: $SCRIPT_DIR/data/hourly.jsonl (GB/hour,
|
|
rebuilt from history on restart). Used for the trailing-24h bar chart
|
|
and exact rolling-24h write volume.
|
|
- Serves a live HTML dashboard (dense layout, interval-synced polling)
|
|
with wear-over-time and trailing-24h hourly-write charts, plus a
|
|
projected life-remaining estimate in hours/days/years derived from
|
|
your actual rolling-24h write rate and implied TBW endurance.
|
|
|
|
Requirements:
|
|
- smartmontools (smartctl) installed.
|
|
- Root access to read NVMe SMART logs — either run Fenris via sudo,
|
|
or set up passwordless sudo for smartctl (see option 1).
|
|
|
|
CLI usage (equivalent to this menu):
|
|
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]
|
|
|
|
Leave it running in the background (option 1) and check back after a
|
|
few days/weeks of normal use — more samples = a more accurate lifespan
|
|
estimate.
|
|
|
|
Fenris — created by Bongbetic.
|
|
EOF
|
|
}
|
|
|
|
# Loop instead of recurse to avoid stack overflow.
|
|
while true; do menu; done
|