367 lines
10 KiB
JavaScript
367 lines
10 KiB
JavaScript
/**
|
|
* 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<string, string | undefined>} 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<string, string | undefined>} env
|
|
* @param {string} key
|
|
*/
|
|
function envStr(env, key) {
|
|
const v = env[key]
|
|
if (v === undefined || v === null) return ''
|
|
return String(v).trim()
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, string | undefined>} env
|
|
* @param {'initd' | 'kernel'} profile
|
|
* @returns {{ enabled: boolean, err?: string, opts?: Record<string, unknown> }}
|
|
*/
|
|
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<string, unknown>} */
|
|
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<string, unknown>} ctx
|
|
* @param {string} line
|
|
*/
|
|
function logLine(ctx, line) {
|
|
void appendVarLog(ctx, BARE_HOLESAIL_LOG, 'holesail', line)
|
|
}
|
|
|
|
/**
|
|
* @param {Record<string, unknown>} ctx
|
|
* @param {Record<string, string | undefined>} 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<string, unknown>} ctx
|
|
*/
|
|
export async function maybeStartBareHolesailKernelFromBooter(ctx) {
|
|
const env =
|
|
ctx.env && typeof ctx.env === 'object'
|
|
? /** @type {Record<string, string | undefined>} */ (ctx.env)
|
|
: /** @type {Record<string, string | undefined>} */ ({})
|
|
|
|
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<string, string | undefined>} */ (ctx.env)
|
|
: /** @type {Record<string, string | undefined>} */ ({})
|
|
|
|
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)
|
|
})
|