/* * Single-file binary entry point (compiled with `bun build --compile`). * * Not part of upstream. Copied to the server clone root by scripts/patch.sh. * Two archives are embedded into the binary by scripts/build.sh and extracted to * a temp dir on first run: * - ui.tar.gz the built web UI, served over HTTP * - overrides.tar.gz the interception overrides tree (terminal shims, the JVM * agent, frida, the browser webextension) that upstream * expects to find on disk at APP_ROOT/overrides * * Usage: httptoolkit [--port 8080] [--server-port 45457] [--mockttp-port 45456] [--no-open] */ // Bun global is present at runtime in the compiled binary: declare const Bun: { file(path: string): { arrayBuffer(): Promise } }; import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs'; import * as fsp from 'fs/promises'; import { x as extractTar } from 'tar'; // Embedded archives. These resolve to paths Bun can read from the compiled binary: import uiArchive from './ui.tar.gz' with { type: 'file' }; import overridesArchive from './overrides.tar.gz' with { type: 'file' }; // Deliberately NOT imported from './src/constants': that module computes APP_ROOT // at load time, and we have to set HTK_APP_ROOT before it is ever evaluated (see // main()). package.json carries the version without pulling in any of that. import pkg from './package.json'; const SERVER_VERSION: string = pkg.version; function printHelp() { console.log(`HTTP Toolkit (self-hosted single binary) v${SERVER_VERSION} Usage: httptoolkit [options] Options: -p, --port Port to serve the web UI on (default: 8080) --server-port HTK management API port (default: 45457) --mockttp-port Mockttp admin port (default: 45456) --no-open Don't open a browser automatically -h, --help Show this help `); } function parseArgs(argv: string[]) { const opts = { port: 8080, serverPort: 45457, mockttpPort: 45456, open: true }; for (let i = 0; i < argv.length; i++) { const a = argv[i]; const eq = a.indexOf('='); const inlineVal = eq !== -1 ? a.slice(eq + 1) : undefined; const key = eq !== -1 ? a.slice(0, eq) : a; const val = () => inlineVal ?? argv[++i]; switch (key) { case '-p': case '--port': opts.port = parseInt(val(), 10); break; case '--server-port': opts.serverPort = parseInt(val(), 10); break; case '--mockttp-port': opts.mockttpPort = parseInt(val(), 10); break; case '--no-open': opts.open = false; break; case '-h': case '--help': printHelp(); process.exit(0); default: console.error(`Unknown option: ${a}`); printHelp(); process.exit(1); } } if (!Number.isInteger(opts.port) || opts.port <= 0 || opts.port > 65535) { console.error(`Invalid --port value`); process.exit(1); } return opts; } // Extract one embedded tarball into `dir`, unless it's already there for this // version. Modes are preserved, which matters: overrides/path holds executable // shims that only work if they stay +x. async function extractArchive(archivePath: string, dir: string): Promise { const marker = path.join(dir, '.extracted'); if (fs.existsSync(marker)) return dir; // already extracted for this version await fsp.rm(dir, { recursive: true, force: true }); await fsp.mkdir(dir, { recursive: true }); const bytes = new Uint8Array(await Bun.file(archivePath).arrayBuffer()); const tmpArchive = path.join(dir, 'archive.tar.gz'); await fsp.writeFile(tmpArchive, bytes); await extractTar({ file: tmpArchive, cwd: dir }); await fsp.unlink(tmpArchive).catch(() => {}); await fsp.writeFile(marker, ''); return dir; } async function main() { const opts = parseArgs(process.argv.slice(2)); // One app root per version, laid out the way upstream expects to find it: // /httptoolkit-/ui // /httptoolkit-/overrides const appRoot = path.join(os.tmpdir(), `httptoolkit-${SERVER_VERSION}`); const uiDir = path.join(appRoot, 'ui'); await extractArchive(uiArchive, uiDir); await extractArchive(overridesArchive, path.join(appRoot, 'overrides')); // Must be set before ./src/constants is evaluated, since APP_ROOT (and hence // OVERRIDES_DIR) is computed once at module load. That's why the app is pulled // in with a dynamic import here rather than a static one at the top of the file. process.env.HTK_APP_ROOT = appRoot; const { startHtkApp } = await import('./src/htk-app'); await startHtkApp({ ...opts, uiDir }); } main().catch((e) => { console.error('Failed to start HTTP Toolkit:', e); process.exit(1); });