From 4529b473dc12d8a4d1b49c9ee1ba89897f86d616 Mon Sep 17 00:00:00 2001 From: Jeff Carr Date: Sat, 6 Jan 2024 05:24:11 -0600 Subject: [PATCH] add LinuxStatus() Signed-off-by: Jeff Carr --- hostname.go | 11 -- linuxstatus/args.go | 61 ++++++++ linuxstatus/common.go | 34 +++++ linuxstatus/draw.go | 34 +++++ linuxstatus/hostname.go | 86 ++++++++++++ linuxstatus/linuxloop.go | 44 ++++++ linuxstatus/net.go | 292 +++++++++++++++++++++++++++++++++++++++ linuxstatus/new.go | 17 +++ linuxstatus/structs.go | 57 ++++++++ linuxstatus/timer.go | 31 +++++ linuxstatus/unix.go | 97 +++++++++++++ linuxstatus/update.go | 31 +++++ 12 files changed, 784 insertions(+), 11 deletions(-) create mode 100644 linuxstatus/args.go create mode 100644 linuxstatus/common.go create mode 100644 linuxstatus/draw.go create mode 100644 linuxstatus/hostname.go create mode 100644 linuxstatus/linuxloop.go create mode 100644 linuxstatus/net.go create mode 100644 linuxstatus/new.go create mode 100644 linuxstatus/structs.go create mode 100644 linuxstatus/timer.go create mode 100644 linuxstatus/unix.go create mode 100644 linuxstatus/update.go diff --git a/hostname.go b/hostname.go index 062f033..9ca821f 100644 --- a/hostname.go +++ b/hostname.go @@ -25,18 +25,7 @@ func getHostname() { me.status.SetHostname(s) dn := run("domainname") - if (me.domainname.S != dn) { - log.Log(CHANGE, "domainname has changed from", me.domainname.S, "to", dn) - me.domainname.SetText(dn) - me.changed = true - } - hshort := run("hostname -s") - if (me.hostshort.S != hshort) { - log.Log(CHANGE, "hostname -s has changed from", me.hostshort.S, "to", hshort) - me.hostshort.SetText(hshort) - me.changed = true - } var test string test = hshort + "." + dn diff --git a/linuxstatus/args.go b/linuxstatus/args.go new file mode 100644 index 0000000..9457410 --- /dev/null +++ b/linuxstatus/args.go @@ -0,0 +1,61 @@ +package linuxstatus + +/* + this enables command line options from other packages like 'gui' and 'log' +*/ + +import ( + "go.wit.com/log" +) + +var NOW log.LogFlag +var NET log.LogFlag +var DNS log.LogFlag +var PROC log.LogFlag +var SPEW log.LogFlag +var CHANGE log.LogFlag +var STATUS log.LogFlag + +func init() { + NOW.B = false + NOW.Name = "NOW" + NOW.Subsystem = "cpdns" + NOW.Desc = "temp debugging stuff" + NOW.Register() + + NET.B = false + NET.Name = "NET" + NET.Subsystem = "cpdns" + NET.Desc = "Network logging" + NET.Register() + + DNS.B = false + DNS.Name = "DNS" + DNS.Subsystem = "cpdns" + DNS.Desc = "dnsStatus.update()" + DNS.Register() + + PROC.B = false + PROC.Name = "PROC" + PROC.Subsystem = "cpdns" + PROC.Desc = "/proc logging" + PROC.Register() + + SPEW.B = false + SPEW.Name = "SPEW" + SPEW.Subsystem = "cpdns" + SPEW.Desc = "spew logging" + SPEW.Register() + + CHANGE.B = false + CHANGE.Name = "CHANGE" + CHANGE.Subsystem = "cpdns" + CHANGE.Desc = "show droplet state changes" + CHANGE.Register() + + STATUS.B = false + STATUS.Name = "STATUS" + STATUS.Subsystem = "cpdns" + STATUS.Desc = "updateStatus()" + STATUS.Register() +} diff --git a/linuxstatus/common.go b/linuxstatus/common.go new file mode 100644 index 0000000..c4aea10 --- /dev/null +++ b/linuxstatus/common.go @@ -0,0 +1,34 @@ +// This creates a simple hello world window +package linuxstatus + +import ( + "go.wit.com/log" +) + +func (hs *LinuxStatus) Show() { + log.Log(CHANGE, "linuxStatus.Show() window") + hs.window.Show() + hs.hidden = false +} + +func (hs *LinuxStatus) Hide() { + log.Log(CHANGE, "linuxStatus.Hide() window") + hs.window.Hide() + hs.hidden = true +} + +func (hs *LinuxStatus) Toggle() { + log.Log(CHANGE, "linuxStatus.Toggle() window") + if hs.hidden { + hs.window.Show() + } else { + hs.window.Hide() + } +} + +func (hs *LinuxStatus) Ready() bool { + if me == nil {return false} + if hs == nil {return false} + if hs.window == nil {return false} + return me.ready +} diff --git a/linuxstatus/draw.go b/linuxstatus/draw.go new file mode 100644 index 0000000..b7fbbea --- /dev/null +++ b/linuxstatus/draw.go @@ -0,0 +1,34 @@ +// This creates a simple hello world window +package linuxstatus + +import ( + "go.wit.com/gui/gadgets" +) + +// creates the actual widgets. +// it's assumed you are always passing in a box +func draw(ls *LinuxStatus) { + if ! ls.Ready() {return} + ls.group = ls.window.Box().NewGroup("Real Stuff") + + ls.grid = ls.group.NewGrid("gridnuts", 2, 2) + + ls.grid.SetNext(1,1) + + ls.hostshort = gadgets.NewOneLiner(ls.grid, "hostname -s") + ls.domainname = gadgets.NewOneLiner(ls.grid, "domain name") + ls.NSrr = gadgets.NewOneLiner(ls.grid, "NS records =") + ls.uid = gadgets.NewOneLiner(ls.grid, "UID =") + ls.IPv4 = gadgets.NewOneLiner(ls.grid, "Current IPv4 =") + ls.IPv6 = gadgets.NewOneLiner(ls.grid, "Current IPv6 =") + ls.workingIPv6 = gadgets.NewOneLiner(ls.grid, "Real IPv6 =") + // ls.nics = gadgets.NewOneLiner(ls.grid, "network intefaces =") + + ls.grid.NewLabel("interfaces =") + ls.Interfaces = ls.grid.NewCombobox("Interfaces") + + ls.speedActual = gadgets.NewOneLiner(ls.grid, "refresh speed =") + + ls.grid.Margin() + ls.grid.Pad() +} diff --git a/linuxstatus/hostname.go b/linuxstatus/hostname.go new file mode 100644 index 0000000..76854f5 --- /dev/null +++ b/linuxstatus/hostname.go @@ -0,0 +1,86 @@ +// figures out if your hostname is valid +// then checks if your DNS is setup correctly +package linuxstatus + +import ( + "go.wit.com/log" + "go.wit.com/shell" + + // will try to get this hosts FQDN + "github.com/Showmax/go-fqdn" +) + +func (ls *LinuxStatus) GetDomainName() string { + if ! me.Ready() {return ""} + return me.domainname.Get() +} + +func (ls *LinuxStatus) setDomainName(dn string) { + if ! me.Ready() {return} + me.domainname.Set(dn) +} + +func getHostname() { + var err error + var s string = "gui.Label == nil" + s, err = fqdn.FqdnHostname() + if (err != nil) { + log.Error(err, "FQDN hostname error") + return + } + log.Warn("full hostname should be:", s) + + dn := run("domainname") + if (me.domainname.Get() != dn) { + log.Log(CHANGE, "domainname has changed from", me.GetDomainName(), "to", dn) + me.setDomainName(dn) + me.changed = true + } + + hshort := run("hostname -s") + if (me.hostshort.Get() != hshort) { + log.Log(CHANGE, "hostname -s has changed from", me.hostshort.Get(), "to", hshort) + me.hostshort.Set(hshort) + me.changed = true + } + + /* + var test string + test = hshort + "." + dn + if (me.status.GetHostname() != test) { + log.Log(CHANGE, "me.hostname", me.status.GetHostname(), "does not equal", test) + if (me.hostnameStatus.S != "BROKEN") { + log.Log(CHANGE, "me.hostname", me.status.GetHostname(), "does not equal", test) + me.changed = true + me.hostnameStatus.SetText("BROKEN") + } + } else { + if (me.hostnameStatus.S != "VALID") { + log.Log(CHANGE, "me.hostname", me.status.GetHostname(), "is valid") + me.hostnameStatus.SetText("VALID") + me.changed = true + } + } + */ +} + +// returns true if the hostname is good +// check that all the OS settings are correct here +// On Linux, /etc/hosts, /etc/hostname +// and domainname and hostname +func goodHostname() bool { + hostname := shell.Chomp(shell.Cat("/etc/hostname")) + log.Log(NOW, "hostname =", hostname) + + hs := run("hostname -s") + dn := run("domainname") + log.Log(NOW, "hostname short =", hs, "domainname =", dn) + + tmp := hs + "." + dn + if (hostname == tmp) { + log.Log(NOW, "hostname seems to be good", hostname) + return true + } + + return false +} diff --git a/linuxstatus/linuxloop.go b/linuxstatus/linuxloop.go new file mode 100644 index 0000000..44946c6 --- /dev/null +++ b/linuxstatus/linuxloop.go @@ -0,0 +1,44 @@ +// GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 +// Copyright (c) 2023 WIT.COM, Inc. +// This is a control panel for DNS + +package linuxstatus + +import ( + "strconv" + + "go.wit.com/log" +) + +func linuxLoop() { + me.changed = false + duration := timeFunction(getHostname) + log.Info("getHostname() execution Time: ", duration, "me.changed =", me.changed) + + duration = timeFunction(scanInterfaces) + log.Log(NET, "scanInterfaces() execution Time: ", duration) + for i, t := range me.ifmap { + log.Log(NET, strconv.Itoa(i) + " iface = " + t.iface.Name) + } + + var aaaa []string + aaaa = dhcpAAAA() + var all string + for _, s := range aaaa { + log.Log(NET, "my actual AAAA = ",s) + all += s + "\n" + } + // me.IPv6.SetText(all) + + /* + processName := getProcessNameByPort(53) + fmt.Println("Process with port 53:", processName) + + commPath := filepath.Join("/proc", proc.Name(), "comm") + comm, err := ioutil.ReadFile(commPath) + if err != nil { + return "", err // Error reading the process name + } + return strings.TrimSpace(string(comm)), nil + */ +} diff --git a/linuxstatus/net.go b/linuxstatus/net.go new file mode 100644 index 0000000..27e1a7b --- /dev/null +++ b/linuxstatus/net.go @@ -0,0 +1,292 @@ +// This creates a simple hello world window +package linuxstatus + +import ( + // "log" + "net" + "strings" + + "go.wit.com/log" +) + +// this doesn't work +/* +func watchNetworkInterfaces() { + // Get list of network interfaces + interfaces, _ := net.Interfaces() + + // Set up a notification channel + notification := make(chan net.Interface) + + log.Log(NET, "watchNet()") + // Start goroutine to watch for changes + go func() { + log.Log(NET, "watchNet() func") + for { + log.Log(NET, "forever loop start") + // Check for changes in each interface + for _, i := range interfaces { + log.Log(NET, "something on i =", i) + if status := i.Flags & net.FlagUp; status != 0 { + notification <- i + log.Log(NET, "something on i =", i) + } + } + log.Log(NET, "forever loop end") + } + }() +} +*/ + +func IsIPv6(address string) bool { + return strings.Count(address, ":") >= 2 +} + +func (t *IPtype) IsReal() bool { + if (t.ip.IsPrivate() || t.ip.IsLoopback() || t.ip.IsLinkLocalUnicast()) { + log.Log(NET, "\t\tIP is Real = false") + return false + } else { + log.Log(NET, "\t\tIP is Real = true") + return true + } +} + +func IsReal(ip *net.IP) bool { + if (ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast()) { + log.Log(NET, "\t\tIP is Real = false") + return false + } else { + log.Log(NET, "\t\tIP is Real = true") + return true + } +} + +func renameInterface(i *net.Interface) { + /* + /sbin/ip link set eth1 down + /sbin/ip link set eth1 name eth123 + /sbin/ip link set eth123 up + */ +} + +// Will figure out if an interface was just added +func checkInterface(i net.Interface) { + val, ok := me.ifmap[i.Index] + if ! ok { + log.Info(i.Name, "is a new network interface. The linux kernel index =", i.Index) + me.ifmap[i.Index] = new(IFtype) + me.ifmap[i.Index].gone = false + me.ifmap[i.Index].iface = &i + me.changed = true + if (me.Interfaces != nil) { + me.Interfaces.AddText(i.Name) + me.Interfaces.SetText(i.Name) + } + return + } + me.ifmap[i.Index].gone = false + log.Log(NET, "me.ifmap[i] does exist. Need to compare everything.", i.Index, i.Name, val.iface.Index, val.iface.Name) + if (val.iface.Name != i.Name) { + log.Info(val.iface.Name, "has changed to it's name to", i.Name) + me.ifmap[i.Index].iface = &i + me.changed = true + if (me.Interfaces != nil) { + me.Interfaces.AddText(i.Name) + me.Interfaces.SetText(i.Name) + } + return + } +} + +/* + These are the real IP address you have been + given from DHCP +*/ +func dhcpAAAA() []string { + var aaaa []string + + for s, t := range me.ipmap { + if (t.IsReal()) { + if (t.ipv6) { + aaaa = append(aaaa, s) + } + } + } + return aaaa +} + +func realA() []string { + var a []string + + for s, t := range me.ipmap { + if (t.IsReal()) { + if (t.ipv4) { + a = append(a, s) + } + } + } + return a +} + +func checkDNS() (map[string]*IPtype, map[string]*IPtype) { + var ipv4s map[string]*IPtype + var ipv6s map[string]*IPtype + + ipv4s = make(map[string]*IPtype) + ipv6s = make(map[string]*IPtype) + + for s, t := range me.ipmap { + i := t.iface + ipt := "IPv4" + if (t.ipv6) { + ipt = "IPv6" + } + if (t.IsReal()) { + log.Info("\tIP is Real ", ipt, i.Index, i.Name, s) + if (t.ipv6) { + ipv6s[s] = t + } else { + ipv4s[s] = t + } + } else { + log.Info("\tIP is not Real", ipt, i.Index, i.Name, s) + } + } + return ipv6s, ipv4s +} + +// Will figure out if an IP address is new +func checkIP(ip *net.IPNet, i net.Interface) bool { + log.Log(NET, "\t\taddr.(type) = *net.IPNet") + log.Log(NET, "\t\taddr.(type) =", ip) + var realip string + realip = ip.IP.String() + + val, ok := me.ipmap[realip] + if ok { + log.Log(NET, val.ipnet.IP.String(), "is already a defined IP address") + me.ipmap[realip].gone = false + return false + } + + me.ipmap[realip] = new(IPtype) + me.ipmap[realip].gone = false + me.ipmap[realip].ipv4 = true + me.ipmap[realip].ipnet = ip + me.ipmap[realip].ip = ip.IP + me.ipmap[realip].iface = &i + t := "IPv4" + if (IsIPv6(ip.String())) { + me.ipmap[realip].ipv6 = true + me.ipmap[realip].ipv4 = false + t = "IPv6" + if (me.IPv6 != nil) { + me.IPv6.Set(realip) + } + } else { + me.ipmap[realip].ipv6 = false + me.ipmap[realip].ipv4 = true + if (me.IPv4 != nil) { + me.IPv4.Set(realip) + } + } + if (IsReal(&ip.IP)) { + log.Info("\tIP is Real ", t, i.Index, i.Name, realip) + } else { + log.Info("\tIP is not Real", t, i.Index, i.Name, realip) + } + log.Log(NET, "\t\tIP is IsPrivate() =", ip.IP.IsPrivate()) + log.Log(NET, "\t\tIP is IsLoopback() =", ip.IP.IsLoopback()) + log.Log(NET, "\t\tIP is IsLinkLocalUnicast() =", ip.IP.IsLinkLocalUnicast()) + // log.Info("HERE HERE", "realip =", realip, "me.ip[realip]=", me.ipmap[realip]) + return true +} + +func scanInterfaces() { + log.Log(NET, "scanInterfaces() START") + ifaces, _ := net.Interfaces() + // me.ifnew = ifaces + log.Log(NET, SPEW, ifaces) + for _, i := range ifaces { + addrs, _ := i.Addrs() + // log.Info("range ifaces = ", i) + checkInterface(i) + log.Log(NET, "*net.Interface.Name = ", i.Name, i.Index) + log.Log(NET, SPEW, i) + log.Log(NET, SPEW, addrs) + for _, addr := range addrs { + log.Log(NET, "\taddr =", addr) + log.Log(NET, SPEW, addrs) + ips, _ := net.LookupIP(addr.String()) + log.Log(NET, "\tLookupIP(addr) =", ips) + switch v := addr.(type) { + case *net.IPNet: + if checkIP(v, i) { + log.Log(true, "scanInterfaces() IP is new () i =", v.IP.String()) + } + default: + log.Log(NET, "\t\taddr.(type) = NO IDEA WHAT TO DO HERE v =", v) + } + } + } + if deleteChanges() { + me.changed = true + log.Log(NET, "deleteChanges() detected network changes") + } + updateRealAAAA() + log.Log(NET, "scanInterfaces() END") +} + +// displays the IP address found on your network interfaces +func updateRealAAAA() { + var all4 string + var all6 string + for s, t := range me.ipmap { + if (t.ipv4) { + all4 += s + "\n" + log.Log(NET, "IPv4 =", s) + } else if (t.ipv6) { + all6 += s + "\n" + log.Log(NET, "IPv6 =", s) + } else { + log.Log(NET, "???? =", s) + } + } + all4 = sortLines(all4) + all6 = sortLines(all6) + if (me.IPv4.Get() != all4) { + log.Log(NET, "IPv4 addresses have changed", all4) + me.IPv4.Set(all4) + } + if (me.IPv6.Get() != all6) { + log.Log(NET, "IPv6 addresses have changed", all6) + me.IPv6.Set(all6) + } +} + +// delete network interfaces and ip addresses from the gui +func deleteChanges() bool { + var changed bool = false + for i, t := range me.ifmap { + if (t.gone) { + log.Log(CHANGE, "DELETE int =", i, "name =", t.name, t.iface) + delete(me.ifmap, i) + changed = true + } + t.gone = true + } + for s, t := range me.ipmap { + if (t.gone) { + log.Log(CHANGE, "DELETE name =", s, "IPv4 =", t.ipv4) + log.Log(CHANGE, "DELETE name =", s, "IPv6 =", t.ipv6) + log.Log(CHANGE, "DELETE name =", s, "iface =", t.iface) + log.Log(CHANGE, "DELETE name =", s, "ip =", t.ip) + delete(me.ipmap, s) + changed = true + } + t.gone = true + } + + return changed +} diff --git a/linuxstatus/new.go b/linuxstatus/new.go new file mode 100644 index 0000000..0ce504c --- /dev/null +++ b/linuxstatus/new.go @@ -0,0 +1,17 @@ +// This creates a simple hello world window +package linuxstatus + +import ( +) + +func New() *LinuxStatus { + me = &LinuxStatus { + hidden: true, + ready: false, + } + + me.init = true + return me + + // me.window = gadgets.NewBasicWindow(me.myGui, "Linux OS Details") +} diff --git a/linuxstatus/structs.go b/linuxstatus/structs.go new file mode 100644 index 0000000..185f0d7 --- /dev/null +++ b/linuxstatus/structs.go @@ -0,0 +1,57 @@ +/* + figures out if your hostname is valid + then checks if your DNS is setup correctly +*/ + +package linuxstatus + +import ( + "net" + "go.wit.com/gui/gui" + "go.wit.com/gui/gadgets" +) + +var me *LinuxStatus + +type LinuxStatus struct { + init bool + ready bool + hidden bool + changed bool + + ifmap map[int]*IFtype // the current interfaces + ipmap map[string]*IPtype // the current ip addresses + + window *gadgets.BasicWindow + group *gui.Node + grid *gui.Node + + hostshort *gadgets.OneLiner + domainname *gadgets.OneLiner + fqdn *gadgets.OneLiner + NSrr *gadgets.OneLiner + uid *gadgets.OneLiner + IPv4 *gadgets.OneLiner + IPv6 *gadgets.OneLiner + workingIPv6 *gadgets.OneLiner + Interfaces *gui.Node + speedActual *gadgets.OneLiner + +} + +type IPtype struct { + gone bool // used to track if the ip exists + ipv6 bool // the future + ipv4 bool // the past + LinkLocal bool + iface *net.Interface + ip net.IP + ipnet *net.IPNet +} + +type IFtype struct { + gone bool // used to track if the interface exists + name string // just a shortcut to the name. maybe this is dumb + // up bool // could be used to track ifup/ifdown + iface *net.Interface +} diff --git a/linuxstatus/timer.go b/linuxstatus/timer.go new file mode 100644 index 0000000..cd82f49 --- /dev/null +++ b/linuxstatus/timer.go @@ -0,0 +1,31 @@ +package linuxstatus + +import ( + "time" + "sort" + "strings" +) + +// timeFunction takes a function as an argument and returns the execution time. +func timeFunction(f func()) time.Duration { + startTime := time.Now() // Record the start time + f() // Execute the function + return time.Since(startTime) // Calculate the elapsed time +} + +// sortLines takes a string, splits it on newlines, sorts the lines, +// and rejoins them with newlines. +func sortLines(input string) string { + lines := strings.Split(input, "\n") + + // Trim leading and trailing whitespace from each line + for i, line := range lines { + lines[i] = strings.TrimSpace(line) + } + + sort.Strings(lines) + tmp := strings.Join(lines, "\n") + tmp = strings.TrimLeft(tmp, "\n") + tmp = strings.TrimRight(tmp, "\n") + return tmp +} diff --git a/linuxstatus/unix.go b/linuxstatus/unix.go new file mode 100644 index 0000000..99509c0 --- /dev/null +++ b/linuxstatus/unix.go @@ -0,0 +1,97 @@ +// Various Linux/Unix'y things + +// https://wiki.archlinux.org/title/Dynamic_DNS + +package linuxstatus + +import ( + "os" + "os/exec" + "net" + "bytes" + "fmt" + "strings" + + "go.wit.com/log" + "go.wit.com/shell" +) + +func CheckSuperuser() bool { + return os.Getuid() == 0 +} + +func Escalate() { + if os.Getuid() != 0 { + cmd := exec.Command("sudo", "./control-panel-dns") // TODO: get the actual path + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + if err != nil { + log.Error(err, "exit in Escalate()") + log.Exit(err) + } + } +} + +// You need permission to do a zone transfer. Otherwise: +// dig +noall +answer +multiline lab.wit.com any +// dig +all +multiline fire.lab.wit.com # gives the zonefile header (ttl vals) +func DumpPublicDNSZone(zone string) { + entries, err := net.LookupHost(zone) + if err != nil { + panic(err) + } + for _, entry := range entries { + log.Println(entry) + } +} + +func dumpIPs(host string) { + ips, err := net.LookupIP(host) + if err != nil { + log.Error(err, "dumpIPs() failed") + } + for _, ip := range ips { + log.Println(host, ip) + } +} + +/* + check if ddclient is installed, working, and/or configured + https://github.com/ddclient/ddclient +*/ +func ddclient() { +} + +/* + check if ddupdate is installed, working, and/or configured +*/ +func ddupdate() { +} + +func run(s string) string { + cmdArgs := strings.Fields(s) + // Define the command you want to run + // cmd := exec.Command(cmdArgs) + cmd := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...) + + // Create a buffer to capture the output + var out bytes.Buffer + + // Set the output of the command to the buffer + cmd.Stdout = &out + + // Run the command + err := cmd.Run() + if err != nil { + fmt.Println("Error running command:", err) + return "" + } + + tmp := shell.Chomp(out.String()) + // Output the results + log.Info("Command Output:", tmp) + + return tmp +} diff --git a/linuxstatus/update.go b/linuxstatus/update.go new file mode 100644 index 0000000..1ab8cee --- /dev/null +++ b/linuxstatus/update.go @@ -0,0 +1,31 @@ +package linuxstatus + +import ( + "errors" + "fmt" + "time" + + "go.wit.com/log" +) + +func (ls *LinuxStatus) Update() { + log.Info("linuxStatus() Update() START") + if ls == nil { + log.Error(errors.New("linuxStatus() Update() ls == nil")) + return + } + duration := timeFunction(func () { + linuxLoop() + }) + s := fmt.Sprint(duration) + ls.speedActual.Set(s) + + if (duration > 500 * time.Millisecond ) { + // ls.speed, "SLOW") + } else if (duration > 100 * time.Millisecond ) { + // ls.speed, "OK") + } else { + // ls.speed, "FAST") + } + log.Info("linuxStatus() Update() END") +}