--- Build: pass | Tests: FAIL — Tests 9 failed | 2081 passed | 4 skipped (2094)
134 lines
3.6 KiB
TypeScript
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;
|
|
}
|
|
}
|