feat(maitred): Update maitred - hookup to the API (#198)

## Description
We are attempting to hookup maitred to the API
Maitred duties will be:
- [ ] Hookup to the API
- [ ]  Wait for signal (from the API) to start Steam
- [ ] Stop signal to stop the gaming session, clean up Steam... and
maybe do the backup

## Summary by CodeRabbit

- **New Features**
- Introduced Docker-based deployment configurations for both the main
and relay applications.
- Added new API endpoints enabling real-time machine messaging and
enhanced IoT operations.
- Expanded database schema and actor types to support improved machine
tracking.

- **Improvements**
- Enhanced real-time communication and relay management with streamlined
room handling.
- Upgraded dependencies, logging, and error handling for greater
stability and performance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: DatCaptainHorse <DatCaptainHorse@users.noreply.github.com>
Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com>
This commit is contained in:
Wanjohi
2025-04-07 23:23:53 +03:00
committed by GitHub
parent 6990494b34
commit de80f3e6ab
84 changed files with 7357 additions and 1331 deletions

View File

@@ -0,0 +1,184 @@
package system
import (
"bytes"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
const (
pciClassVGA = 0x0300 // VGA compatible controller
pciClass3D = 0x0302 // 3D controller
pciClassDisplay = 0x0380 // Display controller
pciClassCoProcessor = 0x0b40 // Co-processor (e.g., NVIDIA Tesla)
)
type infoPair struct {
Name string
ID int
}
type PCIInfo struct {
Slot string
Class infoPair
Vendor infoPair
Device infoPair
SVendor infoPair
SDevice infoPair
Rev string
ProgIf string
Driver string
Modules []string
IOMMUGroup string
}
const (
VendorIntel = 0x8086
VendorNVIDIA = 0x10de
VendorAMD = 0x1002
)
func GetAllGPUInfo() ([]PCIInfo, error) {
var gpus []PCIInfo
cmd := exec.Command("lspci", "-mmvvvnnkD")
output, err := cmd.Output()
if err != nil {
return nil, err
}
sections := bytes.Split(output, []byte("\n\n"))
for _, section := range sections {
var info PCIInfo
lines := bytes.Split(section, []byte("\n"))
for _, line := range lines {
parts := bytes.SplitN(line, []byte(":"), 2)
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(string(parts[0]))
value := strings.TrimSpace(string(parts[1]))
switch key {
case "Slot":
info.Slot = value
case "Class":
info.Class, err = parseInfoPair(value)
case "Vendor":
info.Vendor, err = parseInfoPair(value)
case "Device":
info.Device, err = parseInfoPair(value)
case "SVendor":
info.SVendor, err = parseInfoPair(value)
case "SDevice":
info.SDevice, err = parseInfoPair(value)
case "Rev":
info.Rev = value
case "ProgIf":
info.ProgIf = value
case "Driver":
info.Driver = value
case "Module":
info.Modules = append(info.Modules, value)
case "IOMMUGroup":
info.IOMMUGroup = value
}
if err != nil {
return nil, err
}
}
// Check if this is a GPU device
if isGPUClass(info.Class.ID) {
gpus = append(gpus, info)
}
}
return gpus, nil
}
// gets infoPair from "SomeName [SomeID]"
// example: "DG2 [Arc A770] [56a0]" -> Name: "DG2 [Arc A770]", ID: "56a0"
func parseInfoPair(pair string) (infoPair, error) {
parts := strings.Split(pair, "[")
if len(parts) < 2 {
return infoPair{}, errors.New("invalid info pair")
}
id := strings.TrimSuffix(parts[len(parts)-1], "]")
name := strings.TrimSuffix(pair, "["+id)
name = strings.TrimSpace(name)
id = strings.TrimSpace(id)
// Remove ID including square brackets from name
name = strings.ReplaceAll(name, "["+id+"]", "")
name = strings.TrimSpace(name)
idHex, err := parseHexID(id)
if err != nil {
return infoPair{}, err
}
return infoPair{
Name: name,
ID: idHex,
}, nil
}
func parseHexID(id string) (int, error) {
if strings.HasPrefix(id, "0x") {
id = id[2:]
}
parsed, err := strconv.ParseInt(id, 16, 32)
if err != nil {
return 0, err
}
return int(parsed), nil
}
func isGPUClass(class int) bool {
return class == pciClassVGA || class == pciClass3D || class == pciClassDisplay || class == pciClassCoProcessor
}
// GetCardDevices returns the /dev/dri/cardX and /dev/dri/renderDXXX device
func (info PCIInfo) GetCardDevices() (cardPath, renderPath string, err error) {
busID := strings.ToLower(info.Slot)
if !strings.HasPrefix(busID, "0000:") || len(busID) != 12 || busID[4] != ':' || busID[7] != ':' || busID[10] != '.' {
return "", "", fmt.Errorf("invalid PCI Bus ID format: %s (expected 0000:XX:YY.Z)", busID)
}
byPathDir := "/dev/dri/by-path/"
entries, err := os.ReadDir(byPathDir)
if err != nil {
return "", "", fmt.Errorf("failed to read %s: %v", byPathDir, err)
}
for _, entry := range entries {
name := entry.Name()
if strings.HasPrefix(name, "pci-"+busID+"-card") {
cardPath, err = filepath.EvalSymlinks(filepath.Join(byPathDir, name))
if err != nil {
return "", "", fmt.Errorf("failed to resolve card symlink %s: %v", name, err)
}
}
if strings.HasPrefix(name, "pci-"+busID+"-render") {
renderPath, err = filepath.EvalSymlinks(filepath.Join(byPathDir, name))
if err != nil {
return "", "", fmt.Errorf("failed to resolve render symlink %s: %v", name, err)
}
}
}
if cardPath == "" && renderPath == "" {
return "", "", fmt.Errorf("no DRM devices found for PCI Bus ID: %s", busID)
}
return cardPath, renderPath, nil
}