/** * bare-holesail — Holesail TCP/UDP P2P proxy via the official holesail package API only. * Initd unit `bare-holesail` (stock-on managed mode; disable via env/initd) and optional early booter instance (kernel-path). * * Upstream is AGPL-3.0; the booter imports `holesail` directly (same Pear-staged pattern as * bare-openssh / bare-ssh2) instead of depending on ctx.bare / drive-bundle resolution. */ import { appendVarLog, BARE_OS_VAR_LOG_DIR } from './bare-os-var-log.js' import { bareHolesailEnvTruthy } from './bare-holesail-env.js' import { bareHolesailManagedServiceIsRunning, bareHolesailManagedStartService, bareHolesailManagedStopService } from './bare-holesail-managed.js' import { bindFallbackEnabled, isAddrInUse } from './bare-os-bind-fallback.js' import { loadHolesailConstructor } from './bare-holesail-loader.js' import { registerBareInitdDisposer, registerBareService, registerKernelShutdownHook } from './bare-initd.js' export { bareHolesailEnvTruthy } export const BARE_HOLESAIL_LOG = `${BARE_OS_VAR_LOG_DIR}/holesail.log` /** @type {unknown} */ let kernelHolesail = null /** @type {unknown} */ let initdHolesail = null /** @type {(() => void)[]} */ let initdHookUnsubs = [] /** @type {(() => void)[]} */ let kernelHookUnsubs = [] /** @type {boolean} */ let bareHolesailInitdRegistered = false /** * Register the **bare-holesail** initd unit (call from **`bare-user-session-stack`** after unlock). * Stock **`BARE_OS_HOLESAIL_INITD`** default is on; set **`0`** to skip registration. * @param {Record} env */ export function maybeRegisterBareHolesailInitd(env) { if (bareHolesailInitdRegistered) return if (!bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_INITD)) return bareHolesailInitdRegistered = true registerBareService({ name: 'bare-holesail', description: 'Holesail P2P TCP/UDP proxy (official holesail API); on by default (managed multi-tunnel for /bin/holesail); disable with BARE_OS_HOLESAIL_INITD=0 or systemctl disable', logPath: BARE_HOLESAIL_LOG, start: initdStartBareHolesail, stop: initdStopBareHolesail }) } /** * @param {Record} env * @param {string} key */ function envStr(env, key) { const v = env[key] if (v === undefined || v === null) return '' return String(v).trim() } /** * @param {Record} env * @param {'initd' | 'kernel'} profile * @returns {{ enabled: boolean, err?: string, opts?: Record }} */ export function bareHolesailParseConfig(env, profile) { const isKernel = profile === 'kernel' const enabled = isKernel ? bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_KERNEL) : bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_INITD) if (!enabled) return { enabled: false } const P = isKernel ? 'BARE_OS_HOLESAIL_KERNEL_' : 'BARE_OS_HOLESAIL_' const server = bareHolesailEnvTruthy(env[`${P}SERVER`]) const client = bareHolesailEnvTruthy(env[`${P}CLIENT`]) if (server === client) { return { enabled: true, err: 'holesail: set exactly one of SERVER or CLIENT for this profile' } } const key = envStr(env, `${P}KEY`) if (client && !key) { return { enabled: true, err: 'holesail: CLIENT requires KEY' } } /** @type {Record} */ const opts = { server, client, key: key || undefined } const sec = envStr(env, `${P}SECURE`) if (sec !== '') opts.secure = bareHolesailEnvTruthy(sec) const portRaw = envStr(env, `${P}PORT`) if (portRaw !== '') { const n = Number.parseInt(portRaw, 10) if (!Number.isFinite(n)) { return { enabled: true, err: 'holesail: invalid PORT' } } opts.port = n } const host = envStr(env, `${P}HOST`) if (host !== '') opts.host = host const udp = envStr(env, `${P}UDP`) if (udp !== '') opts.udp = bareHolesailEnvTruthy(udp) const logRaw = envStr(env, `${P}LOG`) if (logRaw !== '') { if (/^\d+$/.test(logRaw)) opts.log = Number.parseInt(logRaw, 10) else opts.log = bareHolesailEnvTruthy(logRaw) } return { enabled: true, opts } } /** * @param {Record} ctx * @param {string} line */ function logLine(ctx, line) { void appendVarLog(ctx, BARE_HOLESAIL_LOG, 'holesail', line) } /** * @param {Record} ctx * @param {Record} env */ function debugEnabled(ctx, env) { return bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_DEBUG) } /** * @param {unknown} hs */ async function safeCloseHolesail(hs) { if (!hs || typeof hs.close !== 'function') return try { await hs.close() } catch { /* ignore */ } } /** * Optional early booter instance: runs after ctx is wired (repl phase), before bare-initd. * Env: BARE_OS_HOLESAIL_KERNEL=1 and BARE_OS_HOLESAIL_KERNEL_SERVER|CLIENT, etc. * * @param {Record} ctx */ export async function maybeStartBareHolesailKernelFromBooter(ctx) { const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : /** @type {Record} */ ({}) const parsed = bareHolesailParseConfig(env, 'kernel') if (!parsed.enabled) return if (parsed.err) { logLine(ctx, `kernel: ${parsed.err}`) try { ctx.console?.error?.(`[bare-holesail] ${parsed.err}`) } catch { /* ignore */ } return } if (!parsed.opts) return try { const Holesail = await loadHolesailConstructor(ctx) let hs = new Holesail(parsed.opts) try { await hs.ready() } catch (e) { if (!bindFallbackEnabled(env) || !isAddrInUse(e)) throw e await safeCloseHolesail(hs) hs = new Holesail({ ...parsed.opts, port: 0 }) await hs.ready() logLine(ctx, 'kernel: ready after EADDRINUSE fallback (ephemeral port)') } kernelHolesail = hs logLine(ctx, 'kernel: ready (early booter instance)') if (debugEnabled(ctx, env)) { try { const info = hs.info const url = info && typeof info === 'object' ? info.url : '' ctx.console?.log?.(`[bare-holesail] kernel url=${String(url).slice(0, 120)}`) } catch { /* ignore */ } } kernelHookUnsubs.push( ctx.bareOsRegisterSuspendHook(async () => { if (kernelHolesail && typeof kernelHolesail.pause === 'function') { try { await kernelHolesail.pause() } catch { /* ignore */ } } }) ) kernelHookUnsubs.push( ctx.bareOsRegisterResumeHook(async () => { if (kernelHolesail && typeof kernelHolesail.resume === 'function') { try { await kernelHolesail.resume() } catch { /* ignore */ } } }) ) registerKernelShutdownHook(async () => { for (const u of kernelHookUnsubs.splice(0)) { try { u() } catch { /* ignore */ } } const h = kernelHolesail kernelHolesail = null await safeCloseHolesail(h) logLine(ctx, 'kernel: closed (session shutdown)') }) } catch (e) { const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e) logLine(ctx, `kernel: start error: ${msg}`) try { ctx.console?.error?.(`[bare-holesail] kernel: ${msg}`) } catch { /* ignore */ } } } async function initdStartBareHolesail(ctx) { const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : /** @type {Record} */ ({}) const initdOn = bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_INITD) const managedOn = bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_MANAGED) if (initdOn && managedOn) { try { await bareHolesailManagedStartService(ctx, env) } catch (e) { const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e) logLine(ctx, `initd managed: start error: ${msg}`) throw e } return } const parsed = bareHolesailParseConfig(env, 'initd') if (!parsed.enabled) return if (parsed.err) { logLine(ctx, `initd: ${parsed.err}`) throw new Error(parsed.err) } if (!parsed.opts) return try { const Holesail = await loadHolesailConstructor(ctx) let hs = new Holesail(parsed.opts) try { await hs.ready() } catch (e) { if (!bindFallbackEnabled(env) || !isAddrInUse(e)) throw e await safeCloseHolesail(hs) hs = new Holesail({ ...parsed.opts, port: 0 }) await hs.ready() logLine(ctx, 'initd: ready after EADDRINUSE fallback (ephemeral port)') } initdHolesail = hs logLine(ctx, 'initd: ready') if (debugEnabled(ctx, env)) { try { const info = hs.info const url = info && typeof info === 'object' ? info.url : '' ctx.console?.log?.(`[bare-holesail] initd url=${String(url).slice(0, 120)}`) } catch { /* ignore */ } } initdHookUnsubs.push( ctx.bareOsRegisterSuspendHook(async () => { if (initdHolesail && typeof initdHolesail.pause === 'function') { try { await initdHolesail.pause() } catch { /* ignore */ } } }) ) initdHookUnsubs.push( ctx.bareOsRegisterResumeHook(async () => { if (initdHolesail && typeof initdHolesail.resume === 'function') { try { await initdHolesail.resume() } catch { /* ignore */ } } }) ) } catch (e) { const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e) logLine(ctx, `initd: start error: ${msg}`) throw e } } async function initdStopBareHolesail(ctx) { if (bareHolesailManagedServiceIsRunning()) { await bareHolesailManagedStopService(ctx) logLine(ctx, 'initd: stopped (managed)') return } for (const u of initdHookUnsubs.splice(0)) { try { u() } catch { /* ignore */ } } const h = initdHolesail initdHolesail = null await safeCloseHolesail(h) logLine(ctx, 'initd: stopped') } registerBareInitdDisposer(() => { if (bareHolesailManagedServiceIsRunning()) { void bareHolesailManagedStopService({}) return } for (const u of initdHookUnsubs.splice(0)) { try { u() } catch { /* ignore */ } } const h = initdHolesail initdHolesail = null void safeCloseHolesail(h) })