107 lines
3.2 KiB
Go
107 lines
3.2 KiB
Go
package zygostates
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
type HardwareIdentity struct {
|
|
Key string `json:"key"`
|
|
BoardVendor string `json:"board_vendor,omitempty"`
|
|
BoardName string `json:"board_name,omitempty"`
|
|
BoardVersion string `json:"board_version,omitempty"`
|
|
BoardSerial string `json:"board_serial,omitempty"`
|
|
ProductName string `json:"product_name,omitempty"`
|
|
ProductSerial string `json:"product_serial,omitempty"`
|
|
ProductUUID string `json:"product_uuid,omitempty"`
|
|
}
|
|
|
|
var uuidPattern = regexp.MustCompile(`(?i)^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)
|
|
|
|
func readHardwareIdentity(root string) (HardwareIdentity, error) {
|
|
read := func(name string) string {
|
|
contents, err := os.ReadFile(filepath.Join(root, name))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(contents))
|
|
}
|
|
identity := HardwareIdentity{
|
|
BoardVendor: read("board_vendor"),
|
|
BoardName: read("board_name"),
|
|
BoardVersion: read("board_version"),
|
|
BoardSerial: read("board_serial"),
|
|
ProductName: read("product_name"),
|
|
ProductSerial: read("product_serial"),
|
|
ProductUUID: strings.ToLower(read("product_uuid")),
|
|
}
|
|
if validUUID(identity.ProductUUID) {
|
|
identity.Key = identity.ProductUUID
|
|
return identity, nil
|
|
}
|
|
if validIdentifier(identity.BoardSerial) {
|
|
identity.Key = sanitizeIdentifier(identity.BoardSerial)
|
|
return identity, nil
|
|
}
|
|
return HardwareIdentity{}, fmt.Errorf("no valid SMBIOS product UUID or board serial")
|
|
}
|
|
|
|
func validUUID(value string) bool {
|
|
if !uuidPattern.MatchString(value) {
|
|
return false
|
|
}
|
|
compact := strings.ReplaceAll(strings.ToLower(value), "-", "")
|
|
return compact != strings.Repeat("0", 32) && compact != strings.Repeat("f", 32)
|
|
}
|
|
|
|
func validIdentifier(value string) bool {
|
|
normalized := strings.ToLower(strings.TrimSpace(value))
|
|
if normalized == "" {
|
|
return false
|
|
}
|
|
for _, placeholder := range []string{"default string", "none", "not specified", "system serial number", "to be filled by o.e.m.", "unknown"} {
|
|
if normalized == placeholder {
|
|
return false
|
|
}
|
|
}
|
|
return sanitizeIdentifier(value) != ""
|
|
}
|
|
|
|
func sanitizeIdentifier(value string) string {
|
|
var result strings.Builder
|
|
for _, character := range strings.ToLower(strings.TrimSpace(value)) {
|
|
switch {
|
|
case character >= 'a' && character <= 'z', character >= '0' && character <= '9', character == '-', character == '_', character == '.':
|
|
result.WriteRune(character)
|
|
default:
|
|
result.WriteByte('-')
|
|
}
|
|
}
|
|
return strings.Trim(result.String(), "-.")
|
|
}
|
|
|
|
type SystemMetadata struct {
|
|
Identity HardwareIdentity `json:"identity"`
|
|
Hostname string `json:"hostname"`
|
|
Kernel string `json:"kernel"`
|
|
Architecture string `json:"architecture"`
|
|
LogicalCPUs int `json:"logical_cpus"`
|
|
GoVersion string `json:"go_version"`
|
|
}
|
|
|
|
func systemMetadata(identity HardwareIdentity) SystemMetadata {
|
|
hostname, _ := os.Hostname()
|
|
kernel, _ := os.ReadFile("/proc/sys/kernel/osrelease")
|
|
return SystemMetadata{
|
|
Identity: identity,
|
|
Hostname: hostname,
|
|
Kernel: strings.TrimSpace(string(kernel)),
|
|
Architecture: runtime.GOARCH,
|
|
LogicalCPUs: runtime.NumCPU(),
|
|
GoVersion: runtime.Version(),
|
|
}
|
|
}
|