$ smartctl -a /dev/sdb
...
SMART overall-health self-assessment test result: PASSED
...
$ smartctl -a /dev/sdb
...
SMART overall-health self-assessment test result: PASSED
...
$ smartctl -a /dev/sdb
...
SMART overall-health self-assessment test result: PASSED
...
smartctl -a -d jmb39x,0 /dev/sdb # slot 1
smartctl -a -d jmb39x,1 /dev/sdb # slot 2
smartctl -a -d jmb39x,2 /dev/sdb # slot 3
smartctl -a -d jmb39x,3 /dev/sdb # slot 4
smartctl -a -d jmb39x,4 /dev/sdb # slot 5
smartctl -a -d jmb39x,0 /dev/sdb # slot 1
smartctl -a -d jmb39x,1 /dev/sdb # slot 2
smartctl -a -d jmb39x,2 /dev/sdb # slot 3
smartctl -a -d jmb39x,3 /dev/sdb # slot 4
smartctl -a -d jmb39x,4 /dev/sdb # slot 5
smartctl -a -d jmb39x,0 /dev/sdb # slot 1
smartctl -a -d jmb39x,1 /dev/sdb # slot 2
smartctl -a -d jmb39x,2 /dev/sdb # slot 3
smartctl -a -d jmb39x,3 /dev/sdb # slot 4
smartctl -a -d jmb39x,4 /dev/sdb # slot 5
👁️ Argus SMART Analysis — 2026-04-17T09:00:00+00:00 Samples: 28 (forecast window: 30d) Overall: WARNING ✅ ssd-system (SanDisk Ultra II 960GB) health=95/100 -weight: 500;">status=OK
✅ das-slot1 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot2 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot3 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot4 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
🟡 das-slot5 (WD Red Pro 8TB) health=70/100 -weight: 500;">status=WARNING 🟡 udma_crc_error_count=1 ≥ WARN (5) 📈 udma_crc_error_count: 1→100 forecast in 142d
👁️ Argus SMART Analysis — 2026-04-17T09:00:00+00:00 Samples: 28 (forecast window: 30d) Overall: WARNING ✅ ssd-system (SanDisk Ultra II 960GB) health=95/100 -weight: 500;">status=OK
✅ das-slot1 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot2 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot3 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot4 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
🟡 das-slot5 (WD Red Pro 8TB) health=70/100 -weight: 500;">status=WARNING 🟡 udma_crc_error_count=1 ≥ WARN (5) 📈 udma_crc_error_count: 1→100 forecast in 142d
👁️ Argus SMART Analysis — 2026-04-17T09:00:00+00:00 Samples: 28 (forecast window: 30d) Overall: WARNING ✅ ssd-system (SanDisk Ultra II 960GB) health=95/100 -weight: 500;">status=OK
✅ das-slot1 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot2 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot3 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
✅ das-slot4 (WD Red Pro 8TB) health=100/100 -weight: 500;">status=OK
🟡 das-slot5 (WD Red Pro 8TB) health=70/100 -weight: 500;">status=WARNING 🟡 udma_crc_error_count=1 ≥ WARN (5) 📈 udma_crc_error_count: 1→100 forecast in 142d
def linear_forecast(points: list, target: float) -> float | None: xs = [p[0] for p in points] ys = [p[1] for p in points] n = len(points) mean_x = sum(xs) / n mean_y = sum(ys) / n num = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) den = sum((x - mean_x) ** 2 for x in xs) if den == 0 or (slope := num / den) <= 0: return None x_target = (target - (mean_y - slope * mean_x)) / slope days_until = x_target - max(xs) return round(days_until, 1) if 0 < days_until <= 180 else None
def linear_forecast(points: list, target: float) -> float | None: xs = [p[0] for p in points] ys = [p[1] for p in points] n = len(points) mean_x = sum(xs) / n mean_y = sum(ys) / n num = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) den = sum((x - mean_x) ** 2 for x in xs) if den == 0 or (slope := num / den) <= 0: return None x_target = (target - (mean_y - slope * mean_x)) / slope days_until = x_target - max(xs) return round(days_until, 1) if 0 < days_until <= 180 else None
def linear_forecast(points: list, target: float) -> float | None: xs = [p[0] for p in points] ys = [p[1] for p in points] n = len(points) mean_x = sum(xs) / n mean_y = sum(ys) / n num = sum((x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)) den = sum((x - mean_x) ** 2 for x in xs) if den == 0 or (slope := num / den) <= 0: return None x_target = (target - (mean_y - slope * mean_x)) / slope days_until = x_target - max(xs) return round(days_until, 1) if 0 < days_until <= 180 else None
# /opt/argus/config/argus.conf [argus]
history_file = /var/lib/argus/argus-history.json
retention_days = 180 [ntfy]
url = http://your-ntfy:8080
topic = argus-disk [disk:ssd-system]
device = /dev/sda
type = sat
class = ssd [disk:das-slot1]
device = /dev/sdb
type = jmb39x,0
class = hdd [disk:das-slot2]
device = /dev/sdb
type = jmb39x,1
class = hdd [disk:das-slot3]
device = /dev/sdb
type = jmb39x,2
class = hdd [disk:das-slot4]
device = /dev/sdb
type = jmb39x,3
class = hdd [disk:das-slot5]
device = /dev/sdb
type = jmb39x,4
class = hdd
# /opt/argus/config/argus.conf [argus]
history_file = /var/lib/argus/argus-history.json
retention_days = 180 [ntfy]
url = http://your-ntfy:8080
topic = argus-disk [disk:ssd-system]
device = /dev/sda
type = sat
class = ssd [disk:das-slot1]
device = /dev/sdb
type = jmb39x,0
class = hdd [disk:das-slot2]
device = /dev/sdb
type = jmb39x,1
class = hdd [disk:das-slot3]
device = /dev/sdb
type = jmb39x,2
class = hdd [disk:das-slot4]
device = /dev/sdb
type = jmb39x,3
class = hdd [disk:das-slot5]
device = /dev/sdb
type = jmb39x,4
class = hdd
# /opt/argus/config/argus.conf [argus]
history_file = /var/lib/argus/argus-history.json
retention_days = 180 [ntfy]
url = http://your-ntfy:8080
topic = argus-disk [disk:ssd-system]
device = /dev/sda
type = sat
class = ssd [disk:das-slot1]
device = /dev/sdb
type = jmb39x,0
class = hdd [disk:das-slot2]
device = /dev/sdb
type = jmb39x,1
class = hdd [disk:das-slot3]
device = /dev/sdb
type = jmb39x,2
class = hdd [disk:das-slot4]
device = /dev/sdb
type = jmb39x,3
class = hdd [disk:das-slot5]
device = /dev/sdb
type = jmb39x,4
class = hdd
-weight: 500;">git clone https://github.com/pdegidio/argus-disk.-weight: 500;">git
cd argus-disk
bash -weight: 500;">install.sh
-weight: 500;">git clone https://github.com/pdegidio/argus-disk.-weight: 500;">git
cd argus-disk
bash -weight: 500;">install.sh
-weight: 500;">git clone https://github.com/pdegidio/argus-disk.-weight: 500;">git
cd argus-disk
bash -weight: 500;">install.sh - seek_error_rate is excluded — Seagate packs seek totals in the upper 32 bits, making the raw value meaningless for cross-vendor comparison. Backblaze doesn't use it either.
- udma_crc_error_count warns at 5, not 1 — a single historical CRC on a multi-year disk is physiological. Growth is what matters, and the forecast captures it.
- Temperature anomaly uses z-score over 10+ samples, not a fixed threshold — so a disk that normally runs at 28°C will alert at 36°C, while one that normally runs at 38°C won't.