Volt CLI: source-available under AGPSL v5.0
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
This commit is contained in:
302
pkg/ode/ode.go
Normal file
302
pkg/ode/ode.go
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user