package sysinfo import ( "encoding/json" "os" "regexp" "strconv" "strings" "code.thetadev.de/TSGRain/SEBRAUC/src/util" ) type SystemInfo struct { Hostname string `json:"hostname"` OsName string `json:"os_name"` OsVersion string `json:"os_version"` Uptime int `json:"uptime"` RaucCompatible string `json:"rauc_compatible"` RaucVariant string `json:"rauc_variant"` RaucRootfs map[string]Rootfs `json:"rauc_rootfs"` } type Rootfs struct { Device string `json:"device"` Type string `json:"type"` bootname string Mountpoint *string `json:"mountpoint"` Bootable bool `json:"bootable"` Booted bool `json:"booted"` Primary bool `json:"primary"` } type raucInfo struct { Compatible string `json:"compatible"` Variant string `json:"variant"` Booted string `json:"booted"` BootPrimary string `json:"boot_primary"` Slots []map[string]raucFS `json:"slots"` } type raucFS struct { Class string `json:"class"` Device string `json:"device"` Type string `json:"type"` Bootname string `json:"bootname"` State string `json:"state"` Mountpoint *string `json:"mountpoint"` BootStatus string `json:"boot_status"` } type osRelease struct { OsName string `json:"os_name"` OsVersion string `json:"os_version"` } var ( rexpOsName = regexp.MustCompile(`(?m)^NAME="(.+)"`) rexpOsVersion = regexp.MustCompile(`(?m)^VERSION="(.+)"`) rexpUptime = regexp.MustCompile(`^\d+`) ) func parseRaucInfo(raucInfoJson []byte) (raucInfo, error) { res := raucInfo{} err := json.Unmarshal(raucInfoJson, &res) return res, err } func parseOsRelease(osReleaseFile string) (osRelease, error) { osReleaseTxt, err := os.ReadFile(osReleaseFile) if err != nil { return osRelease{}, err } nameMatch := rexpOsName.FindSubmatch(osReleaseTxt) versionMatch := rexpOsVersion.FindSubmatch(osReleaseTxt) name := "" if nameMatch != nil { name = string(nameMatch[1]) } version := "" if versionMatch != nil { version = string(versionMatch[1]) } return osRelease{ OsName: name, OsVersion: version, }, nil } func mapRootfs(rinf raucInfo) map[string]Rootfs { res := make(map[string]Rootfs) for _, slot := range rinf.Slots { for name, fs := range slot { if fs.Class == "rootfs" { res[name] = Rootfs{ Device: fs.Device, Type: fs.Type, bootname: fs.Bootname, Mountpoint: fs.Mountpoint, Bootable: fs.BootStatus == "good", Booted: fs.State == "booted", Primary: rinf.BootPrimary == name, } } } } return res } func getFSNameFromBootname(rfslist map[string]Rootfs, bootname string) string { for name, rfs := range rfslist { if rfs.bootname == bootname { return name } } return "n/a" } func mapSysinfo(rinf raucInfo, osr osRelease, uptime int, hostname string) SystemInfo { rfslist := mapRootfs(rinf) return SystemInfo{ Hostname: hostname, OsName: osr.OsName, OsVersion: osr.OsVersion, Uptime: uptime, RaucCompatible: rinf.Compatible, RaucVariant: rinf.Variant, RaucRootfs: rfslist, } } func getUptime() (int, error) { uptimeRaw, err := os.ReadFile("/proc/uptime") if err != nil { return 0, err } uptimeChars := rexpUptime.Find(uptimeRaw) return strconv.Atoi(string(uptimeChars)) } func getHostname() string { hostname, err := os.ReadFile("/etc/hostname") if err != nil { return "" } return strings.TrimSpace(string(hostname)) } func GetSysinfo() (SystemInfo, error) { cmd := util.CommandFromString(util.RaucCmd + " status --output-format=json") rinfJson, err := cmd.Output() if err != nil { return SystemInfo{}, err } rinf, err := parseRaucInfo(rinfJson) if err != nil { return SystemInfo{}, err } osinf, err := parseOsRelease("/etc/os-release") if err != nil { return SystemInfo{}, err } uptime, err := getUptime() if err != nil { return SystemInfo{}, err } hostname := getHostname() return mapSysinfo(rinf, osinf, uptime, hostname), nil }