Files
smartproxy/ts/proxies/smart-proxy/rust-proxy-bridge.ts

180 lines
6.3 KiB
TypeScript

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<string, never>; result: void };
updateRoutes: { params: { routes: IRouteConfig[] }; result: void };
getMetrics: { params: Record<string, never>; result: any };
getStatistics: { params: Record<string, never>; result: any };
provisionCertificate: { params: { routeName: string }; result: void };
renewCertificate: { params: { routeName: string }; result: void };
getCertificateStatus: { params: { routeName: string }; result: any };
getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
getNftablesStatus: { params: Record<string, never>; 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<string, string> = { x64: 'amd64', arm64: 'arm64' };
const osMap: Record<string, string> = { 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<TSmartProxyCommands>;
constructor() {
super();
this.bridge = new plugins.smartrust.RustBridge<TSmartProxyCommands>({
binaryName: 'rustproxy',
envVarName: 'SMARTPROXY_RUST_BINARY',
platformPackagePrefix: '@push.rocks/smartproxy',
localPaths: buildLocalPaths(),
logger: {
log: (level: string, message: string, data?: Record<string, any>) => {
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<boolean> {
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<void> {
await this.bridge.sendCommand('start', { config });
}
public async stopProxy(): Promise<void> {
await this.bridge.sendCommand('stop', {} as Record<string, never>);
}
public async updateRoutes(routes: IRouteConfig[]): Promise<void> {
await this.bridge.sendCommand('updateRoutes', { routes });
}
public async getMetrics(): Promise<any> {
return this.bridge.sendCommand('getMetrics', {} as Record<string, never>);
}
public async getStatistics(): Promise<any> {
return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
}
public async provisionCertificate(routeName: string): Promise<void> {
await this.bridge.sendCommand('provisionCertificate', { routeName });
}
public async renewCertificate(routeName: string): Promise<void> {
await this.bridge.sendCommand('renewCertificate', { routeName });
}
public async getCertificateStatus(routeName: string): Promise<any> {
return this.bridge.sendCommand('getCertificateStatus', { routeName });
}
public async getListeningPorts(): Promise<number[]> {
const result = await this.bridge.sendCommand('getListeningPorts', {} as Record<string, never>);
return result?.ports ?? [];
}
public async getNftablesStatus(): Promise<any> {
return this.bridge.sendCommand('getNftablesStatus', {} as Record<string, never>);
}
public async setSocketHandlerRelay(socketPath: string): Promise<void> {
await this.bridge.sendCommand('setSocketHandlerRelay', { socketPath });
}
public async addListeningPort(port: number): Promise<void> {
await this.bridge.sendCommand('addListeningPort', { port });
}
public async removeListeningPort(port: number): Promise<void> {
await this.bridge.sendCommand('removeListeningPort', { port });
}
public async loadCertificate(domain: string, cert: string, key: string, ca?: string): Promise<void> {
await this.bridge.sendCommand('loadCertificate', { domain, cert, key, ca });
}
}