mirror of
https://github.com/AikidoSec/safe-chain.git
synced 2026-05-26 20:20:49 +00:00
Add uninstallation process for ultimate
This commit is contained in:
parent
dcebe734df
commit
53b7d9295c
4 changed files with 167 additions and 12 deletions
|
|
@ -1,11 +1,14 @@
|
|||
import { tmpdir } from "os";
|
||||
import { unlinkSync } from "fs";
|
||||
import { unlinkSync, rmSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { execSync } from "child_process";
|
||||
import { ui } from "../environment/userInteraction.js";
|
||||
import { printVerboseAndSafeSpawn } from "../utils/safeSpawn.js";
|
||||
import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js";
|
||||
import chalk from "chalk";
|
||||
|
||||
const MACOS_PKG_IDENTIFIER = "com.aikidosecurity.safechainultimate";
|
||||
|
||||
export async function installOnMacOS() {
|
||||
if (!isRunningAsRoot()) {
|
||||
ui.writeError("Root privileges required.");
|
||||
|
|
@ -52,6 +55,87 @@ export async function installOnMacOS() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function uninstallOnMacOS() {
|
||||
if (!isRunningAsRoot()) {
|
||||
ui.writeError("Root privileges required.");
|
||||
ui.writeInformation("Please run this command with sudo:");
|
||||
ui.writeInformation(" sudo safe-chain --uninstall-ultimate");
|
||||
return;
|
||||
}
|
||||
|
||||
ui.emptyLine();
|
||||
|
||||
if (!isPackageInstalled()) {
|
||||
ui.writeInformation("SafeChain Ultimate is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
ui.writeInformation("⏹️ Stopping service...");
|
||||
await stopService();
|
||||
|
||||
ui.writeInformation("🗑️ Removing installed files...");
|
||||
removeKnownFiles();
|
||||
|
||||
ui.writeInformation("🧹 Forgetting package receipt...");
|
||||
forgetPackage();
|
||||
|
||||
ui.emptyLine();
|
||||
ui.writeInformation("✅ SafeChain Ultimate has been uninstalled.");
|
||||
ui.emptyLine();
|
||||
}
|
||||
|
||||
function isPackageInstalled() {
|
||||
try {
|
||||
const output = execSync(`pkgutil --pkg-info ${MACOS_PKG_IDENTIFIER}`, {
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
});
|
||||
return output.includes(MACOS_PKG_IDENTIFIER);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function stopService() {
|
||||
const result = await printVerboseAndSafeSpawn(
|
||||
"launchctl",
|
||||
["bootout", `system/${MACOS_PKG_IDENTIFIER}`],
|
||||
{ stdio: "pipe" },
|
||||
);
|
||||
|
||||
if (result.status !== 0) {
|
||||
ui.writeVerbose("Service not running (will continue with uninstall).");
|
||||
}
|
||||
}
|
||||
|
||||
const MACOS_KNOWN_PATHS = [
|
||||
"/Library/Application Support/AikidoSecurity/SafeChainUltimate",
|
||||
"/Library/Logs/AikidoSecurity/SafeChainUltimate",
|
||||
`/Library/LaunchDaemons/${MACOS_PKG_IDENTIFIER}.plist`,
|
||||
];
|
||||
|
||||
function removeKnownFiles() {
|
||||
for (const filePath of MACOS_KNOWN_PATHS) {
|
||||
try {
|
||||
rmSync(filePath, { recursive: true, force: true });
|
||||
ui.writeVerbose(`Removed: ${filePath}`);
|
||||
} catch {
|
||||
ui.writeVerbose(`Failed to remove: ${filePath}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function forgetPackage() {
|
||||
try {
|
||||
execSync(`pkgutil --forget ${MACOS_PKG_IDENTIFIER}`, {
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
});
|
||||
} catch {
|
||||
ui.writeVerbose("Failed to forget package receipt.");
|
||||
}
|
||||
}
|
||||
|
||||
function isRunningAsRoot() {
|
||||
const rootUserUid = 0;
|
||||
return process.getuid?.() === rootUserUid;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,34 @@ import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js";
|
|||
const WINDOWS_SERVICE_NAME = "SafeChainUltimate";
|
||||
const WINDOWS_APP_NAME = "SafeChain Ultimate";
|
||||
|
||||
export async function uninstallOnWindows() {
|
||||
if (!(await isRunningAsAdmin())) {
|
||||
ui.writeError("Administrator privileges required.");
|
||||
ui.writeInformation(
|
||||
"Please run this command in an elevated terminal (Run as Administrator).",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
ui.emptyLine();
|
||||
|
||||
const productCode = getInstalledProductCode();
|
||||
if (!productCode) {
|
||||
ui.writeInformation("SafeChain Ultimate is not installed.");
|
||||
return;
|
||||
}
|
||||
|
||||
ui.writeInformation("⏹️ Stopping running service...");
|
||||
await stopServiceIfRunning();
|
||||
|
||||
ui.writeInformation("🗑️ Uninstalling SafeChain Ultimate...");
|
||||
await uninstallByProductCode(productCode);
|
||||
|
||||
ui.emptyLine();
|
||||
ui.writeInformation("✅ SafeChain Ultimate has been uninstalled.");
|
||||
ui.emptyLine();
|
||||
}
|
||||
|
||||
export async function installOnWindows() {
|
||||
if (!(await isRunningAsAdmin())) {
|
||||
ui.writeError("Administrator privileges required.");
|
||||
|
|
@ -64,7 +92,11 @@ async function isRunningAsAdmin() {
|
|||
return result.status === 0 && result.stdout.trim() === "True";
|
||||
}
|
||||
|
||||
async function uninstallIfInstalled() {
|
||||
/**
|
||||
* Returns the MSI product code for SafeChain Ultimate, or null if not installed.
|
||||
* @returns {string | null}
|
||||
*/
|
||||
function getInstalledProductCode() {
|
||||
// Query Win32_Product via WMI to find the installed SafeChain Agent.
|
||||
// If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall.
|
||||
ui.writeVerbose(`Finding product code with PowerShell`);
|
||||
|
|
@ -76,15 +108,15 @@ async function uninstallIfInstalled() {
|
|||
{ encoding: "utf8" },
|
||||
).trim();
|
||||
} catch {
|
||||
ui.writeVerbose("No existing installation found (fresh install).");
|
||||
return;
|
||||
}
|
||||
if (!productCode) {
|
||||
ui.writeVerbose("No existing installation found (fresh install).");
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
return productCode || null;
|
||||
}
|
||||
|
||||
ui.writeInformation("🗑️ Removing previous installation...");
|
||||
/**
|
||||
* @param {string} productCode
|
||||
*/
|
||||
async function uninstallByProductCode(productCode) {
|
||||
ui.writeVerbose(`Found product code: ${productCode}`);
|
||||
|
||||
// Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec)
|
||||
|
|
@ -103,6 +135,17 @@ async function uninstallIfInstalled() {
|
|||
}
|
||||
}
|
||||
|
||||
async function uninstallIfInstalled() {
|
||||
const productCode = getInstalledProductCode();
|
||||
if (!productCode) {
|
||||
ui.writeVerbose("No existing installation found (fresh install).");
|
||||
return;
|
||||
}
|
||||
|
||||
ui.writeInformation("🗑️ Removing previous installation...");
|
||||
await uninstallByProductCode(productCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} msiPath
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,8 +1,24 @@
|
|||
import { platform } from "os";
|
||||
import { ui } from "../environment/userInteraction.js";
|
||||
import { initializeCliArguments } from "../config/cliArguments.js";
|
||||
import { installOnWindows } from "./installOnWindows.js";
|
||||
import { installOnMacOS } from "./installOnMacOS.js";
|
||||
import { installOnWindows, uninstallOnWindows } from "./installOnWindows.js";
|
||||
import { installOnMacOS, uninstallOnMacOS } from "./installOnMacOS.js";
|
||||
|
||||
export async function uninstallUltimate() {
|
||||
initializeCliArguments(process.argv);
|
||||
|
||||
const operatingSystem = platform();
|
||||
|
||||
if (operatingSystem === "win32") {
|
||||
await uninstallOnWindows();
|
||||
} else if (operatingSystem === "darwin") {
|
||||
await uninstallOnMacOS();
|
||||
} else {
|
||||
ui.writeInformation(
|
||||
`Uninstall is not yet supported on ${operatingSystem}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function installUltimate() {
|
||||
initializeCliArguments(process.argv);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue