252 lines
5.4 KiB
Go
252 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
)
|
|
|
|
type SmartDev struct {
|
|
DeviceFile string
|
|
Name string
|
|
Type string
|
|
}
|
|
|
|
// Lower case for zabbix-expected Json
|
|
type SmartDiscoEntry struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Model *string `json:"model"`
|
|
SN *string `json:"sn"`
|
|
Rotations *int32 `json:"rotations"`
|
|
Lbs *int32 `json:"lbs"`
|
|
IsExternal int32 `json:"isExternal"`
|
|
IsSSD int32 `json:"isSSD"`
|
|
}
|
|
|
|
type SmartInfoEntry struct {
|
|
Name string
|
|
DeviceFile string
|
|
Health string
|
|
PowerOnHours int32
|
|
PowerCycleCount int32
|
|
Temperature float32
|
|
}
|
|
|
|
type smartctlJson struct {
|
|
ModelName string `json:"model_name"`
|
|
SN string `json:"serial_number"`
|
|
Rotations int32 `json:"rotation_rate"`
|
|
Lbs int32 `json:"logical_block_size"`
|
|
}
|
|
|
|
func ExecSmartCtl(resAsSingleString bool, args ...string) ([]string, error) {
|
|
|
|
path, err := exec.LookPath("smartctl")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("coudn't find smartctl %v", err)
|
|
}
|
|
|
|
out, err := exec.Command(path, args...).Output()
|
|
if err != nil {
|
|
var exitErr *exec.ExitError
|
|
if errors.As(err, &exitErr) && exitErr.ExitCode() == 2 {
|
|
return nil, fmt.Errorf("smartctl failed, not running root?: %v", exitErr)
|
|
} else if errors.As(err, &exitErr) && exitErr.ExitCode() == 4 {
|
|
// ignore, no or partial smart support
|
|
} else {
|
|
return nil, fmt.Errorf("smartctl failed: %v", exitErr)
|
|
}
|
|
}
|
|
|
|
if !resAsSingleString {
|
|
var lns []string
|
|
s := bufio.NewScanner(strings.NewReader(string(out)))
|
|
for s.Scan() {
|
|
lns = append(lns, s.Text())
|
|
}
|
|
return lns, nil
|
|
} else {
|
|
var lns []string
|
|
lns = append(lns, string(out))
|
|
return lns, nil
|
|
}
|
|
}
|
|
|
|
func IsUSBDrive(dev string) (bool, error) {
|
|
path, err := exec.LookPath("lsblk")
|
|
if err != nil {
|
|
return false, fmt.Errorf("couldn't find lsblk: %v", err)
|
|
}
|
|
|
|
out, err := exec.Command(path, "-d", dev, "-o", "SUBSYSTEMS").Output()
|
|
if err != nil {
|
|
return false, fmt.Errorf("calling lsblk failed: %v", err)
|
|
}
|
|
|
|
return strings.Index(string(out), "usb") >= 0, nil
|
|
}
|
|
|
|
func GetSmartDevsFromScan() ([]SmartDev, error) {
|
|
var r []SmartDev
|
|
|
|
lns, err := ExecSmartCtl(false, "-n", "standby", "--scan")
|
|
if err != nil {
|
|
return r, fmt.Errorf("Scanning for devices: %w", err)
|
|
}
|
|
|
|
for _, ln := range lns {
|
|
if len(ln) > 0 {
|
|
//fmt.Println("x" + ln)
|
|
splt := strings.Split(ln, " ")
|
|
var sd SmartDev
|
|
sd.DeviceFile = splt[0]
|
|
sd.Name = strings.Split(splt[0], "/")[2]
|
|
sd.Type = strings.Split(strings.Split(ln, ",")[1], " ")[1]
|
|
r = append(r, sd)
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func GetSmartDisco() ([]SmartDiscoEntry, error) {
|
|
var r []SmartDiscoEntry
|
|
|
|
devs, err := GetSmartDevsFromScan()
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
|
|
for _, dev := range devs {
|
|
isUSB, err := IsUSBDrive(dev.DeviceFile)
|
|
if err != nil {
|
|
return r, fmt.Errorf("failed get disco: %v", err)
|
|
}
|
|
|
|
var entry SmartDiscoEntry
|
|
|
|
if !isUSB {
|
|
//fmt.Println(dev.DeviceFile)
|
|
lns, err := ExecSmartCtl(true, "-n", "standby", "-a", dev.DeviceFile, "--json")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "? skipped device %v: %v\n", dev.Name, err)
|
|
continue
|
|
}
|
|
//j := lns[0]
|
|
var sc smartctlJson
|
|
// Prevent collision with bash-call
|
|
err = json.Unmarshal([]byte(strings.ReplaceAll(lns[0], "'", "")), &sc)
|
|
if err != nil {
|
|
return r, fmt.Errorf("failed parsing json: %v", err)
|
|
}
|
|
entry.Model = &sc.ModelName
|
|
entry.SN = &sc.SN
|
|
entry.Rotations = &sc.Rotations
|
|
entry.Lbs = &sc.Lbs
|
|
|
|
} else {
|
|
entry.Model = nil
|
|
entry.SN = nil
|
|
entry.Rotations = nil
|
|
entry.Lbs = nil
|
|
}
|
|
entry.Name = dev.Name
|
|
entry.Type = dev.Type
|
|
entry.IsExternal = func() int32 {
|
|
if isUSB {
|
|
return 1
|
|
}
|
|
return 0
|
|
}()
|
|
entry.IsSSD = 0 // TODO
|
|
r = append(r, entry)
|
|
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
func SendSmartDiscoToZabbix(testMode bool) error {
|
|
disco, err := GetSmartDisco()
|
|
if err != nil {
|
|
return fmt.Errorf("SendSmartDiscoToZabbix failed: %w", err)
|
|
}
|
|
|
|
j, err := json.Marshal(disco)
|
|
if err != nil {
|
|
return fmt.Errorf("SendSmartDiscoToZabbix failed: %v", err)
|
|
}
|
|
|
|
if testMode {
|
|
fmt.Printf("Testmode: Would send %v\n", string(j))
|
|
|
|
} else {
|
|
path, err := exec.LookPath("bash")
|
|
if err != nil {
|
|
return fmt.Errorf("coudn't find bash %v", err)
|
|
}
|
|
|
|
_, err = exec.Command(path, "-c", ""+
|
|
"zabbix_sender -vv -k 8o_smartcheck.disco_devs -o '"+
|
|
string(j)+
|
|
"' -c /etc/zabbix/zabbix_agentd.conf"+
|
|
" | logger -t zbs_smart").Output()
|
|
if err != nil {
|
|
return fmt.Errorf("zabbix_sender exec failed: %v", err)
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
|
|
doDiscoPtr := flag.Bool("smart_disco", false, "Do discovery")
|
|
doCheckPtr := flag.Bool("smart_check", false, "Do check")
|
|
testModePtr := flag.Bool("test", false, "TestMode")
|
|
|
|
flag.Parse()
|
|
|
|
var err error
|
|
|
|
if *testModePtr {
|
|
fmt.Println("----- Testing GetSmartDevsFromScan")
|
|
s, _ := GetSmartDevsFromScan()
|
|
j, _ := json.Marshal(s)
|
|
fmt.Println(string(j))
|
|
fmt.Println()
|
|
|
|
fmt.Println("----- Testing IsUsbDrive")
|
|
for _, dev := range s {
|
|
isUSB, err := IsUSBDrive(dev.DeviceFile)
|
|
fmt.Printf("- %s %v %+v\n", dev.Name, isUSB, err)
|
|
}
|
|
|
|
fmt.Println("----- SendSmartDiscoToZabbix")
|
|
err = SendSmartDiscoToZabbix(*testModePtr)
|
|
}
|
|
|
|
if *doDiscoPtr {
|
|
fmt.Println("Doing SMART device discovery")
|
|
err = SendSmartDiscoToZabbix(*testModePtr)
|
|
}
|
|
|
|
if err == nil && *doCheckPtr {
|
|
fmt.Println("Doig SMART checks")
|
|
// TODO
|
|
}
|
|
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "! %+v\n", err)
|
|
}
|
|
|
|
fmt.Println("Done")
|
|
|
|
}
|