Holesail Service Daemon + Bin Client

This commit is contained in:
Raven Scott
2026-04-09 20:42:54 -04:00
parent 3a69906046
commit c8c877f688
168 changed files with 387155 additions and 30713 deletions
+41
View File
@@ -63,6 +63,7 @@ import { runOpensslCli } from './lib/openssl-cli.js'
import { runSshKeygenCli } from './lib/ssh-keygen-cli.js'
import { runTarCli } from './lib/tar-cli.js'
import { runSystemctlCli } from './lib/systemctl-cli.js'
import { runHolesailCli } from './lib/holesail-cli.js'
import { createBareOsDiskOsBridge } from './lib/bare-os-disk-os-bridge.js'
import { buildBareOsHyperbeeGuestHint } from './lib/bare-os-hyperbee-guest-hint.js'
import { invokeBareOsPearUpdaterDelegate } from './lib/bare-os-pear-updater-bridge.js'
@@ -193,6 +194,7 @@ import {
bareOsListThemeNames
} from './lib/bare-os-theme-presets.js'
import './lib/bare-cron.js'
import { maybeStartBareHolesailKernelFromBooter } from './lib/bare-holesail.js'
import { runSshdCli, getBareOpensshProcJsonText } from './lib/bare-openssh.js'
import { createHash } from 'bare-crypto'
import { buildBareOsHostProcJsonText } from './lib/bare-os-host-proc-snapshot.js'
@@ -733,6 +735,27 @@ async function executeKernel(disk, store, swarm, initSource) {
'BARE_OS_PROC_RLIMITS_JSON',
'BARE_OS_SUBPROCESS_BRIDGE_UID_GID_MAP_JSON',
'BARE_OS_HDMS_VAULT_ROTATE_COUNT',
'BARE_OS_HOLESAIL_INITD',
'BARE_OS_HOLESAIL_MANAGED',
'BARE_OS_HOLESAIL_SERVER',
'BARE_OS_HOLESAIL_CLIENT',
'BARE_OS_HOLESAIL_KEY',
'BARE_OS_HOLESAIL_SECURE',
'BARE_OS_HOLESAIL_PORT',
'BARE_OS_HOLESAIL_HOST',
'BARE_OS_HOLESAIL_UDP',
'BARE_OS_HOLESAIL_LOG',
'BARE_OS_HOLESAIL_DEBUG',
'BARE_OS_HOLESAIL_STATE',
'BARE_OS_HOLESAIL_KERNEL',
'BARE_OS_HOLESAIL_KERNEL_SERVER',
'BARE_OS_HOLESAIL_KERNEL_CLIENT',
'BARE_OS_HOLESAIL_KERNEL_KEY',
'BARE_OS_HOLESAIL_KERNEL_SECURE',
'BARE_OS_HOLESAIL_KERNEL_PORT',
'BARE_OS_HOLESAIL_KERNEL_HOST',
'BARE_OS_HOLESAIL_KERNEL_UDP',
'BARE_OS_HOLESAIL_KERNEL_LOG',
'BARE_OS_SUBPROCESS_BRIDGE_JOBS_JSON',
'BARE_OS_SUBPROCESS_BRIDGE_META_JSON',
'BARE_OS_HOST_BARE_OS_PROC',
@@ -777,6 +800,18 @@ async function executeKernel(disk, store, swarm, initSource) {
shellEnv.COLORTERM = String(hostEnv.COLORTERM)
}
}
if (
shellEnv.BARE_OS_HOLESAIL_INITD === undefined ||
shellEnv.BARE_OS_HOLESAIL_INITD === ''
) {
shellEnv.BARE_OS_HOLESAIL_INITD = '1'
}
if (
shellEnv.BARE_OS_HOLESAIL_MANAGED === undefined ||
shellEnv.BARE_OS_HOLESAIL_MANAGED === ''
) {
shellEnv.BARE_OS_HOLESAIL_MANAGED = '1'
}
let bootProfileResolved = ''
const bpfEarly = shellEnv.BARE_OS_BOOT_PROFILE
if (bpfEarly != null && String(bpfEarly).trim()) {
@@ -8585,6 +8620,10 @@ async function executeKernel(disk, store, swarm, initSource) {
async bareOsRunSystemctlCli(argv) {
return runSystemctlCli(ctx, argv)
},
/** Persisted Holesail tunnels (/bin/holesail); requires managed bare-holesail for live start. */
async bareOsRunHolesailCli(argv) {
return runHolesailCli(ctx, argv)
},
bareOsEmitPearStageHint(payload = {}) {
if (typeof globalThis.process?.emit !== 'function') return false
try {
@@ -9588,6 +9627,8 @@ async function executeKernel(disk, store, swarm, initSource) {
emitBooterBootPhase('repl')
await maybeStartBareHolesailKernelFromBooter(ctx)
await startBareInitd(ctx)
{
const active = listBareServices()
@@ -0,0 +1,10 @@
/**
* Shared env parsing for bare-holesail / managed tunnels.
* @param {unknown} v
*/
export function bareHolesailEnvTruthy(v) {
const s = String(v ?? '')
.trim()
.toLowerCase()
return s === '1' || s === 'true' || s === 'yes'
}
@@ -0,0 +1,29 @@
/**
* Resolve the upstream `holesail` class for bare-holesail / managed tunnels.
*
* This follows the same pattern as `bare-openssh` / `bare-ssh2`: keep `holesail`
* as a booter-owned dependency and import it directly so Pear stages the package.
* Upstream is AGPL-3.0.
*/
import Holesail from 'holesail'
/** ESM import so Pear stages `holesail` and its traced package deps. */
let holesailCache = null
/**
* @param {Record<string, unknown>} ctx
* @returns {Promise<new (opts?: object) => { ready: () => Promise<void>, close: () => Promise<void>, pause?: () => Promise<void>, resume?: () => Promise<void>, info?: unknown }>}
*/
export async function loadHolesailConstructor(ctx) {
void ctx
if (!holesailCache) {
const Ho = Holesail && Holesail.default ? Holesail.default : Holesail
if (typeof Ho !== 'function') {
throw new Error('holesail: expected default export to be a constructor')
}
holesailCache = Ho
}
return holesailCache
}
@@ -0,0 +1,368 @@
import { dirname } from 'node:path'
/**
* Multi-tunnel Holesail manager: persisted state on the VFS, one Holesail instance per entry.
* Used when managed mode is on (stock default with BARE_OS_HOLESAIL_INITD/MANAGED).
*
* State path: BARE_OS_HOLESAIL_STATE or default ~/.holesail/state.json (VFS expands ~/ under $HOME).
* Upstream holesail is AGPL-3.0.
*/
import { bareHolesailEnvTruthy } from './bare-holesail-env.js'
import { loadHolesailConstructor } from './bare-holesail-loader.js'
import { appendVarLog, BARE_OS_VAR_LOG_DIR } from './bare-os-var-log.js'
export const BARE_HOLESAIL_STATE_DIR = '~/.holesail'
export const BARE_HOLESAIL_STATE_DEFAULT = `${BARE_HOLESAIL_STATE_DIR}/state.json`
const HOLESAIL_LOG = `${BARE_OS_VAR_LOG_DIR}/holesail.log`
/** @type {Map<string, { hs: unknown, entry: Record<string, unknown> }>} */
const managedInstances = new Map()
/** @type {(() => void)[]} */
let managedHookUnsubs = []
let managedServiceRunning = false
/**
* @param {Record<string, string | undefined>} env
*/
export function bareHolesailManagedStatePath(env) {
const raw = String(env.BARE_OS_HOLESAIL_STATE || '').trim()
return raw || BARE_HOLESAIL_STATE_DEFAULT
}
function logLine(ctx, line) {
void appendVarLog(ctx, HOLESAIL_LOG, 'holesail', line)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} dir
*/
async function ensureDir(ctx, dir) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.mkdir !== 'function') return
try {
await vfs.mkdir(dir, { recursive: true })
} catch {
/* ignore */
}
}
/**
* @param {unknown} hs
*/
async function safeCloseHolesail(hs) {
if (!hs || typeof hs.close !== 'function') return
try {
await hs.close()
} catch {
/* ignore */
}
}
/**
* @param {string} id
*/
export function bareHolesailManagedValidateId(id) {
const s = String(id || '').trim()
if (!s || s.length > 64) return { ok: false, err: 'invalid id (164 chars)' }
if (!/^[a-zA-Z0-9._-]+$/.test(s))
return { ok: false, err: 'invalid id (use [a-zA-Z0-9._-])' }
return { ok: true, id: s }
}
/**
* @param {Record<string, unknown>} raw
* @returns {{ ok: true, entry: Record<string, unknown> } | { ok: false, err: string }}
*/
export function bareHolesailManagedNormalizeEntry(raw) {
if (!raw || typeof raw !== 'object')
return { ok: false, err: 'connection entry must be an object' }
const server = bareHolesailEnvTruthy(
/** @type {Record<string, unknown>} */ (raw).server
)
const client = bareHolesailEnvTruthy(
/** @type {Record<string, unknown>} */ (raw).client
)
if (server === client) {
return { ok: false, err: 'set exactly one of server or client' }
}
/** @type {Record<string, unknown>} */
const entry = { server, client }
const key = String(raw.key ?? '').trim()
if (client && !key) return { ok: false, err: 'client requires key' }
if (key) entry.key = key
if (raw.secure !== undefined && raw.secure !== null && String(raw.secure) !== '')
entry.secure = bareHolesailEnvTruthy(raw.secure)
if (raw.port != null && raw.port !== '') {
const n = Number.parseInt(String(raw.port), 10)
if (!Number.isFinite(n)) return { ok: false, err: 'invalid port' }
entry.port = n
}
const host = String(raw.host ?? '').trim()
if (host) entry.host = host
if (raw.udp !== undefined && raw.udp !== null && String(raw.udp) !== '')
entry.udp = bareHolesailEnvTruthy(raw.udp)
if (raw.log !== undefined && raw.log !== null && raw.log !== '') {
if (typeof raw.log === 'number') entry.log = raw.log
else if (/^\d+$/.test(String(raw.log)))
entry.log = Number.parseInt(String(raw.log), 10)
else entry.log = bareHolesailEnvTruthy(raw.log)
}
const enabled =
raw.enabled === undefined || raw.enabled === null
? true
: bareHolesailEnvTruthy(raw.enabled)
entry.enabled = enabled
return { ok: true, entry }
}
/**
* @param {unknown} parsed
*/
function coerceState(parsed) {
if (!parsed || typeof parsed !== 'object') {
return { version: 1, connections: {} }
}
const o = /** @type {Record<string, unknown>} */ (parsed)
const conns = o.connections
const connections =
conns && typeof conns === 'object' && !Array.isArray(conns)
? /** @type {Record<string, unknown>} */ (conns)
: {}
return { version: 1, connections }
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
*/
export async function bareHolesailManagedReadState(ctx, env) {
const path = bareHolesailManagedStatePath(env)
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') {
return { path, state: { version: 1, connections: {} } }
}
try {
const buf = await vfs.readFile(path)
const text = ctx.b4a ? ctx.b4a.toString(buf, 'utf8') : Buffer.from(buf).toString('utf8')
const parsed = JSON.parse(text)
return { path, state: coerceState(parsed) }
} catch {
return { path, state: { version: 1, connections: {} } }
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {{ version: number, connections: Record<string, unknown> }} state
*/
export async function bareHolesailManagedWriteState(ctx, env, state) {
const path = bareHolesailManagedStatePath(env)
const vfs = ctx.vfs
if (!vfs || typeof vfs.writeFile !== 'function') {
throw new Error('holesail: vfs.writeFile unavailable')
}
await ensureDir(ctx, dirname(path))
const body = JSON.stringify(state, null, 2) + '\n'
await vfs.writeFile(path, ctx.b4a.from(body))
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
*/
export function bareHolesailManagedDebugEnabled(ctx, env) {
return bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_DEBUG)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} id
* @param {Record<string, unknown>} entry
*/
async function startManagedInstance(ctx, env, id, entry) {
if (managedInstances.has(id)) return
const norm = bareHolesailManagedNormalizeEntry(entry)
if (!norm.ok) throw new Error(norm.err)
if (!norm.entry.enabled) return
const Holesail = await loadHolesailConstructor(ctx)
const hs = new Holesail(norm.entry)
await hs.ready()
managedInstances.set(id, { hs, entry: norm.entry })
logLine(ctx, `managed: started ${id}`)
if (bareHolesailManagedDebugEnabled(ctx, env)) {
try {
const info = hs.info
const url = info && typeof info === 'object' ? info.url : ''
ctx.console?.log?.(`[bare-holesail] managed ${id} url=${String(url).slice(0, 120)}`)
} catch {
/* ignore */
}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} id
*/
async function stopManagedInstance(ctx, id) {
const rec = managedInstances.get(id)
if (!rec) return
managedInstances.delete(id)
await safeCloseHolesail(rec.hs)
logLine(ctx, `managed: stopped ${id}`)
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
*/
export async function bareHolesailManagedStartService(ctx, env) {
if (managedServiceRunning) return
const { state } = await bareHolesailManagedReadState(ctx, env)
managedServiceRunning = true
for (const [id, raw] of Object.entries(state.connections)) {
const idOk = bareHolesailManagedValidateId(id)
if (!idOk.ok) {
logLine(ctx, `managed: skip invalid id ${id}: ${idOk.err}`)
continue
}
const norm = bareHolesailManagedNormalizeEntry(raw)
if (!norm.ok) {
logLine(ctx, `managed: skip ${id}: ${norm.err}`)
continue
}
if (!norm.entry.enabled) continue
try {
await startManagedInstance(ctx, env, idOk.id, norm.entry)
} catch (e) {
const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
logLine(ctx, `managed: start ${id} failed: ${msg}`)
try {
ctx.console?.error?.(`[bare-holesail] managed ${id}: ${msg}`)
} catch {
/* ignore */
}
}
}
managedHookUnsubs.push(
ctx.bareOsRegisterSuspendHook(async () => {
for (const { hs } of managedInstances.values()) {
if (hs && typeof hs.pause === 'function') {
try {
await hs.pause()
} catch {
/* ignore */
}
}
}
})
)
managedHookUnsubs.push(
ctx.bareOsRegisterResumeHook(async () => {
for (const { hs } of managedInstances.values()) {
if (hs && typeof hs.resume === 'function') {
try {
await hs.resume()
} catch {
/* ignore */
}
}
}
})
)
logLine(ctx, 'managed: service ready')
}
/**
* @param {Record<string, unknown>} ctx
*/
export async function bareHolesailManagedStopService(ctx) {
for (const u of managedHookUnsubs.splice(0)) {
try {
u()
} catch {
/* ignore */
}
}
const ids = [...managedInstances.keys()]
for (const id of ids) {
await stopManagedInstance(ctx, id)
}
managedServiceRunning = false
logLine(ctx, 'managed: service stopped')
}
export function bareHolesailManagedServiceIsRunning() {
return managedServiceRunning
}
/**
* @param {string} id
*/
export function bareHolesailManagedRuntimeRunning(id) {
return managedInstances.has(id)
}
/**
* @param {string} id
*/
export function bareHolesailManagedRuntimeUrl(id) {
const rec = managedInstances.get(id)
if (!rec || !rec.hs) return ''
try {
const info = rec.hs.info
if (info && typeof info === 'object' && 'url' in info)
return String(/** @type {{ url?: string }} */ (info).url || '')
} catch {
/* ignore */
}
return ''
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {string} id
*/
export async function bareHolesailManagedStartOne(ctx, env, id) {
if (!bareHolesailManagedServiceIsRunning()) {
throw new Error('holesail: managed daemon is not running (enable bare-holesail + MANAGED)')
}
const idOk = bareHolesailManagedValidateId(id)
if (!idOk.ok) throw new Error(idOk.err)
const { state } = await bareHolesailManagedReadState(ctx, env)
const raw = state.connections[idOk.id]
if (!raw) throw new Error(`holesail: unknown connection ${idOk.id}`)
const norm = bareHolesailManagedNormalizeEntry(raw)
if (!norm.ok) throw new Error(norm.err)
if (!norm.entry.enabled) throw new Error(`holesail: ${idOk.id} is disabled`)
await startManagedInstance(ctx, env, idOk.id, norm.entry)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} id
*/
export async function bareHolesailManagedStopOne(ctx, id) {
const idOk = bareHolesailManagedValidateId(id)
if (!idOk.ok) throw new Error(idOk.err)
await stopManagedInstance(ctx, idOk.id)
}
@@ -0,0 +1,336 @@
/**
* 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 { 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 = []
/**
* @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)
const hs = new Holesail(parsed.opts)
await hs.ready()
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)
const hs = new Holesail(parsed.opts)
await hs.ready()
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)
})
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
})
@@ -43,7 +43,8 @@ export const BARE_INITD_TIMERS_DIR = '~/.config/bare-os/timers'
/** Built-in ordering when no drop-in file exists (kernel-logger before cron). */
export const BARE_INITD_DEFAULT_AFTER = Object.freeze({
'bare-cron': ['kernel-logger'],
'bare-openssh': ['kernel-logger', 'bare-cron']
'bare-openssh': ['kernel-logger', 'bare-cron'],
'bare-holesail': ['kernel-logger']
})
/**
@@ -1187,6 +1187,14 @@ export default {
"optional": true,
"nativeHint": true,
"skipReason": "Optional Holepunch bare-*; may be native, platform-specific, or Bare-only"
},
{
"ctxKey": "holesail",
"package": "holesail",
"export": "default",
"bundle": true,
"optional": false,
"nativeHint": false
}
]
}
@@ -1182,6 +1182,14 @@
"optional": true,
"nativeHint": true,
"skipReason": "Optional Holepunch bare-*; may be native, platform-specific, or Bare-only"
},
{
"ctxKey": "holesail",
"package": "holesail",
"export": "default",
"bundle": true,
"optional": false,
"nativeHint": false
}
]
}
+250 -18
View File
@@ -3,12 +3,29 @@
* plus optional merge from trusted IIFE bundles on the system drive.
*/
import b4a from 'b4a'
import { readFileSync } from '#host-fs'
import { dirname, join } from '#host-path'
import { fileURLToPath } from 'url'
import bareModuleManifestEmbedded from './bare-module-manifest.data.mjs'
import { bareOsHostBooterWarn } from './bare-os-host-booter-log.js'
/**
* Used only by {@link tryLoadBareCtxKeyFromDriveBundlePath}: some Bare booters lack
* `TextDecoder`. Dynamic `import('b4a')` fails on Pear (no referrer); static import is fine.
* @param {Uint8Array} buf
*/
function decodeUtf8BytesBareFallback(buf) {
if (typeof TextDecoder !== 'undefined') {
try {
return new TextDecoder().decode(buf)
} catch {
/* ignore */
}
}
return b4a.toString(buf, 'utf8')
}
/**
* `fileURLToPath` only accepts `file:` URLs. Pear uses `pear://dev/...` for `import.meta.url`,
* so we must not call it at load time or when running under `pear run`.
@@ -26,35 +43,160 @@ function bareOsBooterPackageJsonPathForCreateRequire() {
/**
* Drive IIFEs call `require("events")`, `require("bare-stream")`, …; `new Function` has no
* lexical `require`, so they look at **`globalThis.require`**. On **`file:`** dev trees, install
* Node's `createRequire` for the booter package for the duration of bundle eval. Under Pear
* (`pear://`), skip — runtime `require` or `tryBareFetchImportWhenDriveMissing` covers fetch.
* lexical `require`, so they look at **`globalThis.require`**. Bundled code also calls
* **`__require.addon()`** (native bindings); that only works when `globalThis.require` is from
* **`bare-module`'s `createRequire`** (exposes **`require.addon`**). A bare function or Node-only
* `require` breaks eval with **`__require.addon is not a function`**.
*
* Order: if existing `require` already has **`addon`**, use it; else install **`createRequire`**
* from the booter **`file:`** package.json when available; else **`createRequire(import.meta.url)`**
* (covers **`pear://`** on Bare). If **`addon`** is still missing (e.g. Node dev), attach a stub
* for the duration of eval only.
*
* @param {() => void | Promise<void>} fn
*/
async function withDriveBundleGlobalRequire(fn) {
if (typeof globalThis.require === 'function') return fn()
async function tryBareModuleCreateRequire(parentURL) {
try {
const bm = await import('bare-module')
const createRequire = bm.createRequire || bm.default?.createRequire
if (typeof createRequire !== 'function') return null
return createRequire(parentURL)
} catch {
return null
}
}
/**
* Pear `import('pkg')` can fail when the current referrer is a `pear://` booter URL.
* Try loading via bare-module's createRequire from the booter package root first,
* then fall back to `createRequire(import.meta.url)`.
*
* @param {string} specifier
* @returns {Promise<unknown>}
*/
export async function tryRequireFromBooter(specifier) {
if (typeof specifier !== 'string' || !specifier.trim()) return undefined
const liveRequire = globalThis.require
if (typeof liveRequire === 'function') {
try {
return liveRequire(specifier)
} catch {
/* continue */
}
}
const pkgJson = bareOsBooterPackageJsonPathForCreateRequire()
let installed = false
if (pkgJson) {
try {
const urlMod = await import('url')
const bm = await import('bare-module')
const createRequire = bm.createRequire || bm.default?.createRequire
if (typeof createRequire === 'function') {
const href = urlMod.pathToFileURL(pkgJson).href
globalThis.require = createRequire(href)
installed = true
}
const href = urlMod.pathToFileURL(pkgJson).href
const req = await tryBareModuleCreateRequire(href)
if (typeof req === 'function') return req(specifier)
} catch {
/* bare-module createRequire unavailable (e.g. some pear:// trees) */
/* continue */
}
}
try {
const req = await tryBareModuleCreateRequire(import.meta.url)
if (typeof req === 'function') return req(specifier)
} catch {
/* continue */
}
return undefined
}
/** @returns {import('bare-module').Require['addon']} */
function driveBundleRequireAddonStub() {
const stub = function bareOsDriveBundleAddon() {
return new Proxy(
{},
{
get() {
return function bareOsDriveBundleAddonExport() {
return {}
}
}
}
)
}
stub.resolve = function bareOsDriveBundleAddonResolve() {
return ''
}
stub.host = ''
return stub
}
/**
* Install a temporary `require` wrapper for drive-bundle eval so we control both
* module resolution and the `require.addon` surface seen by esbuild IIFEs.
*
* @param {Function} delegate
* @param {Function} addon
*/
function createDriveBundleRequireWrapper(delegate, addon) {
const wrapped = function bareOsDriveBundleRequire(...args) {
return Reflect.apply(delegate, this, args)
}
for (const key of ['resolve', 'cache', 'extensions', 'main']) {
try {
const v = delegate[key]
if (v !== undefined) wrapped[key] = v
} catch {
/* ignore */
}
}
wrapped.addon = addon
return wrapped
}
async function withDriveBundleGlobalRequire(fn) {
const origRequire = globalThis.require
/** @type {Function | null} */
let delegate =
typeof origRequire === 'function' ? /** @type {Function} */ (origRequire) : null
if (!delegate) {
const pkgJson = bareOsBooterPackageJsonPathForCreateRequire()
if (pkgJson) {
try {
const urlMod = await import('url')
const href = urlMod.pathToFileURL(pkgJson).href
const req = await tryBareModuleCreateRequire(href)
if (typeof req === 'function') delegate = req
} catch {
/* continue */
}
}
}
if (!delegate) {
const req = await tryBareModuleCreateRequire(import.meta.url)
if (typeof req === 'function') delegate = req
}
if (typeof delegate === 'function') {
/** @type {Function | null} */
let addon =
typeof delegate.addon === 'function' ? /** @type {Function} */ (delegate.addon) : null
if (!addon) {
const addonReq = await tryBareModuleCreateRequire(import.meta.url)
if (addonReq && typeof addonReq.addon === 'function') addon = addonReq.addon
}
if (!addon) addon = driveBundleRequireAddonStub()
globalThis.require = createDriveBundleRequireWrapper(delegate, addon)
}
try {
return await fn()
} finally {
if (installed) {
delete globalThis.require
}
if (origRequire === undefined) delete globalThis.require
else globalThis.require = origRequire
}
}
@@ -154,12 +296,34 @@ function bareHostRuntime() {
export async function buildBareCtxObjectFromHost(shellEnv, target) {
if (!bareOsBareModulesEnabled(shellEnv)) return
if (!bareOsBareHostImportsEnabled(shellEnv)) return
const pearBooter =
typeof import.meta !== 'undefined' &&
String(import.meta.url || '').startsWith('pear:')
const { entries } = loadBareModuleManifest()
const skipHostKeys = new Set(
String(shellEnv?.BARE_OS_BARE_HOST_SKIP_CTX_KEYS || '')
.split(/[\s,]+/)
.map((s) => s.trim())
.filter(Boolean)
)
const onlyHostKeysRaw = String(
shellEnv?.BARE_OS_BARE_HOST_ONLY_CTX_KEYS || ''
)
.split(/[\s,]+/)
.map((s) => s.trim())
.filter(Boolean)
const onlyHostKeys =
onlyHostKeysRaw.length > 0 ? new Set(onlyHostKeysRaw) : null
const onBare = bareHostRuntime()
const tasks = entries.map(async (ent) => {
if (!onBare && ent.nativeHint === true) return null
const key = ent.ctxKey
if (!key || target[key] !== undefined) return null
if (skipHostKeys.has(key)) return null
if (onlyHostKeys && !onlyHostKeys.has(key)) return null
// Pear (`pear:` import.meta.url): bare-module cannot resolve `import("holesail")` from
// this booter URL (MODULE_NOT_FOUND / no referrer). Drive IIFEs supply bundle:true keys.
if (pearBooter && ent.bundle === true) return null
try {
const mod = await import(/* webpackIgnore: true */ ent.package)
const val = ent.sideEffectImport
@@ -253,6 +417,74 @@ function unwrapDriveBundleExport(val) {
return val
}
/**
* Eval a single `/lib/bare/bundles/*.js` IIFE (same as {@link maybeMergeBareFromDrive}) and
* return one `ctxKey` from `globalThis.__bare_os_stdlib__`. Does not mutate `ctx.bare`.
* Used when Pear skipped populating a key (merge short-circuit or eval failure).
*
* @param {Record<string, unknown>} ctx
* @param {{ readFile: (p: string) => Promise<Uint8Array | null> }} vfs
* @param {string} logicalPath
* @param {string} ctxKey
* @returns {Promise<unknown>}
*/
export async function tryLoadBareCtxKeyFromDriveBundlePath(
ctx,
vfs,
logicalPath,
ctxKey
) {
void ctx
if (!vfs || typeof vfs.readFile !== 'function') return undefined
if (
typeof logicalPath !== 'string' ||
!logicalPath.startsWith('/lib/bare/') ||
typeof ctxKey !== 'string' ||
!ctxKey
) {
return undefined
}
let buf
try {
buf = await vfs.readFile(logicalPath)
} catch {
return undefined
}
if (!buf || !buf.byteLength) return undefined
const text = decodeUtf8BytesBareFallback(buf)
if (text.length > 12 * 1024 * 1024) return undefined
const g = globalThis
const sym = BARE_OS_STDLIB_GLOBAL
/** @type {string | null} */
let errMsg = null
await withDriveBundleGlobalRequire(async () => {
g[sym] = g[sym] && typeof g[sym] === 'object' ? g[sym] : {}
try {
const run = new Function(
`"use strict"; var require = globalThis.require;\n${text}\n//# sourceURL=bare-drive-bundle:${logicalPath}`
)
run()
} catch (err) {
errMsg = (err && /** @type {{ message?: string }} */ (err).message) || String(err)
}
})
if (errMsg) {
bareOsHostBooterWarn(
'drive_bundle_eval_failed',
`tryLoadBareCtxKeyFromDriveBundlePath ${logicalPath}`,
errMsg
)
return undefined
}
const snap = g[sym]
if (!snap || typeof snap !== 'object') return undefined
if (!Object.prototype.hasOwnProperty.call(snap, ctxKey)) return undefined
return unwrapDriveBundleExport(snap[ctxKey])
}
export async function maybeMergeBareFromDrive(shellEnv, vfs, target) {
if (!bareOsBareModulesEnabled(shellEnv)) return
if (!bareOsBareDriveBundlesEnabled(shellEnv)) return
@@ -354,7 +586,7 @@ export async function maybeMergeBareFromDrive(shellEnv, vfs, target) {
try {
const run = new Function(
`"use strict"; ${source}\n//# sourceURL=bare-drive-bundle:${path}`
`"use strict"; var require = globalThis.require;\n${source}\n//# sourceURL=bare-drive-bundle:${path}`
)
run()
} catch (err) {
+1
View File
@@ -232,6 +232,7 @@ export interface BareOsKernelContext {
bareOsRunSshKeygenCli?(argv: string[]): Promise<unknown>
bareOsRunTarCli?(argv: string[]): Promise<unknown>
bareOsRunSystemctlCli?(argv: string[]): Promise<unknown>
bareOsRunHolesailCli?(argv: string[]): Promise<unknown>
bareOsRegisterBareDiagnosticsTap?(
fn: (ev: Record<string, unknown>) => void
): () => void
@@ -26,6 +26,7 @@ const README_TEXT = `Bare OS session logs (mirrored on your personal drive under
kernel-console.log — console.log / console.error from the kernel session
cron.log — bare-cron job errors
openssh.log — bare-openssh / sshd listen and auth errors
holesail.log — bare-holesail / early kernel-path Holesail (AGPL-3.0 upstream)
initd.log — bare-initd service start failures
audit.log — optional execLine audit when BARE_OS_AUDIT=1
`
+282
View File
@@ -0,0 +1,282 @@
/**
* Drive-resident /bin/holesail → ctx.bareOsRunHolesailCli (booter).
* Manages persisted tunnels under BARE_OS_HOLESAIL_STATE (default ~/.holesail/state.json).
* Live start/stop uses the bare-holesail initd unit (managed mode is the stock default).
*/
import {
bareHolesailManagedNormalizeEntry,
bareHolesailManagedReadState,
bareHolesailManagedRuntimeRunning,
bareHolesailManagedRuntimeUrl,
bareHolesailManagedServiceIsRunning,
bareHolesailManagedStartOne,
bareHolesailManagedStatePath,
bareHolesailManagedStopOne,
bareHolesailManagedValidateId,
bareHolesailManagedWriteState
} from './bare-holesail-managed.js'
/**
* @param {Record<string, unknown>} ctx
*/
function ctxEnv(ctx) {
return ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string | undefined>} */ (ctx.env)
: /** @type {Record<string, string | undefined>} */ ({})
}
function printHelp(ctx, prog) {
ctx.console.log(
`Usage: ${prog} help|path|list
${prog} add ID --server|--client [--key KEY] [--port N] [--host H] [--udp] [--secure] [--no-secure] [--log N|true|false]
${prog} remove ID
${prog} start ID | stop ID | restart ID
${prog} enable ID | disable ID
State file: BARE_OS_HOLESAIL_STATE or ${bareHolesailManagedStatePath({})}
Stock boot enables bare-holesail in managed mode (BARE_OS_HOLESAIL_INITD/MANAGED default on).
Disable with BARE_OS_HOLESAIL_INITD=0 or systemctl disable bare-holesail.
Upstream holesail is AGPL-3.0; see handbook/04-the-booter-runtime.md.`
)
}
/**
* @param {string[]} flagArgs tokens after ID for "add"
* @returns {{ ok: true, id: string, entry: Record<string, unknown> } | { ok: false, err: string }}
*/
/**
* Accept `hs://…` without `--key` (otherwise parsed as an unknown flag).
* @param {string} a
*/
function holesailCliTokenLooksLikeHolesailConnectionString(a) {
return typeof a === 'string' && /^hs:\/\//i.test(a)
}
export function holesailCliParseAdd(flagArgs) {
if (flagArgs.length < 1) {
return { ok: false, err: 'holesail add: missing ID' }
}
const idOk = bareHolesailManagedValidateId(flagArgs[0])
if (!idOk.ok) return { ok: false, err: `holesail add: ${idOk.err}` }
/** @type {Record<string, unknown>} */
const raw = {}
for (let i = 1; i < flagArgs.length; i++) {
const a = flagArgs[i]
if (a === '--server') raw.server = true
else if (a === '--client') raw.client = true
else if (holesailCliTokenLooksLikeHolesailConnectionString(a)) {
if (raw.key) {
return { ok: false, err: 'holesail add: duplicate key' }
}
raw.key = a
} else if (a === '--key' && flagArgs[i + 1]) raw.key = flagArgs[++i]
else if (a === '--port' && flagArgs[i + 1]) raw.port = flagArgs[++i]
else if (a === '--host' && flagArgs[i + 1]) raw.host = flagArgs[++i]
else if (a === '--udp') raw.udp = true
else if (a === '--secure') raw.secure = true
else if (a === '--no-secure') raw.secure = false
else if (a === '--log' && flagArgs[i + 1]) {
const v = flagArgs[++i]
if (v === 'true' || v === 'false') raw.log = v
else raw.log = v
} else {
return { ok: false, err: `holesail add: unknown or incomplete flag: ${a}` }
}
}
const norm = bareHolesailManagedNormalizeEntry(raw)
if (!norm.ok) return { ok: false, err: `holesail add: ${norm.err}` }
return { ok: true, id: idOk.id, entry: norm.entry }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} argv argv[0] is holesail
*/
export async function runHolesailCli(ctx, argv) {
const env = ctxEnv(ctx)
const args = argv.slice(1)
const prog = 'holesail'
if (
args.length === 0 ||
args[0] === 'help' ||
args[0] === '--help' ||
args[0] === '-h'
) {
printHelp(ctx, prog)
ctx.exitCode = 0
return
}
const sub = args[0]
const rest = args.slice(1)
if (sub === 'path') {
ctx.console.log(bareHolesailManagedStatePath(env))
ctx.exitCode = 0
return
}
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function') {
ctx.console.error('holesail: ctx.vfs read/write unavailable')
ctx.exitCode = 1
return
}
if (!ctx.b4a || typeof ctx.b4a.from !== 'function') {
ctx.console.error('holesail: ctx.b4a.from unavailable')
ctx.exitCode = 1
return
}
try {
if (sub === 'list') {
const { path, state } = await bareHolesailManagedReadState(ctx, env)
const daemon = bareHolesailManagedServiceIsRunning()
ctx.console.log(`state: ${path}`)
ctx.console.log(`daemon: ${daemon ? 'managed bare-holesail running' : 'no managed daemon (live ops limited)'}`)
const ids = Object.keys(state.connections).sort()
if (ids.length === 0) {
ctx.console.log('(no connections)')
ctx.exitCode = 0
return
}
for (const id of ids) {
const raw = state.connections[id]
const norm = bareHolesailManagedNormalizeEntry(raw)
const en = norm.ok ? String(!!norm.entry.enabled) : '?'
const live = bareHolesailManagedRuntimeRunning(id)
const url = live ? bareHolesailManagedRuntimeUrl(id) : ''
const mode =
norm.ok && norm.entry.server ? 'server' : norm.ok && norm.entry.client ? 'client' : '?'
const err = norm.ok ? '' : ` INVALID: ${norm.err}`
ctx.console.log(
`${id}\t${mode}\tenabled=${en}\tlive=${live}${url ? `\turl=${url.slice(0, 80)}` : ''}${err}`
)
}
ctx.exitCode = 0
return
}
if (sub === 'add') {
const parsed = holesailCliParseAdd(rest)
if (!parsed.ok) {
ctx.console.error(parsed.err)
ctx.exitCode = 2
return
}
const { path, state } = await bareHolesailManagedReadState(ctx, env)
if (state.connections[parsed.id]) {
ctx.console.error(`holesail add: ${parsed.id} already exists (use remove first)`)
ctx.exitCode = 1
return
}
state.connections[parsed.id] = parsed.entry
await bareHolesailManagedWriteState(ctx, env, state)
ctx.console.log(`wrote ${path}`)
if (bareHolesailManagedServiceIsRunning()) {
try {
await bareHolesailManagedStartOne(ctx, env, parsed.id)
ctx.console.log(`started ${parsed.id}`)
} catch (e) {
const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
ctx.console.error(`holesail: persisted but start failed: ${msg}`)
ctx.exitCode = 1
return
}
}
ctx.exitCode = 0
return
}
if (sub === 'remove') {
const idOk = bareHolesailManagedValidateId(rest[0] || '')
if (!idOk.ok) {
ctx.console.error(`holesail remove: ${idOk.err}`)
ctx.exitCode = 2
return
}
const { path, state } = await bareHolesailManagedReadState(ctx, env)
if (!state.connections[idOk.id]) {
ctx.console.error(`holesail remove: unknown ${idOk.id}`)
ctx.exitCode = 1
return
}
if (bareHolesailManagedRuntimeRunning(idOk.id)) {
await bareHolesailManagedStopOne(ctx, idOk.id)
}
delete state.connections[idOk.id]
await bareHolesailManagedWriteState(ctx, env, state)
ctx.console.log(`removed ${idOk.id} (${path})`)
ctx.exitCode = 0
return
}
if (sub === 'start' || sub === 'stop' || sub === 'restart') {
const idOk = bareHolesailManagedValidateId(rest[0] || '')
if (!idOk.ok) {
ctx.console.error(`holesail ${sub}: ${idOk.err}`)
ctx.exitCode = 2
return
}
if (sub === 'restart' || sub === 'stop') {
if (bareHolesailManagedRuntimeRunning(idOk.id)) {
await bareHolesailManagedStopOne(ctx, idOk.id)
}
}
if (sub === 'stop') {
ctx.console.log(`stopped ${idOk.id} (still in state; will restart on boot if enabled)`)
ctx.exitCode = 0
return
}
await bareHolesailManagedStartOne(ctx, env, idOk.id)
ctx.console.log(`started ${idOk.id}`)
ctx.exitCode = 0
return
}
if (sub === 'enable' || sub === 'disable') {
const idOk = bareHolesailManagedValidateId(rest[0] || '')
if (!idOk.ok) {
ctx.console.error(`holesail ${sub}: ${idOk.err}`)
ctx.exitCode = 2
return
}
const { path, state } = await bareHolesailManagedReadState(ctx, env)
const raw = state.connections[idOk.id]
if (!raw || typeof raw !== 'object') {
ctx.console.error(`holesail ${sub}: unknown ${idOk.id}`)
ctx.exitCode = 1
return
}
const o = /** @type {Record<string, unknown>} */ (raw)
o.enabled = sub === 'enable'
await bareHolesailManagedWriteState(ctx, env, state)
ctx.console.log(`${sub}d ${idOk.id} (${path})`)
if (sub === 'disable' && bareHolesailManagedRuntimeRunning(idOk.id)) {
await bareHolesailManagedStopOne(ctx, idOk.id)
}
if (sub === 'enable' && bareHolesailManagedServiceIsRunning()) {
try {
await bareHolesailManagedStartOne(ctx, env, idOk.id)
} catch (e) {
const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
ctx.console.error(`holesail: enable saved but start failed: ${msg}`)
ctx.exitCode = 1
return
}
}
ctx.exitCode = 0
return
}
ctx.console.error(`holesail: unknown command ${sub} (try ${prog} help)`)
ctx.exitCode = 2
} catch (e) {
const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
ctx.console.error(`holesail: ${msg}`)
ctx.exitCode = 1
}
}
+4 -1
View File
@@ -8,7 +8,7 @@
"start": "node ../../scripts/ensure-pear-node-modules.mjs packages/bare-os-booter && bare index.js",
"dev": "node ../../scripts/ensure-pear-node-modules.mjs packages/bare-os-booter && bare index.js",
"pear:dev": "node ../../scripts/ensure-pear-node-modules.mjs packages/bare-os-booter && pear run --dev .",
"test": "../../node_modules/.bin/brittle-bare test.identity.js && node --require ./scripts/bare-node-test-shim.cjs ../../node_modules/.bin/brittle-node test.js test.bare-openssh-pty.js test.hrpc-allowlist.js test.socket-scm-rights.js",
"test": "../../node_modules/.bin/brittle-bare test.identity.js && node --require ./scripts/bare-node-test-shim.cjs ../../node_modules/.bin/brittle-node test.js test.bare-holesail.js test.bare-holesail-managed.js test.bare-openssh-pty.js test.hrpc-allowlist.js test.socket-scm-rights.js",
"test:bare": "../../node_modules/.bin/brittle-bare test.identity.js test.bare-smoke.js"
},
"dependencies": {
@@ -44,6 +44,7 @@
"corestore": "^7.2.1",
"hypercore-id-encoding": "^1.3.0",
"hyperdrive": "^13.3.2",
"holesail": "^2.4.1",
"hyperbee": "^2.11.1",
"hypercore-crypto": "^3.4.0",
"hyperswarm": "^4.17.0",
@@ -74,6 +75,8 @@
"test.js",
"test.identity.js",
"test.bare-smoke.js",
"test.bare-holesail.js",
"test.bare-holesail-managed.js",
".test-data"
]
}
@@ -1,12 +1,25 @@
/**
* Preload for `brittle-node` / Node-based booter tests:
* - `bare-type/binding.js` uses Bare-only `require.addon()`.
* - `bare-dns/binding.js` uses Bare-only `require.addon()` (transitive via holesail / DHT).
* - `bare-inspect` pulls the same; brittle → bare-assert → bare-inspect.
*
* Usage: node --require ./scripts/bare-node-test-shim.cjs …
*/
'use strict'
/** `bare-path` and some Holepunch modules expect a global `Bare` (Pear/Bare runtime). */
if (typeof globalThis.Bare === 'undefined') {
globalThis.Bare = {
platform: process.platform,
on(event, fn) {
if (event === 'exit' && typeof fn === 'function' && process.on) {
process.on('exit', fn)
}
}
}
}
const path = require('path')
const Module = require('module')
const { types } = require('node:util')
@@ -170,6 +183,25 @@ module.exports = function inspect(value, opts = {}) {
module.exports.styles = {}
`
/** Minimal stub so `require('bare-dns')` loads under Node (no native addon). */
const bareDnsBindingStub = {
initResolver() {
return 0
},
destroyResolver() {},
resolveTxt(_handle, _hostname, cb) {
if (typeof cb === 'function') {
process.nextTick(() => cb(new Error('bare-dns: stub binding in Node tests')))
}
},
lookup(_hostname, _family, _all, _req, callback) {
if (typeof callback === 'function') {
process.nextTick(() => callback(new Error('bare-dns: stub binding in Node tests'), null))
}
return null
}
}
const origLoad = Module._load
Module._load = function (request, parent, isMain) {
if (
@@ -181,13 +213,65 @@ Module._load = function (request, parent, isMain) {
) {
return bareTypeBinding
}
if (
request === './binding' &&
parent &&
typeof parent.filename === 'string' &&
parent.filename.includes(`${path.sep}bare-dns${path.sep}`) &&
parent.filename.endsWith(`${path.sep}index.js`)
) {
return bareDnsBindingStub
}
return origLoad.apply(this, arguments)
}
const ADDON_STUB_PREFIX =
'if (typeof require.addon !== "function") {\n' +
' require.addon = function bareOsNodeTestAddon() {\n' +
' return new Proxy({}, {\n' +
' get() {\n' +
' return function () { return {} }\n' +
' }\n' +
' })\n' +
' }\n' +
'}\n'
function isBareOsBindingFile(filename) {
if (typeof filename !== 'string') return false
const n = filename.replace(/\\/g, '/')
return n.endsWith('/bare-os/binding.js')
}
function isBareAddonBindingFile(filename, src) {
if (typeof filename !== 'string') return false
if (isBareOsBindingFile(filename)) return false
if (!filename.includes(`${path.sep}node_modules${path.sep}`)) return false
if (!filename.endsWith(`${path.sep}binding.js`)) return false
const s = String(src || '')
.replace(/\/\/[^\n]*/g, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.trim()
return /^\s*module\.exports\s*=\s*require\.addon\s*\(\s*\)\s*;?\s*$/.test(s)
}
const bareOsBindingNodeShimPath = path.join(
__dirname,
'bare-os-binding-node-shim.cjs'
)
const origCompile = Module.prototype._compile
Module.prototype._compile = function (content, filename) {
if (typeof filename === 'string' && isBareInspectIndex(filename)) {
return origCompile.call(this, BARE_INSPECT_NODE_SOURCE, filename)
}
if (isBareOsBindingFile(filename)) {
const src = `module.exports = require(${JSON.stringify(
bareOsBindingNodeShimPath
)})\n`
return origCompile.call(this, src, filename)
}
if (isBareAddonBindingFile(filename, content)) {
return origCompile.call(this, ADDON_STUB_PREFIX + content, filename)
}
return origCompile.call(this, content, filename)
}
@@ -0,0 +1,76 @@
/**
* Node test shim: bare-os `binding.js` uses `require.addon()` (Bare-only).
* Provides a minimal process/os-shaped object for brittle-node tests.
*/
'use strict'
const os = require('node:os')
const p = process
function networkInterfacesFlat() {
const ni = os.networkInterfaces()
const out = []
for (const name of Object.keys(ni)) {
const addrs = ni[name]
if (!addrs) continue
for (const a of addrs) out.push({ name, ...a })
}
return out
}
module.exports = {
signals: os.constants.signals,
errnos: os.constants.errno || {},
priority: os.constants.priority || {},
platform: p.platform,
arch: os.arch(),
type: os.type(),
version: os.version(),
release: os.release(),
machine: os.machine(),
execPath: p.execPath,
pid: () => p.pid,
ppid: () => p.ppid,
cwd: () => p.cwd(),
chdir: (d) => p.chdir(d),
tmpdir: () => os.tmpdir(),
homedir: () => os.homedir(),
hostname: () => os.hostname(),
userInfo: (opts) => os.userInfo(opts),
networkInterfaces: () => networkInterfacesFlat(),
kill: (pid, sig) => p.kill(pid, sig),
isLittleEndian: os.endianness() === 'LE',
availableParallelism: () =>
typeof os.availableParallelism === 'function'
? os.availableParallelism()
: os.cpus().length,
cpuUsage: () => p.cpuUsage(),
threadCpuUsage: () => ({ user: 0, system: 0 }),
resourceUsage: () => p.resourceUsage(),
memoryUsage: () => p.memoryUsage(),
freemem: () => os.freemem(),
totalmem: () => os.totalmem(),
availableMemory: () => os.freemem(),
constrainedMemory: () => os.totalmem(),
uptime: () => os.uptime(),
loadavg: () => os.loadavg(),
cpus: () => os.cpus(),
getProcessTitle: () => p.title,
setProcessTitle: (t) => {
p.title = t
},
getPriority: (pid) => p.getPriority(pid),
setPriority: (pid, pri) => p.setPriority(pid, pri),
getEnvKeys: () => Object.keys(p.env),
getEnv: (k) => {
const v = p.env[String(k)]
return v === undefined ? '' : String(v)
},
hasEnv: (k) => Object.prototype.hasOwnProperty.call(p.env, String(k)),
setEnv: (k, v) => {
p.env[String(k)] = String(v)
},
unsetEnv: (k) => {
delete p.env[String(k)]
}
}
@@ -0,0 +1,48 @@
import test from 'brittle'
import {
bareHolesailManagedNormalizeEntry,
bareHolesailManagedValidateId
} from './lib/bare-holesail-managed.js'
import { holesailCliParseAdd } from './lib/holesail-cli.js'
test('bareHolesailManagedValidateId', (t) => {
t.ok(bareHolesailManagedValidateId('a').ok)
t.ok(bareHolesailManagedValidateId('t1._-x').ok)
t.absent(bareHolesailManagedValidateId('').ok)
t.absent(bareHolesailManagedValidateId('bad id').ok)
})
test('bareHolesailManagedNormalizeEntry server', (t) => {
const r = bareHolesailManagedNormalizeEntry({ server: true, port: '8080' })
t.ok(r.ok)
t.is(r.entry.server, true)
t.is(r.entry.client, false)
t.is(r.entry.port, 8080)
})
test('bareHolesailManagedNormalizeEntry client needs key', (t) => {
const r = bareHolesailManagedNormalizeEntry({ client: true })
t.absent(r.ok)
})
test('holesailCliParseAdd', (t) => {
const r = holesailCliParseAdd(['t', '--server', '--port', '9000'])
t.ok(r.ok)
t.is(r.id, 't')
t.is(r.entry.server, true)
t.is(r.entry.port, 9000)
})
test('holesailCliParseAdd implicit hs key', (t) => {
const r = holesailCliParseAdd([
't',
'--client',
'hs://s000477b2d983364922df1296a65aeaa2d2a',
'--port',
'8888'
])
t.ok(r.ok)
t.is(r.entry.client, true)
t.is(r.entry.key, 'hs://s000477b2d983364922df1296a65aeaa2d2a')
t.is(r.entry.port, 8888)
})
@@ -0,0 +1,116 @@
import test from 'brittle'
import {
bareHolesailEnvTruthy,
bareHolesailParseConfig
} from './lib/bare-holesail.js'
import { loadHolesailConstructor } from './lib/bare-holesail-loader.js'
import { tryLoadBareCtxKeyFromDriveBundlePath } from './lib/bare-os-ctx-bare.js'
test('bareHolesailEnvTruthy', (t) => {
t.ok(bareHolesailEnvTruthy('1'))
t.ok(bareHolesailEnvTruthy('true'))
t.ok(bareHolesailEnvTruthy(' YES '))
t.absent(bareHolesailEnvTruthy('0'))
t.absent(bareHolesailEnvTruthy(''))
})
test('bareHolesailParseConfig initd disabled', (t) => {
const r = bareHolesailParseConfig({}, 'initd')
t.absent(r.enabled)
})
test('bareHolesailParseConfig initd server secure', (t) => {
const r = bareHolesailParseConfig(
{
BARE_OS_HOLESAIL_INITD: '1',
BARE_OS_HOLESAIL_SERVER: '1',
BARE_OS_HOLESAIL_SECURE: '1',
BARE_OS_HOLESAIL_PORT: '18080'
},
'initd'
)
t.ok(r.enabled)
t.absent(r.err)
t.ok(r.opts)
t.is(r.opts.server, true)
t.is(r.opts.client, false)
t.is(r.opts.secure, true)
t.is(r.opts.port, 18080)
})
test('bareHolesailParseConfig initd client needs key', (t) => {
const r = bareHolesailParseConfig(
{
BARE_OS_HOLESAIL_INITD: '1',
BARE_OS_HOLESAIL_CLIENT: '1'
},
'initd'
)
t.ok(r.enabled)
t.ok(r.err)
})
test('bareHolesailParseConfig kernel profile uses KERNEL_ prefix', (t) => {
const r = bareHolesailParseConfig(
{
BARE_OS_HOLESAIL_KERNEL: '1',
BARE_OS_HOLESAIL_KERNEL_SERVER: '1'
},
'kernel'
)
t.ok(r.enabled)
t.absent(r.err)
t.ok(r.opts?.server)
})
test('bareHolesailParseConfig rejects both server and client', (t) => {
const r = bareHolesailParseConfig(
{
BARE_OS_HOLESAIL_INITD: '1',
BARE_OS_HOLESAIL_SERVER: '1',
BARE_OS_HOLESAIL_CLIENT: '1'
},
'initd'
)
t.ok(r.err)
})
test('loadHolesailConstructor uses booter-owned holesail import', async (t) => {
const ctx = { bare: Object.freeze({}) }
const Ho = await loadHolesailConstructor(ctx)
t.is(typeof Ho, 'function')
t.is(Object.prototype.hasOwnProperty.call(ctx.bare, 'holesail'), false)
})
test('drive bundle eval wraps non-addon require with addon-capable facade', async (t) => {
const prevRequire = globalThis.require
const fakeRequire = Object.preventExtensions(function fakeRequire() {
throw new Error('unexpected require() call in test bundle')
})
globalThis.require = fakeRequire
const bundleSource =
'var __require = ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { throw Error(\'Dynamic require of "\' + x + \'" is not supported\'); });\n' +
'var __bare_os_bundle_exports__ = { default: typeof __require.addon === "function" };\n' +
';(function(){var g=globalThis;var s="__bare_os_stdlib__";g[s]=g[s]||{};var e=typeof __bare_os_bundle_exports__!=="undefined"?__bare_os_bundle_exports__:void 0;var v=e!=null&&typeof e==="object"&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e;g[s]["driveBundleProbe"]=v;})();\n'
try {
const loaded = await tryLoadBareCtxKeyFromDriveBundlePath(
{},
{
async readFile(path) {
return path === '/lib/bare/bundles/driveBundleProbe.js'
? Buffer.from(bundleSource)
: null
}
},
'/lib/bare/bundles/driveBundleProbe.js',
'driveBundleProbe'
)
t.is(loaded, true)
} finally {
if (prevRequire === undefined) delete globalThis.require
else globalThis.require = prevRequire
}
})
+8 -1
View File
@@ -3376,7 +3376,14 @@ test('bare-module-manifest.data.mjs matches bare-module-manifest.json', async (t
test('buildBareCtxObjectFromHost loads core keys on Node', async (t) => {
const target = {}
await buildBareCtxObjectFromHost({}, target)
await buildBareCtxObjectFromHost(
{
// Narrow host import set: parallel import of the full manifest is slow and
// some Bare-oriented packages disturb brittle's hrtime-based timers.
BARE_OS_BARE_HOST_ONLY_CTX_KEYS: 'b4a,compactEncoding,protomux'
},
target
)
t.ok(target.b4a)
t.ok(target.compactEncoding)
t.ok(target.protomux)