From 27980aec8279a564cfbc3def0004211ba05746db Mon Sep 17 00:00:00 2001 From: Sander Declerck Date: Mon, 19 Jan 2026 14:46:04 +0100 Subject: [PATCH] Restructure code into separate files --- .../src/installation/downloadAgent.js | 40 +++++ .../src/installation/installOnWindows.js | 146 +++++++++++++++ .../src/installation/installUltimate.js | 166 +----------------- packages/safe-chain/src/main.js | 10 +- 4 files changed, 193 insertions(+), 169 deletions(-) create mode 100644 packages/safe-chain/src/installation/downloadAgent.js create mode 100644 packages/safe-chain/src/installation/installOnWindows.js diff --git a/packages/safe-chain/src/installation/downloadAgent.js b/packages/safe-chain/src/installation/downloadAgent.js new file mode 100644 index 0000000..2e45b79 --- /dev/null +++ b/packages/safe-chain/src/installation/downloadAgent.js @@ -0,0 +1,40 @@ +import { createWriteStream } from "fs"; +import { pipeline } from "stream/promises"; +import fetch from "make-fetch-happen"; + +const ULTIMATE_VERSION = "v0.2.0"; + +/** + * @typedef {"windows"} Platform + * @typedef {"amd64" | "arm64"} Architecture + */ + +/** + * Builds the download URL for the SafeChain Agent installer. + * @param {Platform} platform + * @param {Architecture} architecture + */ +export function getAgentDownloadUrl(platform, architecture) { + const extension = platform === "windows" ? "msi" : "pkg"; + return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-${platform}-${architecture}.${extension}`; +} + +/** + * Downloads a file from a URL to a local path. + * @param {string} url + * @param {string} destPath + */ +export async function downloadFile(url, destPath) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Download failed: ${response.statusText}`); + } + await pipeline(response.body, createWriteStream(destPath)); +} + +/** + * Returns the current agent version. + */ +export function getAgentVersion() { + return ULTIMATE_VERSION; +} diff --git a/packages/safe-chain/src/installation/installOnWindows.js b/packages/safe-chain/src/installation/installOnWindows.js new file mode 100644 index 0000000..27db104 --- /dev/null +++ b/packages/safe-chain/src/installation/installOnWindows.js @@ -0,0 +1,146 @@ +import { arch, tmpdir } from "os"; +import { unlinkSync } from "fs"; +import { join } from "path"; +import { execSync } from "child_process"; +import { ui } from "../environment/userInteraction.js"; +import { + getAgentDownloadUrl, + getAgentVersion, + downloadFile, +} from "./downloadAgent.js"; + +export async function installOnWindows() { + if (!isRunningAsAdmin()) { + ui.writeError("Administrator privileges required."); + ui.writeInformation( + "Please run this command in an elevated terminal (Run as Administrator).", + ); + return; + } + + const architecture = getWindowsArchitecture(); + const downloadUrl = getAgentDownloadUrl("windows", architecture); + const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); + + ui.emptyLine(); + ui.writeInformation( + `đŸ“Ĩ Downloading SafeChain Agent ${getAgentVersion()} (${architecture})...`, + ); + ui.writeVerbose(`Download URL: ${downloadUrl}`); + ui.writeVerbose(`Destination: ${msiPath}`); + await downloadFile(downloadUrl, msiPath); + + ui.emptyLine(); + stopServiceIfRunning(); + uninstallIfInstalled(); + + // Wait a moment for uninstall to complete + await new Promise((resolve) => setTimeout(resolve, 2000)); + + ui.writeInformation("âš™ī¸ Installing SafeChain Agent..."); + runMsiInstaller(msiPath); + + ui.emptyLine(); + ui.writeInformation("🚀 Starting SafeChain Agent service..."); + startService(); + + ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); + cleanup(msiPath); + + ui.emptyLine(); + ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); + ui.emptyLine(); +} + +function isRunningAsAdmin() { + try { + execSync("net session", { stdio: "ignore" }); + return true; + } catch { + return false; + } +} + +function getWindowsArchitecture() { + const nodeArch = arch(); + if (nodeArch === "x64") return "amd64"; + if (nodeArch === "arm64") return "arm64"; + throw new Error(`Unsupported architecture: ${nodeArch}`); +} + +function uninstallIfInstalled() { + try { + // Use PowerShell to find the product code, then use msiexec to uninstall + // This is the modern alternative to wmic which is deprecated + const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; + ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`); + + const productCode = execSync(findProductCodeCmd, { + encoding: "utf8", + }).trim(); + + if (productCode) { + ui.writeInformation("đŸ—‘ī¸ Removing previous installation..."); + ui.writeVerbose(`Found product code: ${productCode}`); + ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); + execSync(`msiexec /x ${productCode} /qn /norestart`, { + stdio: "inherit", + }); + } else { + ui.writeVerbose("No existing installation found (fresh install)."); + } + } catch { + // Not installed or uninstall failed, which is fine for a fresh install + ui.writeVerbose("No existing installation found (fresh install)."); + } +} + +/** + * @param {string} msiPath + */ +function runMsiInstaller(msiPath) { + // /i = install + // /qn = quiet mode (no UI) + ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); + execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); +} + +function stopServiceIfRunning() { + try { + ui.writeInformation("âšī¸ Stopping running service..."); + ui.writeVerbose('Running: net stop "SafeChainAgent"'); + execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); + } catch { + // Service is not running or doesn't exist, which is fine + ui.writeVerbose("Service not running (will start after installation)."); + } +} + +function startService() { + try { + // Check if service is already running + ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); + const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); + + if (status.includes("RUNNING")) { + ui.writeVerbose("SafeChain Agent service is already running."); + return; + } + } catch { + // Service might not exist yet or query failed, proceed with start + } + + ui.writeVerbose('Running: net start "SafeChainAgent"'); + execSync('net start "SafeChainAgent"', { stdio: "inherit" }); +} + +/** + * @param {string} msiPath + */ +function cleanup(msiPath) { + try { + unlinkSync(msiPath); + } catch { + // Ignore cleanup errors + } +} diff --git a/packages/safe-chain/src/installation/installUltimate.js b/packages/safe-chain/src/installation/installUltimate.js index d1ccf28..7383d2c 100644 --- a/packages/safe-chain/src/installation/installUltimate.js +++ b/packages/safe-chain/src/installation/installUltimate.js @@ -1,13 +1,7 @@ -import { platform, arch, tmpdir } from "os"; -import { createWriteStream, unlinkSync } from "fs"; -import { join } from "path"; -import { execSync } from "child_process"; -import { pipeline } from "stream/promises"; -import fetch from "make-fetch-happen"; +import { platform } from "os"; import { ui } from "../environment/userInteraction.js"; import { initializeCliArguments } from "../config/cliArguments.js"; - -const ULTIMATE_VERSION = "v0.2.0"; +import { installOnWindows } from "./installOnWindows.js"; export function installUltimate() { initializeCliArguments(process.argv); @@ -22,159 +16,3 @@ export function installUltimate() { ); } } - -async function installOnWindows() { - if (!isRunningAsAdmin()) { - ui.writeError("Administrator privileges required."); - ui.writeInformation( - "Please run this command in an elevated terminal (Run as Administrator).", - ); - return; - } - - const architecture = getWindowsArchitecture(); - const downloadUrl = buildDownloadUrl(architecture); - const msiPath = join(tmpdir(), `SafeChainAgent-${Date.now()}.msi`); - - ui.emptyLine(); - ui.writeInformation( - `đŸ“Ĩ Downloading SafeChain Agent ${ULTIMATE_VERSION} (${architecture})...`, - ); - ui.writeVerbose(`Download URL: ${downloadUrl}`); - ui.writeVerbose(`Destination: ${msiPath}`); - await downloadFile(downloadUrl, msiPath); - - ui.emptyLine(); - stopServiceIfRunning(); - uninstallIfInstalled(); - - // Wait a moment for uninstall to complete - await new Promise((resolve) => setTimeout(resolve, 2000)); - - ui.writeInformation("âš™ī¸ Installing SafeChain Agent..."); - ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn /norestart`); - runMsiInstaller(msiPath); - - ui.emptyLine(); - ui.writeInformation("🚀 Starting SafeChain Agent service..."); - startService(); - - ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); - cleanup(msiPath); - - ui.emptyLine(); - ui.writeInformation("✅ SafeChain Agent installed and started successfully!"); - ui.emptyLine(); -} - -function isRunningAsAdmin() { - try { - execSync("net session", { stdio: "ignore" }); - return true; - } catch { - return false; - } -} - -function getWindowsArchitecture() { - const nodeArch = arch(); - if (nodeArch === "x64") return "amd64"; - if (nodeArch === "arm64") return "arm64"; - throw new Error(`Unsupported architecture: ${nodeArch}`); -} - -/** - * @param {string} architecture - */ -function buildDownloadUrl(architecture) { - return `https://github.com/AikidoSec/safechain-internals/releases/download/${ULTIMATE_VERSION}/SafeChainAgent-windows-${architecture}.msi`; -} - -/** - * @param {string} url - * @param {string} destPath - */ -async function downloadFile(url, destPath) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Download failed: ${response.statusText}`); - } - await pipeline(response.body, createWriteStream(destPath)); -} - -function uninstallIfInstalled() { - try { - // Use PowerShell to find the product code, then use msiexec to uninstall - // This is the modern alternative to wmic which is deprecated - const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; - ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`); - - const productCode = execSync(findProductCodeCmd, { - encoding: "utf8", - }).trim(); - - if (productCode) { - ui.writeInformation("đŸ—‘ī¸ Removing previous installation..."); - ui.writeVerbose(`Found product code: ${productCode}`); - ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); - execSync(`msiexec /x ${productCode} /qn /norestart`, { - stdio: "inherit", - }); - } else { - ui.writeVerbose("No existing installation found (fresh install)."); - } - } catch { - // Not installed or uninstall failed, which is fine for a fresh install - ui.writeVerbose("No existing installation found (fresh install)."); - } -} - -/** - * @param {string} msiPath - */ -function runMsiInstaller(msiPath) { - // /i = install - // /qn = quiet mode (no UI) - // /norestart = suppress restarts - execSync(`msiexec /i "${msiPath}" /qn /norestart`, { stdio: "inherit" }); -} - -function stopServiceIfRunning() { - try { - ui.writeInformation("âšī¸ Stopping running service..."); - ui.writeVerbose('Running: net stop "SafeChainAgent"'); - execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); - } catch { - // Service is not running or doesn't exist, which is fine - ui.writeVerbose("Service not running (will start after installation)."); - } -} - -function startService() { - try { - // Check if service is already running - ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); - const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); - - if (status.includes("RUNNING")) { - ui.writeVerbose("SafeChain Agent service is already running."); - return; - } - } catch { - // Service might not exist yet or query failed, proceed with start - } - - ui.writeVerbose('Running: net start "SafeChainAgent"'); - execSync('net start "SafeChainAgent"', { stdio: "inherit" }); -} - -/** - * @param {string} msiPath - */ -function cleanup(msiPath) { - try { - unlinkSync(msiPath); - } catch { - // Ignore cleanup errors - } -} diff --git a/packages/safe-chain/src/main.js b/packages/safe-chain/src/main.js index 9b7ba53..0b37eba 100644 --- a/packages/safe-chain/src/main.js +++ b/packages/safe-chain/src/main.js @@ -73,20 +73,20 @@ export async function main(args) { ui.writeVerbose( `${chalk.green("✔")} Safe-chain: Scanned ${ auditStats.totalPackages - } packages, no malware found.` + } packages, no malware found.`, ); } if (proxy.hasSuppressedVersions()) { ui.writeInformation( `${chalk.yellow( - "ℹ" - )} Safe-chain: Some package versions were suppressed due to minimum age requirement.` + "ℹ", + )} Safe-chain: Some package versions were suppressed due to minimum age requirement.`, ); ui.writeInformation( ` To disable this check, use: ${chalk.cyan( - "--safe-chain-skip-minimum-package-age" - )}` + "--safe-chain-skip-minimum-package-age", + )}`, ); }