clawdie-ai/setup/pf.ts
Operator & Codex f73e522481 Place PF include after translation rules
---
Build: pass | Tests: FAIL — Tests  9 failed | 2081 passed | 4 skipped (2094)
2026-05-02 20:41:31 +02:00

134 lines
3.6 KiB
TypeScript

import { execSync } from 'child_process';
import fs from 'fs';
import { logger } from '../src/logger.js';
import { emitStatus } from './status.js';
import { getPlatform } from './platform.js';
import { SUBNET_BASE } from '../src/config.js';
import { resolveSystemEnv } from './system-env.js';
const PF_CONF = '/etc/pf.conf';
const PF_WARDEN_INCLUDE = '/etc/pf.warden.conf';
function pfEnabled(): boolean {
try {
const out = execSync('pfctl -s info', { encoding: 'utf-8' });
return /Status:\s+Enabled/i.test(out);
} catch {
return false;
}
}
function detectExtIf(): string {
const systemEnv = resolveSystemEnv(process.cwd());
if (systemEnv.networkExternalIf) return systemEnv.networkExternalIf;
const env = (process.env.EXT_IF || '').trim();
if (env) return env;
try {
const out = execSync('route -n get default', {
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'ignore'],
});
const match = out.match(/^\s*interface:\s*(\S+)/m);
if (match?.[1]) return match[1].trim();
} catch {
// fall back
}
return 'vtnet0';
}
function ensureIncludeLine(pfConf: string, includePath: string): void {
const line = `include "${includePath}"`;
const content = fs.readFileSync(pfConf, 'utf-8');
const lines = content.split('\n').filter((l) => l.trim() !== line);
let insertAt = lines.findIndex((l) => {
const trimmed = l.trim();
return (
trimmed.startsWith('pass ') ||
trimmed.startsWith('block ')
);
});
if (insertAt === -1) {
insertAt = lines.length;
}
lines.splice(insertAt, 0, line);
fs.writeFileSync(pfConf, `${lines.join('\n').replace(/\s*$/u, '')}\n`);
}
export async function run(): Promise<void> {
if (getPlatform() !== 'freebsd') {
emitStatus('SETUP_PF', {
STATUS: 'failed',
ERROR: 'unsupported_platform',
LOG: 'logs/setup.log',
});
process.exit(1);
}
if (!fs.existsSync(PF_CONF)) {
emitStatus('SETUP_PF', {
STATUS: 'failed',
ERROR: 'pf_conf_missing',
LOG: 'logs/setup.log',
});
throw new Error(`Missing ${PF_CONF}`);
}
try {
const systemEnv = resolveSystemEnv(process.cwd());
const wardenIf =
systemEnv.networkInternalIf ||
(process.env.WARDEN_BRIDGE || 'warden0').trim() ||
'warden0';
const wardenNet = (process.env.WARDEN_SUBNET || `${SUBNET_BASE}.0/24`).trim();
const extIf = detectExtIf();
const includeRules = [
`ext_if = "${extIf}"`,
`warden_if = "${wardenIf}"`,
`warden_net = "${wardenNet}"`,
'',
'nat on $ext_if from $warden_net to any -> ($ext_if)',
'pass quick on $warden_if inet from $warden_net to any keep state',
'',
].join('\n');
fs.writeFileSync(PF_WARDEN_INCLUDE, includeRules, { mode: 0o644 });
ensureIncludeLine(PF_CONF, PF_WARDEN_INCLUDE);
// Validate before applying
execSync(`pfctl -nf ${PF_CONF}`, { stdio: 'inherit' });
execSync(`pfctl -f ${PF_CONF}`, { stdio: 'inherit' });
if (!pfEnabled()) {
execSync('pfctl -e', { stdio: 'inherit' });
}
emitStatus('SETUP_PF', {
STATUS: 'success',
PF_CONF: PF_CONF,
PF_WARDEN_INCLUDE: PF_WARDEN_INCLUDE,
EXT_IF: extIf,
WARDEN_IF: wardenIf,
WARDEN_NET: wardenNet,
PF_ENABLED: pfEnabled() ? 'true' : 'false',
LOG: 'logs/setup.log',
});
logger.info({ pfConf: PF_CONF }, 'PF reload complete');
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
emitStatus('SETUP_PF', {
STATUS: 'failed',
ERROR: message,
LOG: 'logs/setup.log',
});
throw error;
}
}