import { readEnvFile } from '../src/env.js'; import { logger } from '../src/logger.js'; type BastilleRunner = (args: string[]) => { ok: boolean; output: string }; interface TailscaleConfig { enabled: boolean; authKey: string; } function parseYes(value?: string): boolean { return /^(YES|yes|true|TRUE|1)$/u.test(value || ''); } function loadTailscaleConfig(): TailscaleConfig { const envValues = readEnvFile(['FEATURE_TAILSCALE', 'TAILSCALE_AUTHKEY']); const enabled = parseYes(process.env.FEATURE_TAILSCALE || envValues.FEATURE_TAILSCALE); const authKey = process.env.TAILSCALE_AUTHKEY || envValues.TAILSCALE_AUTHKEY || ''; return { enabled, authKey }; } function normalizeHostname(value: string): string { const cleaned = value.toLowerCase().replace(/[^a-z0-9-]/gu, ''); return cleaned || 'clawdie'; } export function maybeEnableTailscaleInJail( runBastille: BastilleRunner, jailName: string, hostnameHint: string, ): void { const config = loadTailscaleConfig(); if (!config.enabled) return; if (!config.authKey) { logger.warn({ jailName }, 'Tailscale enabled but no auth key; skipping jail login'); return; } const hostname = normalizeHostname(hostnameHint || jailName); const pkg = runBastille(['pkg', jailName, 'install', '-y', 'tailscale']); if (!pkg.ok) { logger.warn({ jailName }, 'Tailscale package install failed in jail (skipping)'); return; } const sysrc = runBastille(['cmd', jailName, 'sysrc', 'tailscaled_enable=YES']); if (!sysrc.ok) { logger.warn({ jailName }, 'Tailscale sysrc enable failed in jail'); } const start = runBastille(['service', jailName, 'tailscaled', 'restart']); if (!start.ok) { logger.warn({ jailName }, 'Tailscale service start failed in jail'); } const up = runBastille([ 'cmd', jailName, 'tailscale', 'up', '--authkey', config.authKey, '--hostname', hostname, ]); if (!up.ok) { logger.warn({ jailName }, 'Tailscale up failed in jail (check jail logs)'); return; } logger.info({ jailName, hostname }, 'Tailscale enabled in jail'); }