2 Commits

Author SHA1 Message Date
Gilles Soulier 3c15943e2e debug(smart v0.1.16): log JSON brut complet en cas d'échec parse
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 13:51:30 +02:00
Gilles Soulier a9506a5505 fix(smart v0.1.15): contrôleur NVMe + règle udev disk group
Cause racine : smartctl -a -j /dev/nvme0n1 (namespace) retourne exit 4
et omet smart_status car les commandes admin échouent via le namespace.
Solution : utiliser /dev/nvme0 (contrôleur) accessible grâce à la règle
udev SUBSYSTEM==nvme GROUP=disk.

- smart.rs : scan /sys/class/nvme/ pour les contrôleurs (nvme0, nvme1)
  au lieu de /sys/block/ pour les namespaces (nvme0n1)
- deploy/99-nanometrics-smart.rules : udev rule KERNEL==nvme* GROUP=disk
- deploy/install.sh : déploie la règle udev + udevadm trigger

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-23 13:39:13 +02:00
5 changed files with 42 additions and 21 deletions
+1 -1
View File
@@ -248,7 +248,7 @@ dependencies = [
[[package]]
name = "nanometrics-agent"
version = "0.1.14"
version = "0.1.16"
dependencies = [
"libc",
"rumqttc",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nanometrics-agent"
version = "0.1.14"
version = "0.1.16"
edition = "2021"
[lib]
+25 -19
View File
@@ -91,24 +91,28 @@ pub fn collect() -> Option<Vec<crate::payload::SmartMetrics>> {
eprintln!("[smart] smartctl introuvable dans PATH");
return None;
}
let mut devs: Vec<String> = std::fs::read_dir("/sys/block")
.into_iter()
.flatten()
.flatten()
.map(|e| e.file_name().into_string().unwrap_or_default())
.filter_map(|n| {
if n.starts_with("sd") {
Some(format!("/dev/{}", n))
} else if n.starts_with("nvme") && n[4..].contains('n') {
// nvme0n1, nvme1n1 — namespace block device ; "nvme0" (contrôleur) ne passerait pas
Some(format!("/dev/{}", n))
} else {
None
}
})
.collect::<std::collections::HashSet<_>>()
.into_iter()
.collect();
let mut set = std::collections::HashSet::new();
// SATA/SAS : /sys/block/sd* → /dev/sda, /dev/sdb…
for e in std::fs::read_dir("/sys/block").into_iter().flatten().flatten() {
let n = e.file_name().into_string().unwrap_or_default();
if n.starts_with("sd") {
set.insert(format!("/dev/{}", n));
}
}
// NVMe : /sys/class/nvme/nvme* → /dev/nvme0, /dev/nvme1…
// On utilise le contrôleur (char device), pas le namespace (block device),
// car smartctl ne peut exécuter les commandes admin SMART que via le contrôleur.
// La règle udev 99-nanometrics-smart.rules lui donne l'accès groupe disk.
for e in std::fs::read_dir("/sys/class/nvme").into_iter().flatten().flatten() {
let n = e.file_name().into_string().unwrap_or_default();
if n.starts_with("nvme") {
set.insert(format!("/dev/{}", n));
}
}
let mut devs: Vec<String> = set.into_iter().collect();
devs.sort();
eprintln!("[smart] disques détectés: {:?}", devs);
@@ -134,7 +138,9 @@ pub fn collect() -> Option<Vec<crate::payload::SmartMetrics>> {
}
Err(e) => {
eprintln!("[smart] {} parse JSON échoué: {}", dev, e);
eprintln!("[smart] premiers 200 octets stdout: {:?}", &json.chars().take(200).collect::<String>());
eprintln!("[smart] --- JSON BRUT BEGIN ({} octets) ---", json.len());
eprintln!("{}", &*json);
eprintln!("[smart] --- JSON BRUT END ---");
}
}
}
+4
View File
@@ -0,0 +1,4 @@
# Nanometrics: accès groupe disk aux contrôleurs NVMe pour SMART
# Sans cette règle, /dev/nvme0 est crw------- root root (root only),
# ce qui empêche smartctl d'exécuter les commandes admin et omet smart_status du JSON.
KERNEL=="nvme[0-9]*", SUBSYSTEM=="nvme", GROUP="disk", MODE="0660"
+11
View File
@@ -45,6 +45,17 @@ else
fi
echo ""
# ── 2. Règle udev NVMe (accès SMART pour le groupe disk) ──────────────────────
UDEV_RULE="/etc/udev/rules.d/99-nanometrics-smart.rules"
cat > "$UDEV_RULE" << 'UDEVRULE'
# Nanometrics: accès groupe disk aux contrôleurs NVMe pour SMART
KERNEL=="nvme[0-9]*", SUBSYSTEM=="nvme", GROUP="disk", MODE="0660"
UDEVRULE
udevadm control --reload-rules
udevadm trigger --subsystem-match=nvme 2>/dev/null || true
ok "Règle udev NVMe installée ($UDEV_RULE)"
echo ""
# ── 3. Détection de l'architecture ────────────────────────────────────────────
ARCH="$(uname -m)"
case "$ARCH" in