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

@ -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),
};
}