Rootless Run

+ Debug Tools
* ReadME
* Optimized config, now it's cached
* ReWrote and optimized logic.go
* rewrote and optimized
+ Styling improvements
This commit is contained in:
2024-01-22 00:28:07 +02:00
parent d7e856aca2
commit 1d970aa6ba
9 changed files with 333 additions and 88 deletions

View File

@@ -20,8 +20,15 @@ type DHConfig struct {
DebugMode bool `json:"debugMode"`
}
func GetConfiguration() DHConfig {
config := DHConfig{
var config *DHConfig = nil
func GetConfiguration() *DHConfig {
if config != nil {
return config
}
config = &DHConfig{
DiskFetchFrequency: 5,
CleanupServiceFrequency: 3600,
MaxHistoryAge: 2592000,

View File

@@ -1,70 +1,78 @@
package hardware
import (
"bufio"
"bytes"
"fmt"
"os/exec"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"gorm.io/gorm"
"tea.chunkbyte.com/kato/drive-health/lib/config"
)
func GetSystemHardDrives(db *gorm.DB, olderThan *time.Time, newerThan *time.Time) ([]*HardDrive, error) {
// Execute the lsblk command to get detailed block device information
cmd := exec.Command("lsblk", "-d", "-o", "NAME,TRAN,SIZE,MODEL,SERIAL,TYPE")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
fmt.Println("Failed to execute command:", err)
return nil, err
}
var systemHardDrives []*HardDrive
// Scan the output line by line
scanner := bufio.NewScanner(&out)
for scanner.Scan() {
line := scanner.Text()
// Skip the header line
if strings.Contains(line, "NAME") {
continue
}
// Split the line into columns
cols := strings.Fields(line)
if len(cols) < 6 {
continue
}
// Filter out nvme drives (M.2)
if cols[1] != "usb" {
hd := &HardDrive{
Name: cols[0],
Transport: cols[1],
Size: cols[2],
Model: cols[3],
Serial: cols[4],
Type: cols[5],
}
systemHardDrives = append(systemHardDrives, hd)
}
// List all block devices
devices, err := os.ReadDir("/sys/block/")
if err != nil {
return nil, fmt.Errorf("failed to list block devices: %w", err)
}
// Handle error
if err := scanner.Err(); err != nil {
return nil, err
for _, device := range devices {
deviceName := device.Name()
// Skip non-physical devices (like loop and ram devices)
// TODO: Read more about this, there might be some other devices we should or should not skip
if strings.HasPrefix(deviceName, "loop") || strings.HasPrefix(deviceName, "ram") {
continue
}
// Read device details
model, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/device/model", deviceName))
serial, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/device/serial", deviceName))
sizeBytes, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/size", deviceName))
size := convertSizeToString(sizeBytes)
transport := getTransportType(deviceName)
// TODO: Maybe find a better way?
if size == "0 Bytes" {
// This looks like an invalid device, skip it.
if config.GetConfiguration().DebugMode {
fmt.Printf("[🟨] Igoring device:[/dev/%s], reported size of 0\n", deviceName)
}
continue
}
hwid, err := getHardwareID(deviceName)
if err != nil {
if config.GetConfiguration().DebugMode {
fmt.Printf("[🟨] No unique identifier found for device:[/dev/%s] unique identifier\n", deviceName)
}
continue
}
hd := &HardDrive{
Name: deviceName,
Transport: transport,
Model: strings.TrimSpace(string(model)),
Serial: strings.TrimSpace(string(serial)),
Size: size,
Type: getDriveType(deviceName),
HWID: hwid,
}
systemHardDrives = append(systemHardDrives, hd)
}
var updatedHardDrives []*HardDrive
for _, sysHDD := range systemHardDrives {
var existingHD HardDrive
q := db.Where("serial = ? AND model = ? AND type = ?", sysHDD.Serial, sysHDD.Model, sysHDD.Type)
q := db.Where("hw_id = ?", sysHDD.HWID)
if newerThan != nil && olderThan != nil {
q = q.Preload("Temperatures", "time_stamp < ? AND time_stamp > ?", newerThan, olderThan)
@@ -90,3 +98,101 @@ func GetSystemHardDrives(db *gorm.DB, olderThan *time.Time, newerThan *time.Time
return updatedHardDrives, nil
}
func getTransportType(deviceName string) string {
transportLink, err := filepath.EvalSymlinks(fmt.Sprintf("/sys/block/%s/device", deviceName))
if err != nil {
return "Unknown"
}
if strings.Contains(transportLink, "/usb/") {
return "USB"
} else if strings.Contains(transportLink, "/ata") {
return "SATA"
} else if strings.Contains(transportLink, "/nvme/") {
return "NVMe"
}
return "Other"
}
func convertSizeToString(sizeBytes []byte) string {
// Convert the size from a byte slice to a string, then to an integer
sizeStr := strings.TrimSpace(string(sizeBytes))
sizeSectors, err := strconv.ParseInt(sizeStr, 10, 64)
if err != nil {
return "Unknown"
}
// Convert from 512-byte sectors to bytes
sizeInBytes := sizeSectors * 512
// Define size units
const (
_ = iota // ignore first value by assigning to blank identifier
KB float64 = 1 << (10 * iota)
MB
GB
TB
)
var size float64 = float64(sizeInBytes)
var unit string
// Determine the unit to use
switch {
case size >= TB:
size /= TB
unit = "TB"
case size >= GB:
size /= GB
unit = "GB"
case size >= MB:
size /= MB
unit = "MB"
case size >= KB:
size /= KB
unit = "KB"
default:
unit = "Bytes"
}
// Return the formatted size
return fmt.Sprintf("%.2f %s", size, unit)
}
// Look throug /sys/block/device/ and try and find the unique identifier of the device.
func getHardwareID(deviceName string) (string, error) {
// Define potential ID file paths
idFilePaths := []string{
"/sys/block/" + deviceName + "/device/wwid",
"/sys/block/" + deviceName + "/device/wwn",
"/sys/block/" + deviceName + "/device/serial",
}
// Try to read each file and return the first successful read
for _, path := range idFilePaths {
if idBytes, err := os.ReadFile(path); err == nil {
return strings.TrimSpace(string(idBytes)), nil
}
}
// Return an empty string if no ID is found
return "", fmt.Errorf("could not find unique identifier for %s", deviceName)
}
// Figure out what kind of device this is by reading if it's rotational or not
func getDriveType(deviceName string) string {
// Check if the drive is rotational (HDD)
if isRotational, _ := os.ReadFile(fmt.Sprintf("/sys/block/%s/queue/rotational", deviceName)); string(isRotational) == "1\n" {
return "HDD"
}
// Check if the drive is NVMe
if strings.HasPrefix(deviceName, "nvme") {
return "NVMe"
}
// Default to SSD for non-rotational and non-NVMe drives
return "SSD"
}

View File

@@ -2,9 +2,12 @@ package hardware
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/anatol/smart.go"
"gorm.io/gorm"
)
@@ -19,6 +22,7 @@ type HardDrive struct {
Model string
Serial string
Type string
HWID string
Temperatures []HardDriveTemperature `gorm:"foreignKey:HardDriveID"`
}
@@ -39,26 +43,49 @@ type Snapshots struct {
List []*HardwareSnapshot
}
// Fetch the temperature of the device, optinally update the reference object
func (h *HardDrive) GetTemperature() int {
// Fetch the device by name
disk, err := smart.Open("/dev/" + h.Name)
if err != nil {
fmt.Printf("Failed to open device %s: %s\n", h.Name, err)
return -1
}
defer disk.Close()
// Fetch SMART data
smartInfo, err := disk.ReadGenericAttributes()
if err != nil {
fmt.Printf("Failed to get SMART data for %s: %s\n", h.Name, err)
return -1
// Try HDD/SSD path
temp, found := h.getTemperatureFromPath("/sys/block/" + h.Name + "/device/hwmon/")
if found {
return temp
}
// Parse the temperature
temperature := int(smartInfo.Temperature)
// Try NVMe path
temp, found = h.getTemperatureFromPath("/sys/block/" + h.Name + "/device/")
if found {
return temp
}
// Return the found value
return temperature
fmt.Printf("[🛑] Failed to get temperature for %s\n", h.Name)
return -1
}
func (h *HardDrive) getTemperatureFromPath(basePath string) (int, bool) {
hwmonDirs, err := os.ReadDir(basePath)
if err != nil {
return 0, false
}
for _, dir := range hwmonDirs {
if strings.HasPrefix(dir.Name(), "hwmon") {
tempPath := filepath.Join(basePath, dir.Name(), "temp1_input")
if _, err := os.Stat(tempPath); err == nil {
tempBytes, err := os.ReadFile(tempPath)
if err != nil {
continue
}
tempStr := strings.TrimSpace(string(tempBytes))
temperature, err := strconv.Atoi(tempStr)
if err != nil {
continue
}
// Convert millidegree Celsius to degree Celsius
return temperature / 1000, true
}
}
}
return 0, false
}