mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 12:10:49 +00:00
Restructure code into separate files
This commit is contained in:
parent
0e7cce750d
commit
fd559cfc63
4 changed files with 193 additions and 169 deletions
40
packages/safe-chain/src/installation/downloadAgent.js
Normal file
40
packages/safe-chain/src/installation/downloadAgent.js
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
146
packages/safe-chain/src/installation/installOnWindows.js
Normal file
146
packages/safe-chain/src/installation/installOnWindows.js
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
import { platform, arch, tmpdir } from "os";
|
import { platform } 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 { ui } from "../environment/userInteraction.js";
|
import { ui } from "../environment/userInteraction.js";
|
||||||
import { initializeCliArguments } from "../config/cliArguments.js";
|
import { initializeCliArguments } from "../config/cliArguments.js";
|
||||||
|
import { installOnWindows } from "./installOnWindows.js";
|
||||||
const ULTIMATE_VERSION = "v0.2.0";
|
|
||||||
|
|
||||||
export function installUltimate() {
|
export function installUltimate() {
|
||||||
initializeCliArguments(process.argv);
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -73,20 +73,20 @@ export async function main(args) {
|
||||||
ui.writeVerbose(
|
ui.writeVerbose(
|
||||||
`${chalk.green("✔")} Safe-chain: Scanned ${
|
`${chalk.green("✔")} Safe-chain: Scanned ${
|
||||||
auditStats.totalPackages
|
auditStats.totalPackages
|
||||||
} packages, no malware found.`
|
} packages, no malware found.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proxy.hasSuppressedVersions()) {
|
if (proxy.hasSuppressedVersions()) {
|
||||||
ui.writeInformation(
|
ui.writeInformation(
|
||||||
`${chalk.yellow(
|
`${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(
|
ui.writeInformation(
|
||||||
` To disable this check, use: ${chalk.cyan(
|
` To disable this check, use: ${chalk.cyan(
|
||||||
"--safe-chain-skip-minimum-package-age"
|
"--safe-chain-skip-minimum-package-age",
|
||||||
)}`
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue