Complete infrastructure platform CLI: - Container runtime (systemd-nspawn) - VoltVisor VMs (Neutron Stardust / QEMU) - Stellarium CAS (content-addressed storage) - ORAS Registry - GitOps integration - Landlock LSM security - Compose orchestration - Mesh networking Copyright (c) Armored Gates LLC. All rights reserved. Licensed under AGPSL v5.0
303 lines
7.5 KiB
Go
303 lines
7.5 KiB
Go
/*
|
|
Volt ODE Integration - Remote display for desktop VMs
|
|
|
|
ODE (Optimized Display Engine) provides:
|
|
- 2 Mbps bandwidth (vs 15+ Mbps for RDP)
|
|
- 54ms latency (vs 90+ ms for RDP)
|
|
- 5% server CPU (vs 25%+ for alternatives)
|
|
- H.264/H.265 encoding
|
|
- WebSocket/WebRTC transport
|
|
- Keyboard/mouse input forwarding
|
|
*/
|
|
package ode
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Profile defines ODE encoding settings
|
|
type Profile struct {
|
|
Name string `json:"name"`
|
|
Encoding string `json:"encoding"`
|
|
Resolution string `json:"resolution"`
|
|
Framerate int `json:"framerate"`
|
|
Bitrate int `json:"bitrate"` // kbps
|
|
LatencyTarget int `json:"latency_target"` // ms
|
|
ColorDepth int `json:"color_depth"` // bits
|
|
AudioEnabled bool `json:"audio_enabled"`
|
|
AudioBitrate int `json:"audio_bitrate"` // kbps
|
|
HardwareEncode bool `json:"hardware_encode"`
|
|
}
|
|
|
|
// Predefined profiles
|
|
var Profiles = map[string]Profile{
|
|
"terminal": {
|
|
Name: "terminal",
|
|
Encoding: "h264_baseline",
|
|
Resolution: "1920x1080",
|
|
Framerate: 30,
|
|
Bitrate: 500,
|
|
LatencyTarget: 30,
|
|
ColorDepth: 8,
|
|
AudioEnabled: false,
|
|
AudioBitrate: 0,
|
|
},
|
|
"office": {
|
|
Name: "office",
|
|
Encoding: "h264_main",
|
|
Resolution: "1920x1080",
|
|
Framerate: 60,
|
|
Bitrate: 2000,
|
|
LatencyTarget: 54,
|
|
ColorDepth: 10,
|
|
AudioEnabled: true,
|
|
AudioBitrate: 128,
|
|
},
|
|
"creative": {
|
|
Name: "creative",
|
|
Encoding: "h265_main10",
|
|
Resolution: "2560x1440",
|
|
Framerate: 60,
|
|
Bitrate: 8000,
|
|
LatencyTarget: 40,
|
|
ColorDepth: 10,
|
|
AudioEnabled: true,
|
|
AudioBitrate: 256,
|
|
HardwareEncode: true,
|
|
},
|
|
"video": {
|
|
Name: "video",
|
|
Encoding: "h265_main10",
|
|
Resolution: "3840x2160",
|
|
Framerate: 60,
|
|
Bitrate: 25000,
|
|
LatencyTarget: 20,
|
|
ColorDepth: 10,
|
|
AudioEnabled: true,
|
|
AudioBitrate: 320,
|
|
HardwareEncode: true,
|
|
},
|
|
"gaming": {
|
|
Name: "gaming",
|
|
Encoding: "h264_high",
|
|
Resolution: "2560x1440",
|
|
Framerate: 120,
|
|
Bitrate: 30000,
|
|
LatencyTarget: 16,
|
|
ColorDepth: 8,
|
|
AudioEnabled: true,
|
|
AudioBitrate: 320,
|
|
HardwareEncode: true,
|
|
},
|
|
}
|
|
|
|
// Config represents ODE server configuration
|
|
type Config struct {
|
|
Profile Profile `json:"profile"`
|
|
ListenAddress string `json:"listen_address"`
|
|
ListenPort int `json:"listen_port"`
|
|
TLSEnabled bool `json:"tls_enabled"`
|
|
TLSCert string `json:"tls_cert"`
|
|
TLSKey string `json:"tls_key"`
|
|
AuthEnabled bool `json:"auth_enabled"`
|
|
AuthToken string `json:"auth_token"`
|
|
}
|
|
|
|
// Server represents an ODE server instance
|
|
type Server struct {
|
|
vmName string
|
|
config Config
|
|
pid int
|
|
}
|
|
|
|
// NewServer creates a new ODE server configuration
|
|
func NewServer(vmName, profileName string) (*Server, error) {
|
|
profile, ok := Profiles[profileName]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unknown ODE profile: %s", profileName)
|
|
}
|
|
|
|
return &Server{
|
|
vmName: vmName,
|
|
config: Config{
|
|
Profile: profile,
|
|
ListenAddress: "0.0.0.0",
|
|
ListenPort: 8443,
|
|
TLSEnabled: true,
|
|
AuthEnabled: true,
|
|
AuthToken: generateToken(),
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// WriteConfig writes ODE configuration to VM filesystem
|
|
func (s *Server) WriteConfig(vmDir string) error {
|
|
configDir := filepath.Join(vmDir, "rootfs", "etc", "ode")
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
configPath := filepath.Join(configDir, "server.json")
|
|
data, err := json.MarshalIndent(s.config, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(configPath, data, 0644)
|
|
}
|
|
|
|
// WriteSystemdUnit writes ODE systemd service
|
|
func (s *Server) WriteSystemdUnit(vmDir string) error {
|
|
unitPath := filepath.Join(vmDir, "rootfs", "etc", "systemd", "system", "ode-server.service")
|
|
if err := os.MkdirAll(filepath.Dir(unitPath), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
unit := fmt.Sprintf(`[Unit]
|
|
Description=ODE Display Server
|
|
After=display-manager.service
|
|
Wants=display-manager.service
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=/usr/bin/ode-server --config /etc/ode/server.json
|
|
Restart=always
|
|
RestartSec=3
|
|
|
|
# ODE-specific settings
|
|
Environment="ODE_PROFILE=%s"
|
|
Environment="ODE_DISPLAY=:0"
|
|
Environment="ODE_HARDWARE_ENCODE=%v"
|
|
|
|
[Install]
|
|
WantedBy=graphical.target
|
|
`, s.config.Profile.Name, s.config.Profile.HardwareEncode)
|
|
|
|
return os.WriteFile(unitPath, []byte(unit), 0644)
|
|
}
|
|
|
|
// WriteCompositorConfig writes Wayland compositor config for ODE
|
|
func (s *Server) WriteCompositorConfig(vmDir string) error {
|
|
// Sway config for headless ODE operation
|
|
configDir := filepath.Join(vmDir, "rootfs", "etc", "sway")
|
|
if err := os.MkdirAll(configDir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
profile := s.config.Profile
|
|
width, height := parseResolution(profile.Resolution)
|
|
|
|
swayConfig := fmt.Sprintf(`# Sway config for ODE
|
|
# Generated by Volt
|
|
|
|
# Output configuration (virtual framebuffer)
|
|
output HEADLESS-1 {
|
|
resolution %dx%d@%d
|
|
scale 1
|
|
}
|
|
|
|
# Enable headless mode
|
|
output * {
|
|
bg #1a1a2e solid_color
|
|
}
|
|
|
|
# ODE capture settings
|
|
exec_always ode-capture --output HEADLESS-1 --framerate %d
|
|
|
|
# Default workspace
|
|
workspace 1 output HEADLESS-1
|
|
|
|
# Basic keybindings
|
|
bindsym Mod1+Return exec foot
|
|
bindsym Mod1+d exec wofi --show drun
|
|
bindsym Mod1+Shift+q kill
|
|
bindsym Mod1+Shift+e exit
|
|
|
|
# Include user config if exists
|
|
include /home/*/.config/sway/config
|
|
`, width, height, profile.Framerate, profile.Framerate)
|
|
|
|
return os.WriteFile(filepath.Join(configDir, "config"), []byte(swayConfig), 0644)
|
|
}
|
|
|
|
// GetConnectionURL returns the URL to connect to this ODE server
|
|
func (s *Server) GetConnectionURL(vmIP string) string {
|
|
proto := "wss"
|
|
if !s.config.TLSEnabled {
|
|
proto = "ws"
|
|
}
|
|
return fmt.Sprintf("%s://%s:%d/ode?token=%s", proto, vmIP, s.config.ListenPort, s.config.AuthToken)
|
|
}
|
|
|
|
// GetWebURL returns a browser-friendly URL
|
|
func (s *Server) GetWebURL(vmIP string) string {
|
|
proto := "https"
|
|
if !s.config.TLSEnabled {
|
|
proto = "http"
|
|
}
|
|
return fmt.Sprintf("%s://%s:%d/?token=%s", proto, vmIP, s.config.ListenPort, s.config.AuthToken)
|
|
}
|
|
|
|
// StreamStats returns current streaming statistics
|
|
type StreamStats struct {
|
|
Connected bool `json:"connected"`
|
|
Bitrate int `json:"bitrate_kbps"`
|
|
Framerate float64 `json:"framerate"`
|
|
Latency int `json:"latency_ms"`
|
|
PacketLoss float64 `json:"packet_loss_pct"`
|
|
EncoderLoad int `json:"encoder_load_pct"`
|
|
Resolution string `json:"resolution"`
|
|
ClientsCount int `json:"clients_count"`
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func parseResolution(res string) (int, int) {
|
|
var width, height int
|
|
fmt.Sscanf(res, "%dx%d", &width, &height)
|
|
if width == 0 {
|
|
width = 1920
|
|
}
|
|
if height == 0 {
|
|
height = 1080
|
|
}
|
|
return width, height
|
|
}
|
|
|
|
func generateToken() string {
|
|
b := make([]byte, 32)
|
|
if _, err := rand.Read(b); err != nil {
|
|
// Fallback: should never happen with crypto/rand
|
|
return "volt-ode-fallback-token"
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
// CalculateBandwidth returns estimated bandwidth for concurrent streams
|
|
func CalculateBandwidth(profile string, streams int) string {
|
|
p, ok := Profiles[profile]
|
|
if !ok {
|
|
return "unknown"
|
|
}
|
|
|
|
totalKbps := p.Bitrate * streams
|
|
if totalKbps < 1000 {
|
|
return fmt.Sprintf("%d Kbps", totalKbps)
|
|
}
|
|
return fmt.Sprintf("%.1f Mbps", float64(totalKbps)/1000)
|
|
}
|
|
|
|
// MaxStreamsPerGbps returns maximum concurrent streams for given profile
|
|
func MaxStreamsPerGbps(profile string) int {
|
|
p, ok := Profiles[profile]
|
|
if !ok {
|
|
return 0
|
|
}
|
|
return 1000000 / p.Bitrate // 1 Gbps = 1,000,000 kbps
|
|
}
|