import * as plugins from './plugins.js'; const execFile = plugins.util.promisify(plugins.childProcess.execFile); export class NftNotRootError extends Error { constructor() { super('Not running as root — nftables commands require root privileges'); this.name = 'NftNotRootError'; } } export class NftCommandError extends Error { public readonly command: string; public readonly stderr: string; constructor(command: string, stderr: string) { super(`nft command failed: ${command} — ${stderr}`); this.name = 'NftCommandError'; this.command = command; this.stderr = stderr; } } /** * Low-level executor for nft CLI commands. */ export class NftExecutor { private dryRun: boolean; constructor(options?: { dryRun?: boolean }) { this.dryRun = options?.dryRun ?? false; } /** Check if running as root (euid 0). */ public isRoot(): boolean { return process.getuid?.() === 0; } /** * Execute a single nft command. * The command may or may not start with "nft " — the prefix is stripped if present. * Returns stdout on success. */ public async exec(command: string): Promise { if (this.dryRun) { return ''; } // Strip "nft " prefix if present const args = command.startsWith('nft ') ? command.slice(4) : command; const { stdout } = await execFile('nft', args.split(/\s+/)); return stdout; } /** * Execute multiple nft commands sequentially. * By default stops on first failure. Set continueOnError to keep going. */ public async execBatch( commands: string[], options?: { continueOnError?: boolean } ): Promise { for (const cmd of commands) { try { await this.exec(cmd); } catch (err) { if (!options?.continueOnError) { throw err; } } } } }