zygostates/internal/zygostates/identity.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(),
}
}