import * as plugins from '../../plugins.js'; import { logger } from '../../core/utils/logger.js'; import type { IRouteConfig } from './models/route-types.js'; /** * Type-safe command definitions for the Rust proxy IPC protocol. */ type TSmartProxyCommands = { start: { params: { config: any }; result: void }; stop: { params: Record; result: void }; updateRoutes: { params: { routes: IRouteConfig[] }; result: void }; getMetrics: { params: Record; result: any }; getStatistics: { params: Record; result: any }; provisionCertificate: { params: { routeName: string }; result: void }; renewCertificate: { params: { routeName: string }; result: void }; getCertificateStatus: { params: { routeName: string }; result: any }; getListeningPorts: { params: Record; result: { ports: number[] } }; getNftablesStatus: { params: Record; result: any }; setSocketHandlerRelay: { params: { socketPath: string }; result: void }; addListeningPort: { params: { port: number }; result: void }; removeListeningPort: { params: { port: number }; result: void }; loadCertificate: { params: { domain: string; cert: string; key: string; ca?: string }; result: void }; }; /** * Get the package root directory using import.meta.url. * This file is at ts/proxies/smart-proxy/, so package root is 3 levels up. */ function getPackageRoot(): string { const thisDir = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url)); return plugins.path.resolve(thisDir, '..', '..', '..'); } /** * Map Node.js process.platform/process.arch to tsrust's friendly name suffix. * tsrust names cross-compiled binaries as: rustproxy_linux_amd64, rustproxy_linux_arm64, etc. */ function getTsrustPlatformSuffix(): string | null { const archMap: Record = { x64: 'amd64', arm64: 'arm64' }; const osMap: Record = { linux: 'linux', darwin: 'macos' }; const os = osMap[process.platform]; const arch = archMap[process.arch]; if (os && arch) { return `${os}_${arch}`; } return null; } /** * Build local search paths for the Rust binary, including dist_rust/ candidates * (built by tsrust) and local development build paths. */ function buildLocalPaths(): string[] { const packageRoot = getPackageRoot(); const suffix = getTsrustPlatformSuffix(); const paths: string[] = []; // dist_rust/ candidates (tsrust cross-compiled output) if (suffix) { paths.push(plugins.path.join(packageRoot, 'dist_rust', `rustproxy_${suffix}`)); } paths.push(plugins.path.join(packageRoot, 'dist_rust', 'rustproxy')); // Local dev build paths paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'release', 'rustproxy')); paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'debug', 'rustproxy')); return paths; } /** * Bridge between TypeScript SmartProxy and the Rust binary. * Wraps @push.rocks/smartrust's RustBridge with type-safe command definitions. */ export class RustProxyBridge extends plugins.EventEmitter { private bridge: plugins.smartrust.RustBridge; constructor() { super(); this.bridge = new plugins.smartrust.RustBridge({ binaryName: 'rustproxy', envVarName: 'SMARTPROXY_RUST_BINARY', platformPackagePrefix: '@push.rocks/smartproxy', localPaths: buildLocalPaths(), logger: { log: (level: string, message: string, data?: Record) => { logger.log(level as any, message, data); }, }, }); // Forward events from the inner bridge this.bridge.on('exit', (code: number | null, signal: string | null) => { this.emit('exit', code, signal); }); } /** * Spawn the Rust binary in management mode. * Returns true if the binary was found and spawned successfully. */ public async spawn(): Promise { return this.bridge.spawn(); } /** * Kill the Rust process and clean up. */ public kill(): void { this.bridge.kill(); } /** * Whether the bridge is currently running. */ public get running(): boolean { return this.bridge.running; } // --- Convenience methods for each management command --- public async startProxy(config: any): Promise { await this.bridge.sendCommand('start', { config }); } public async stopProxy(): Promise { await this.bridge.sendCommand('stop', {} as Record); } public async updateRoutes(routes: IRouteConfig[]): Promise { await this.bridge.sendCommand('updateRoutes', { routes }); } public async getMetrics(): Promise { return this.bridge.sendCommand('getMetrics', {} as Record); } public async getStatistics(): Promise { return this.bridge.sendCommand('getStatistics', {} as Record); } public async provisionCertificate(routeName: string): Promise { await this.bridge.sendCommand('provisionCertificate', { routeName }); } public async renewCertificate(routeName: string): Promise { await this.bridge.sendCommand('renewCertificate', { routeName }); } public async getCertificateStatus(routeName: string): Promise { return this.bridge.sendCommand('getCertificateStatus', { routeName }); } public async getListeningPorts(): Promise { const result = await this.bridge.sendCommand('getListeningPorts', {} as Record); return result?.ports ?? []; } public async getNftablesStatus(): Promise { return this.bridge.sendCommand('getNftablesStatus', {} as Record); } public async setSocketHandlerRelay(socketPath: string): Promise { await this.bridge.sendCommand('setSocketHandlerRelay', { socketPath }); } public async addListeningPort(port: number): Promise { await this.bridge.sendCommand('addListeningPort', { port }); } public async removeListeningPort(port: number): Promise { await this.bridge.sendCommand('removeListeningPort', { port }); } public async loadCertificate(domain: string, cert: string, key: string, ca?: string): Promise { await this.bridge.sendCommand('loadCertificate', { domain, cert, key, ca }); } }