Add installer changes

This commit is contained in:
Reinier Criel 2025-11-19 09:46:09 -08:00
parent e765ccf303
commit 2158478894
13 changed files with 674 additions and 21 deletions

View file

@ -2,11 +2,15 @@
import chalk from "chalk";
import { createRequire } from "module";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
import { ui } from "../src/environment/userInteraction.js";
import { setup } from "../src/shell-integration/setup.js";
import { teardown } from "../src/shell-integration/teardown.js";
import { setupCi } from "../src/shell-integration/setup-ci.js";
import { runCommand } from "../src/agent/runCommand.js";
import { generateCertCommand } from "../src/agent/generateCert.js";
if (process.argv.length < 3) {
ui.writeError("No command provided. Please provide a command to execute.");
@ -32,6 +36,10 @@ if (command === "setup") {
// Pass remaining arguments to runCommand
const runArgs = process.argv.slice(3);
runCommand(runArgs);
} else if (command === "generate-cert") {
// Pass remaining arguments to generateCertCommand
const certArgs = process.argv.slice(3);
generateCertCommand(certArgs);
} else if (command === "--version" || command === "-v" || command === "-v") {
ui.writeInformation(`Current safe-chain version: ${getVersion()}`);
} else {
@ -52,8 +60,8 @@ function writeHelp() {
`Available commands: ${chalk.cyan("setup")}, ${chalk.cyan(
"teardown"
)}, ${chalk.cyan("setup-ci")}, ${chalk.cyan("run")}, ${chalk.cyan(
"help"
)}, ${chalk.cyan("--version")}`
"generate-cert"
)}, ${chalk.cyan("help")}, ${chalk.cyan("--version")}`
);
ui.emptyLine();
ui.writeInformation(
@ -74,7 +82,12 @@ function writeHelp() {
ui.writeInformation(
`- ${chalk.cyan(
"safe-chain run"
)}: Run the proxy as a standalone service. Options: --all (default), --js, --py, --ecosystem=<type>`
)}: Run the proxy as a standalone service. Sets system-wide proxy environment variables. Options: --verbose`
);
ui.writeInformation(
`- ${chalk.cyan(
"safe-chain generate-cert"
)}: Generate CA certificate for MITM proxy. Options: --output <directory>`
);
ui.writeInformation(
`- ${chalk.cyan(
@ -85,7 +98,15 @@ function writeHelp() {
}
function getVersion() {
const require = createRequire(import.meta.url);
const packageJson = require("../package.json");
return packageJson.version;
try {
// Try to load package.json from the expected location
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJsonPath = join(__dirname, '../package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return packageJson.version;
} catch (error) {
// Fallback for bundled version
return '1.0.0';
}
}

View file

@ -64,5 +64,18 @@
"type": "git",
"url": "git+https://github.com/AikidoSec/safe-chain.git",
"directory": "packages/safe-chain"
},
"pkg": {
"assets": [
"src/**/*.js",
"node_modules/**/*"
],
"targets": [
"node20-macos-arm64",
"node20-macos-x64",
"node20-linux-x64",
"node20-linux-arm64",
"node20-win-x64"
]
}
}

View file

@ -0,0 +1,60 @@
/**
* Generate certificate command for Safe Chain
* Creates CA certificate and key for MITM proxy
*/
import { generateCACertificate } from "../registryProxy/certUtils.js";
import { writeFileSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { ui } from "../environment/userInteraction.js";
import chalk from "chalk";
/**
* Generate certificate command
* @param {string[]} args - Command line arguments
*/
export async function generateCertCommand(args) {
// Parse output directory from --output flag
let outputDir = join(homedir(), ".safe-chain");
for (let i = 0; i < args.length; i++) {
if (args[i] === "--output" && args[i + 1]) {
outputDir = args[i + 1];
break;
}
}
try {
ui.writeInformation(chalk.bold("Generating Safe Chain CA certificate..."));
ui.emptyLine();
// Create output directory
mkdirSync(outputDir, { recursive: true });
// Generate certificate
const { cert, key } = generateCACertificate();
// Write certificate and key files
const certPath = join(outputDir, "ca-cert.pem");
const keyPath = join(outputDir, "ca-key.pem");
writeFileSync(certPath, cert);
writeFileSync(keyPath, key);
ui.writeInformation(chalk.green("✓") + " Certificate generated successfully!");
ui.emptyLine();
ui.writeInformation(chalk.bold("Files created:"));
ui.writeInformation(` Certificate: ${chalk.cyan(certPath)}`);
ui.writeInformation(` Private Key: ${chalk.cyan(keyPath)}`);
ui.emptyLine();
ui.writeInformation(chalk.dim("To install this certificate in your system trust store:"));
ui.writeInformation(chalk.dim(" macOS: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain " + certPath));
ui.writeInformation(chalk.dim(" Linux: sudo cp " + certPath + " /usr/local/share/ca-certificates/ && sudo update-ca-certificates"));
ui.writeInformation(chalk.dim(" Windows: certutil -addstore -f ROOT " + certPath));
ui.emptyLine();
} catch (/** @type {any} */ error) {
ui.writeError(`Failed to generate certificate: ${error.message}`);
process.exit(1);
}
}

View file

@ -4,8 +4,6 @@ import chalk from "chalk";
import { initializeCliArguments } from "../config/cliArguments.js";
import { writeProxyState, clearProxyState } from "./proxyState.js";
import { getCaCertPath } from "../registryProxy/certUtils.js";
import { setup } from "../shell-integration/setup.js";
import { teardown } from "../shell-integration/teardown.js";
/**
* Run the Safe Chain proxy as a standalone service
@ -26,9 +24,9 @@ export async function runCommand(args) {
// Initialize logging from args
initializeCliArguments(processedArgs);
// Automatically set up shell integration
await setup();
ui.emptyLine();
// Note: We no longer call setup() here because the installer sets up
// system-wide proxy environment variables via LaunchAgent on macOS
// or systemd on Linux. The certificate is also installed at install time.
const service = new StandaloneProxyService({
autoVerify: false
@ -54,11 +52,14 @@ export async function runCommand(args) {
ui.writeInformation(` PID: ${chalk.cyan(process.pid)}`);
ui.emptyLine();
ui.writeInformation(chalk.bold("How to Use:"));
ui.writeInformation(chalk.dim(" Restart your terminal, then run package managers normally:"));
ui.writeInformation(chalk.cyan(" npm install <package>"));
ui.writeInformation(chalk.cyan(" yarn add <package>"));
ui.writeInformation(chalk.cyan(" pip3 install <package>"));
ui.writeInformation(chalk.bold("Environment Variables Set:"));
ui.writeInformation(` ${chalk.cyan("HTTPS_PROXY")}: http://localhost:${port}`);
ui.writeInformation(` ${chalk.cyan("GLOBAL_AGENT_HTTP_PROXY")}: http://localhost:${port}`);
ui.writeInformation(` ${chalk.cyan("NODE_EXTRA_CA_CERTS")}: ${getCaCertPath()}`);
ui.emptyLine();
ui.writeInformation(chalk.bold("Package managers will use the proxy automatically."));
ui.writeInformation(chalk.dim(" No shell wrappers or aliases needed."));
ui.emptyLine();
ui.writeInformation(
@ -104,9 +105,8 @@ export async function runCommand(args) {
try {
await service.stop();
// Remove shell integration
ui.emptyLine();
await teardown();
// Note: We no longer call teardown() here because the environment
// variables are managed by the system service (LaunchAgent/systemd)
process.exit(0);
} catch (/** @type {any} */ error) {

View file

@ -116,3 +116,15 @@ function generateCa() {
certificate: cert,
};
}
/**
* Generate CA certificate and return as PEM strings
* @returns {{cert: string, key: string}}
*/
export function generateCACertificate() {
const { privateKey, certificate } = generateCa();
return {
cert: forge.pki.certificateToPem(certificate),
key: forge.pki.privateKeyToPem(privateKey),
};
}