diff --git a/installer/agent/package.json b/installer/agent/package.json index 3d24149..0fb5050 100644 --- a/installer/agent/package.json +++ b/installer/agent/package.json @@ -13,6 +13,7 @@ "make-fetch-happen": "14.0.3", "node-forge": "1.3.1", "npm-registry-fetch": "18.0.2", + "ora": "8.2.0", "semver": "7.7.2" }, "author": "Aikido Security", diff --git a/installer/package.json b/installer/package.json index f4abaa0..d54c9ba 100644 --- a/installer/package.json +++ b/installer/package.json @@ -14,7 +14,8 @@ "test-uninstall": "sudo bash build/uninstall.sh" }, "dependencies": { - "node-forge": "1.3.1" + "node-forge": "1.3.1", + "tar": "^7.4.3" }, "devDependencies": { "@types/node": "^18.19.130", diff --git a/installer/scripts/darwin-build-installer.js b/installer/scripts/darwin-build-installer.js index a9a7bda..2c729c0 100644 --- a/installer/scripts/darwin-build-installer.js +++ b/installer/scripts/darwin-build-installer.js @@ -14,8 +14,13 @@ import { execSync } from 'child_process'; import fs from 'fs'; +import https from 'https'; import path from 'path'; import { fileURLToPath } from 'url'; +import { pipeline } from 'stream/promises'; +import { createWriteStream, createReadStream } from 'fs'; +import { createGunzip } from 'zlib'; +import * as tar from 'tar'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -81,19 +86,94 @@ console.log(`\nInstaller: ${path.join(buildDir, 'AikidoSafeChain.pkg')}`); console.log(`Uninstaller: ${path.join(buildDir, 'uninstall.sh')}\n`); /** - * Bundle Node.js runtime from current installation + * Download a file from URL + */ +async function downloadFile(url, destPath) { + return new Promise((resolve, reject) => { + https.get(url, (response) => { + if (response.statusCode === 302 || response.statusCode === 301) { + // Follow redirect + downloadFile(response.headers.location, destPath).then(resolve).catch(reject); + return; + } + + if (response.statusCode !== 200) { + reject(new Error(`Failed to download: ${response.statusCode}`)); + return; + } + + const fileStream = createWriteStream(destPath); + response.pipe(fileStream); + + fileStream.on('finish', () => { + fileStream.close(); + resolve(); + }); + + fileStream.on('error', reject); + }).on('error', reject); + }); +} + +/** + * Bundle Node.js runtime - downloads official binary for target architecture */ async function bundleNodeRuntime() { const binDir = path.join(installRoot, 'bin'); fs.mkdirSync(binDir, { recursive: true }); - // Copy current Node.js binary - const nodePath = process.execPath; - const targetNodePath = path.join(binDir, 'node'); - fs.copyFileSync(nodePath, targetNodePath); - fs.chmodSync(targetNodePath, 0o755); + // Detect target architecture (prefer arm64 for Apple Silicon) + const arch = process.arch === 'arm64' ? 'arm64' : 'x64'; + const nodeVersion = process.version; // e.g., v20.10.0 - console.log(` Copied Node.js ${process.version} to ${targetNodePath}`); + console.log(` Downloading Node.js ${nodeVersion} for macOS-${arch}...`); + + // Download official Node.js binary from nodejs.org + const downloadUrl = `https://nodejs.org/dist/${nodeVersion}/node-${nodeVersion}-darwin-${arch}.tar.gz`; + const tarballPath = path.join(buildDir, 'node.tar.gz'); + + try { + await downloadFile(downloadUrl, tarballPath); + console.log(` Downloaded Node.js tarball`); + + // Extract node binary from tarball + const extractDir = path.join(buildDir, 'node-extract'); + fs.mkdirSync(extractDir, { recursive: true }); + + await tar.extract({ + file: tarballPath, + cwd: extractDir, + strip: 2, + filter: (path) => path.endsWith('/bin/node') + }); + + // Move extracted node binary to target location + const extractedNode = path.join(extractDir, 'node'); + const targetNodePath = path.join(binDir, 'node'); + + if (fs.existsSync(extractedNode)) { + fs.copyFileSync(extractedNode, targetNodePath); + fs.chmodSync(targetNodePath, 0o755); + console.log(` Installed Node.js ${nodeVersion} (${arch}) to ${targetNodePath}`); + } else { + throw new Error('Failed to extract node binary from tarball'); + } + + // Cleanup + fs.rmSync(tarballPath); + fs.rmSync(extractDir, { recursive: true }); + + } catch (error) { + console.warn(` Failed to download Node.js: ${error.message}`); + console.warn(` Falling back to current Node.js binary (may not match target architecture)`); + + // Fallback to copying current Node.js binary + const nodePath = process.execPath; + const targetNodePath = path.join(binDir, 'node'); + fs.copyFileSync(nodePath, targetNodePath); + fs.chmodSync(targetNodePath, 0o755); + console.log(` Copied Node.js ${process.version} (${process.arch}) from ${nodePath}`); + } } /** diff --git a/installer/scripts/templates/postinstall.sh b/installer/scripts/templates/postinstall.sh index f702a45..958e641 100644 --- a/installer/scripts/templates/postinstall.sh +++ b/installer/scripts/templates/postinstall.sh @@ -17,18 +17,46 @@ security add-trusted-cert -d -r trustRoot \ -k /Library/Keychains/System.keychain \ "$INSTALL_DIR/certs/ca-cert.pem" || true -# Configure system proxy -echo "Configuring system proxy settings..." -"$INSTALL_DIR/bin/node" "$INSTALL_DIR/agent/configure-proxy.js" --install || { - echo "Warning: Failed to configure system proxy. You may need to configure manually." -} - -# Load and start the LaunchDaemon +# Load and start the LaunchDaemon FIRST (before configuring proxy) echo "Starting Aikido Safe Chain Agent..." launchctl load -w "$LAUNCHD_PLIST" || { - echo "Warning: Failed to start agent. You may need to restart your computer." + echo "ERROR: Failed to start agent." + exit 1 } +# Wait for agent to be ready (check if port is listening) +echo "Waiting for agent to start..." +for i in {1..10}; do + if lsof -Pi :8765 -sTCP:LISTEN -t >/dev/null 2>&1; then + echo "Agent is running on port 8765" + break + fi + if [ $i -eq 10 ]; then + echo "ERROR: Agent failed to start within 10 seconds" + launchctl unload "$LAUNCHD_PLIST" 2>/dev/null || true + exit 1 + fi + sleep 1 +done + +# Now configure system proxy (agent is confirmed running) +echo "Configuring system proxy settings..." +"$INSTALL_DIR/bin/node" "$INSTALL_DIR/agent/configure-proxy.js" --install || { + echo "ERROR: Failed to configure system proxy." + launchctl unload "$LAUNCHD_PLIST" 2>/dev/null || true + exit 1 +} + +# Configure pip to trust the CA certificate +echo "Configuring pip to trust Aikido CA certificate..." +PIP_CONFIG_DIR="/Library/Application Support/pip" +mkdir -p "$PIP_CONFIG_DIR" +cat > "$PIP_CONFIG_DIR/pip.conf" << EOF +[global] +cert = $INSTALL_DIR/certs/ca-cert.pem +EOF +chmod 644 "$PIP_CONFIG_DIR/pip.conf" + echo "Aikido Safe Chain Agent installed successfully!" echo "" echo "The agent is now running in the background and will protect" diff --git a/installer/scripts/templates/uninstall.sh b/installer/scripts/templates/uninstall.sh index 020b18d..273255b 100644 --- a/installer/scripts/templates/uninstall.sh +++ b/installer/scripts/templates/uninstall.sh @@ -28,6 +28,10 @@ if [ -f "$INSTALL_DIR/agent/configure-proxy.js" ]; then } fi +# Remove pip configuration +echo "Removing pip configuration..." +rm -f "/Library/Application Support/pip/pip.conf" + # Remove files echo "Removing files..." rm -rf "$INSTALL_DIR"