This commit is contained in:
2026-08-18 18:11:28 -04:00
parent 0e9a650fa2
commit bbaf47028f
259 changed files with 0 additions and 0 deletions
@@ -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,928 @@
import { dirname } from '#host-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`** override, else **`~/.holesail/state.json`** (resolves under **`$HOME`**).
* Managed Holesail starts only after unlock; legacy **`/.bare/holesail/**`** files may be merged on first read.
*
* **Server identity** — **`seed`** is the **64-hex** ctor secret (`bare-crypto`); it is passed to **`Holesail({ key })`**
* and **must not** be replaced with the z32 URL suffix (different input ⇒ different tunnel). **`key`** holds the full
* **`hs://…`** from **`hs.info.url`** after **`ready()`** for sharing. Legacy rows may have only **`key`** (no **`seed`**).
* Upstream holesail is AGPL-3.0.
*/
import { randomBytes } from 'bare-crypto'
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'
import { bindFallbackEnabled, isAddrInUse } from './bare-os-bind-fallback.js'
/** Per-user managed state (unlocked shells; resolves under `$HOME`). */
export const BARE_HOLESAIL_STATE_USER = '~/.holesail/state.json'
/** Legacy guest-only managed state (migration source only). */
export const BARE_HOLESAIL_STATE_GUEST = '/.bare/holesail/guest/state.json'
/** Pre-split single file at personal root (migration source only). */
export const BARE_HOLESAIL_STATE_LEGACY_ROOT = '/.bare/holesail/state.json'
/** @deprecated Use {@link BARE_HOLESAIL_STATE_LEGACY_ROOT}. */
export const BARE_HOLESAIL_STATE_STABLE = BARE_HOLESAIL_STATE_LEGACY_ROOT
/** @deprecated Alias for {@link BARE_HOLESAIL_STATE_USER}. */
export const BARE_HOLESAIL_STATE_LEGACY = BARE_HOLESAIL_STATE_USER
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()
if (raw) return raw
return BARE_HOLESAIL_STATE_USER
}
/**
* @param {Record<string, string | undefined>} env
* @param {string} [_id]
*/
export function bareHolesailManagedEnvForConnectionId(env, _id) {
return env
}
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 }
}
/** 32 bytes hex — upstream holesail expects `key` length ≥ 32 chars for servers. */
const HOLESAIL_SEED_HEX_LEN = 64
/**
* New server tunnel secret (persist in **`state.connections[id].seed`**; passed to **`Holesail({ key })`**).
*/
export function bareHolesailManagedGenerateSeedHex() {
return randomBytes(HOLESAIL_SEED_HEX_LEN / 2).toString('hex')
}
/**
* @param {string} s
*/
export function bareHolesailManagedSeedLooksValid(s) {
return /^[0-9a-f]{64}$/i.test(String(s || '').trim())
}
/**
* Extract holesail **`key`** material from a full **`hs://…`** URL or bare connection string (matches upstream **`urlParser`**).
* @param {string} urlOrHs
*/
export function bareHolesailManagedHsUrlKeySuffix(urlOrHs) {
const u = String(urlOrHs || '').trim()
if (u.length < 10) return ''
if (u.substring(0, 5).toLowerCase() !== 'hs://') return ''
return u.substring(9)
}
/**
* Valid persisted **`seed`**: 64-char hex (pre-mint) or holesail **z32-style** suffix (≥32 chars, alphanumeric).
* @param {string} s
*/
export function bareHolesailManagedPersistentSeedLooksValid(s) {
const t = String(s || '').trim()
if (!t) return false
if (/^[0-9a-f]{64}$/i.test(t)) return true
if (t.length >= 32 && t.length <= 128 && /^[a-z0-9]+$/i.test(t)) return true
return false
}
/**
* @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()
const seed = String(raw.seed ?? '').trim()
if (client && !key) return { ok: false, err: 'client requires key' }
if (seed) {
if (!bareHolesailManagedPersistentSeedLooksValid(seed))
return {
ok: false,
err: 'invalid seed (64 hex chars or hs:// key suffix / z32)'
}
entry.seed = seed.toLowerCase()
}
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 }
}
/**
* Compare persisted shareable URL / key string to live `hs.info.url` (case-insensitive trim).
* @param {string} persisted
* @param {string} live
*/
export function bareHolesailManagedPersistedUrlMatchesLive(persisted, live) {
const p = String(persisted ?? '').trim().toLowerCase()
const l = String(live ?? '').trim().toLowerCase()
if (!p || !l) return false
return p === l
}
/**
* `log` for managed tunnels: row → `BARE_OS_HOLESAIL_MANAGED_LOG` / `BARE_OS_HOLESAIL_LOG` → INFO (1),
* or DEBUG (0) when `BARE_OS_HOLESAIL_DEBUG` is set (reference scripts often use `log: 0`).
* @param {Record<string, unknown>} conn
* @param {Record<string, string | undefined>} env
*/
export function bareHolesailManagedLogLevel(conn, env) {
if (conn.log !== undefined && conn.log !== null && conn.log !== '') {
if (typeof conn.log === 'number') return conn.log
if (/^\d+$/.test(String(conn.log)))
return Number.parseInt(String(conn.log), 10)
return bareHolesailEnvTruthy(conn.log) ? 1 : 0
}
const raw =
env.BARE_OS_HOLESAIL_MANAGED_LOG ?? env.BARE_OS_HOLESAIL_LOG ?? ''
const s = String(raw).trim()
if (s !== '' && /^\d+$/.test(s)) {
const n = Number.parseInt(s, 10)
return Number.isFinite(n) ? Math.max(0, Math.min(3, n)) : 1
}
if (bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_DEBUG)) return 0
return 1
}
/**
* Constructor options for `new Holesail(opts)`.
*
* **Server** — Ctor `key` field: **64-hex `seed`** first, then a valid **persisted non-hex seed** (z32 suffix migrated
* from **`hs://…`**), then the full **`hs://…`** **`key`** (legacy). Never replace a hex **`seed`** with only the URL
* suffix written as ctor material — upstream hashes the ctor string; hex ≠ z32 ⇒ different URLs.
*
* **Client** — Passthrough **`key`** (typically **`hs://…`**).
*
* @param {Record<string, unknown>} conn one `state.connections[id]` object
* @param {Record<string, string | undefined>} env
* @returns {Record<string, unknown>}
*/
export function bareHolesailManagedNewHolesailOpts(conn, env) {
const c = /** @type {Record<string, unknown>} */ (conn)
const seedStr = c.seed != null ? String(c.seed).trim() : ''
const keyFull = c.key != null ? String(c.key).trim() : ''
const server = bareHolesailEnvTruthy(c.server)
const client = bareHolesailEnvTruthy(c.client)
let secure = false
if (c.secure !== undefined && c.secure !== null && String(c.secure) !== '') {
secure = bareHolesailEnvTruthy(c.secure)
} else if (seedStr && bareHolesailManagedSeedLooksValid(seedStr)) {
secure = false
} else if (keyFull) {
secure = /^hs:\/\/s/i.test(keyFull)
}
let udp = false
if (c.udp !== undefined && c.udp !== null && String(c.udp) !== '')
udp = bareHolesailEnvTruthy(c.udp)
const log = bareHolesailManagedLogLevel(conn, env)
/** @type {string} */
let keyOpt = ''
if (server) {
if (bareHolesailManagedSeedLooksValid(seedStr)) keyOpt = seedStr.toLowerCase()
else if (
seedStr &&
bareHolesailManagedPersistentSeedLooksValid(seedStr)
)
keyOpt = seedStr.toLowerCase()
else if (/^hs:\/\//i.test(keyFull)) keyOpt = keyFull
else if (seedStr) keyOpt = seedStr.toLowerCase()
else keyOpt = keyFull
} else {
keyOpt = keyFull
}
/** @type {Record<string, unknown>} */
const opts = {
server,
client,
port: c.port,
host: c.host != null ? String(c.host) : undefined,
key: keyOpt,
secure,
udp
}
return opts
}
/**
* @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 {{ version: number, connections: Record<string, unknown> }} a
* @param {{ version: number, connections: Record<string, unknown> }} b
*/
function mergeCoercedHolesailStates(a, b) {
const conns = { ...a.connections }
for (const [id, raw] of Object.entries(b.connections || {})) {
if (!raw || typeof raw !== 'object') continue
const cur = conns[id]
if (!cur || typeof cur !== 'object') {
conns[id] = raw
continue
}
const c = /** @type {Record<string, unknown>} */ (cur)
const n = /** @type {Record<string, unknown>} */ (raw)
const ck = String(c.key ?? '').trim()
const nk = String(n.key ?? '').trim()
const cseed = String(c.seed ?? '').trim()
const nseed = String(n.seed ?? '').trim()
if (nk && !ck) conns[id] = { ...c, ...n, key: nk }
else if (ck && !nk) conns[id] = { ...n, ...c, key: ck }
else if (nseed && !cseed) conns[id] = { ...c, ...n, seed: nseed }
else if (cseed && !nseed) conns[id] = { ...n, ...c, seed: cseed }
else conns[id] = { ...c, ...n }
}
return { version: 1, connections: conns }
}
/**
* @param {Record<string, unknown>} ctx
* @param {{ readFile: (p: string) => Promise<unknown> }} vfs
* @param {string} logicalPath
*/
async function tryReadCoercedStateFile(ctx, vfs, logicalPath) {
try {
const buf = await vfs.readFile(logicalPath)
const text = ctx.b4a
? ctx.b4a.toString(buf, 'utf8')
: Buffer.from(buf).toString('utf8')
return coerceState(JSON.parse(text))
} catch {
return null
}
}
/**
* One-time upgrade when the primary state file is missing or empty: merge legacy sources
* (`/.bare/holesail/state.json`, old guest file, `/home/guest/.holesail/state.json`).
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {string} primaryPath
* @returns {Promise<{ version: number, connections: Record<string, unknown> } | null>}
*/
async function bareHolesailManagedTryMigrateLegacyState(ctx, env, primaryPath) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function' || typeof vfs.resolveLogical !== 'function')
return null
void primaryPath
/** @type {string[]} */
const paths = [
BARE_HOLESAIL_STATE_LEGACY_ROOT,
BARE_HOLESAIL_STATE_GUEST,
'/home/guest/.holesail/state.json'
]
/** @type {{ version: number, connections: Record<string, unknown> }} */
let merged = { version: 1, connections: {} }
const seen = new Set()
for (const p of paths) {
const norm = String(p || '').trim()
if (!norm || seen.has(norm)) continue
seen.add(norm)
const chunk = await tryReadCoercedStateFile(ctx, vfs, norm)
if (chunk && Object.keys(chunk.connections).length > 0) {
merged = mergeCoercedHolesailStates(merged, chunk)
}
}
if (Object.keys(merged.connections).length === 0) return null
return merged
}
/**
* @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)
const state = coerceState(parsed)
if (Object.keys(state.connections).length > 0) return { path, state }
} catch {
/* missing or corrupt */
}
const migrated = await bareHolesailManagedTryMigrateLegacyState(ctx, env, path)
if (migrated && Object.keys(migrated.connections).length > 0) {
try {
await bareHolesailManagedWriteState(ctx, env, migrated)
} catch {
/* return merged in-memory even if persist fails */
}
return { path, state: migrated }
}
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))
}
/**
* Ensure a managed **server** row has a **stable ctor secret** before `new Holesail`.
*
* - **64-hex `seed`** — authoritative; never replaced by sync.
* - **Persisted z32 `seed`** (from migration or upstream) — same as ctor material; **do not clear** (was a bug).
* - **`hs://…` `key` only** (legacy) — migrate suffix into **`seed`** or mint **64-hex** **`seed`** (`bare-crypto`).
*
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {string} id
*/
export async function bareHolesailManagedEnsureServerSeedPersisted(ctx, env, id) {
const { state } = await bareHolesailManagedReadState(ctx, env)
const raw = state.connections[id]
if (!raw || typeof raw !== 'object') return
const row = /** @type {Record<string, unknown>} */ (raw)
if (!bareHolesailEnvTruthy(row.server)) return
const seedRaw = String(row.seed ?? '').trim()
const key = String(row.key ?? '').trim()
if (seedRaw && bareHolesailManagedSeedLooksValid(seedRaw)) {
row.seed = seedRaw.toLowerCase()
state.connections[id] = row
return
}
if (seedRaw && bareHolesailManagedPersistentSeedLooksValid(seedRaw)) {
row.seed = seedRaw.toLowerCase()
state.connections[id] = row
return
}
if (/^hs:\/\//i.test(key)) {
const sfx = bareHolesailManagedHsUrlKeySuffix(key)
if (sfx && bareHolesailManagedPersistentSeedLooksValid(sfx)) {
row.seed = sfx.toLowerCase()
state.connections[id] = row
await bareHolesailManagedWriteState(ctx, env, state)
logLine(ctx, `managed: persisted hs url suffix as seed for ${id}`)
return
}
row.seed = bareHolesailManagedGenerateSeedHex()
delete row.key
state.connections[id] = row
await bareHolesailManagedWriteState(ctx, env, state)
logLine(ctx, `managed: minted new hex seed (hs key had no stable suffix) for ${id}`)
return
}
if (bareHolesailManagedSeedLooksValid(key)) {
row.seed = key.toLowerCase()
state.connections[id] = row
await bareHolesailManagedWriteState(ctx, env, state)
logLine(ctx, `managed: migrated hex key → seed for ${id}`)
return
}
row.seed = bareHolesailManagedGenerateSeedHex()
state.connections[id] = row
await bareHolesailManagedWriteState(ctx, env, state)
logLine(ctx, `managed: persisted new server seed for ${id}`)
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
*/
export function bareHolesailManagedDebugEnabled(ctx, env) {
return bareHolesailEnvTruthy(env.BARE_OS_HOLESAIL_DEBUG)
}
/**
* When a managed **server** is live, persist **`hs.info.url`** into **`key`** only.
* **Never** overwrite **`seed`** (64-hex ctor secret): replacing it with the z32 URL suffix changes the ctor input
* and mints a **different** tunnel on the next login.
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {string} id
* @returns {Promise<boolean>} true if `state.json` was written
*/
/**
* @param {unknown} hs
* @param {number} cfgPort
*/
function bareHolesailManagedResolvedListenPort(hs, cfgPort) {
try {
const info =
hs && typeof hs === 'object' && 'info' in hs
? /** @type {{ info?: unknown }} */ (hs).info
: null
if (info && typeof info === 'object') {
const p = Number(/** @type {{ port?: unknown }} */ (info).port)
if (Number.isFinite(p) && p > 0) return p
}
} catch {
/* ignore */
}
const n = Number(cfgPort)
return Number.isFinite(n) && n > 0 ? n : 0
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {string} id
* @param {number} newPort
* @param {number} previousPort
*/
async function persistManagedListenPortAfterFallback(
ctx,
env,
id,
newPort,
previousPort
) {
if (
!Number.isFinite(newPort) ||
newPort <= 0 ||
!Number.isFinite(previousPort) ||
previousPort <= 0 ||
newPort === previousPort
)
return
try {
const { state } = await bareHolesailManagedReadState(ctx, env)
const cur = state.connections[id]
if (!cur || typeof cur !== 'object') return
const row = /** @type {Record<string, unknown>} */ (cur)
row.port = newPort
state.connections[id] = row
await bareHolesailManagedWriteState(ctx, env, state)
const rec = managedInstances.get(id)
if (rec?.entry && typeof rec.entry === 'object') {
/** @type {Record<string, unknown>} */ (rec.entry).port = newPort
}
logLine(
ctx,
`managed: persisted listen port ${newPort} (fallback from ${previousPort}) for ${id}`
)
} catch (e) {
const msg =
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
logLine(ctx, `managed: persist listen port ${id}: ${msg}`)
}
}
export async function bareHolesailManagedSyncPersistedServerKey(ctx, env, id) {
const url = bareHolesailManagedRuntimeUrl(id).trim()
if (!url) return false
const { state } = await bareHolesailManagedReadState(ctx, env)
const cur = state.connections[id]
if (!cur || typeof cur !== 'object') return false
const row = /** @type {Record<string, unknown>} */ (cur)
if (!bareHolesailEnvTruthy(row.server)) return false
const prev = String(row.key ?? '').trim()
if (prev && bareHolesailManagedPersistedUrlMatchesLive(prev, url)) return false
row.key = url
state.connections[id] = row
await bareHolesailManagedWriteState(ctx, env, state)
const rec = managedInstances.get(id)
if (rec && rec.entry && typeof rec.entry === 'object') {
/** @type {Record<string, unknown>} */ (rec.entry).key = url
}
logLine(ctx, `managed: synced server hs url for ${id}`)
return true
}
/**
* Start one managed tunnel from the **current** `state.json` row for `id` (no normalize — uses disk as-is).
* Re-reads state immediately before `new Holesail` so `key` matches `~/.holesail/state.json`, including full `hs://…`.
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {string} id
*/
async function startManagedInstance(ctx, env, id) {
await bareHolesailManagedEnsureServerSeedPersisted(ctx, env, id)
await yieldForStateCoalescence()
const { state } = await bareHolesailManagedReadState(ctx, env)
const raw = state.connections[id]
if (!raw || typeof raw !== 'object') return
const conn = /** @type {Record<string, unknown>} */ (raw)
const enabled =
conn.enabled === undefined || conn.enabled === null
? true
: bareHolesailEnvTruthy(conn.enabled)
if (!enabled) return
const server = bareHolesailEnvTruthy(conn.server)
const client = bareHolesailEnvTruthy(conn.client)
if (server === client) {
logLine(ctx, `managed: skip ${id}: set exactly one of server or client`)
return
}
const keyStr = String(conn.key ?? '').trim()
const seedStr = String(conn.seed ?? '').trim()
if (client && !keyStr) {
logLine(ctx, `managed: skip ${id}: client requires key`)
return
}
if (managedInstances.has(id)) {
if (!server) return
const live = bareHolesailManagedRuntimeUrl(id).trim()
const rec = managedInstances.get(id)
const prevRow =
rec?.entry && typeof rec.entry === 'object'
? /** @type {Record<string, unknown>} */ (rec.entry)
: null
const prevSeed = prevRow ? String(prevRow.seed ?? '').trim() : ''
if (seedStr && prevSeed && seedStr.toLowerCase() === prevSeed.toLowerCase() && live)
return
if (
keyStr &&
live &&
bareHolesailManagedPersistedUrlMatchesLive(keyStr, live) &&
(!seedStr || seedStr.toLowerCase() === prevSeed.toLowerCase())
)
return
await stopManagedInstance(ctx, id)
}
const Holesail = await loadHolesailConstructor(ctx)
const cfg = bareHolesailManagedNewHolesailOpts(conn, env)
if (server && !String(cfg.key ?? '').trim()) {
logLine(ctx, `managed: skip ${id}: server ctor key material empty after ensure`)
return
}
const requestedPort =
cfg.port !== undefined && cfg.port !== null ? Number(cfg.port) : NaN
let hs = new Holesail(cfg)
try {
await hs.ready()
} catch (e) {
if (!bindFallbackEnabled(env) || !isAddrInUse(e)) throw e
await safeCloseHolesail(hs)
cfg.port = 0
hs = new Holesail(cfg)
await hs.ready()
logLine(
ctx,
`managed: ${id} ready after EADDRINUSE fallback (ephemeral port)`
)
}
managedInstances.set(id, { hs, entry: { ...conn } })
const cfgPortAfter =
typeof cfg.port === 'number'
? cfg.port
: cfg.port != null
? Number(cfg.port)
: NaN
const resolved = bareHolesailManagedResolvedListenPort(hs, cfgPortAfter)
if (
bindFallbackEnabled(env) &&
Number.isFinite(requestedPort) &&
requestedPort > 0 &&
resolved > 0 &&
resolved !== requestedPort
) {
await persistManagedListenPortAfterFallback(
ctx,
env,
id,
resolved,
requestedPort
)
}
if (server) {
try {
await bareHolesailManagedSyncPersistedServerKey(ctx, env, id)
} catch (e) {
const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
logLine(ctx, `managed: persist key ${id} skipped: ${msg}`)
}
}
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}`)
}
/** Yield one tick so `bare-os-www` / `bare-os-ssh-holesail` (and other writers) can finish persisting `~/.holesail/state.json`. */
function yieldForStateCoalescence() {
return new Promise((resolve) => {
if (typeof globalThis.setImmediate === 'function') globalThis.setImmediate(resolve)
else queueMicrotask(resolve)
})
}
/**
* Start every enabled connection from a fresh read of disk (used for initial start + reconcile pass).
* **`onlyNew`** — when `true`, skip ids already live in **`managedInstances`** so a second pass can pick up
* connections written after the first pass (e.g. **bare-os-www**, **bare-openssh**) without destroying running tunnels.
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
* @param {{ onlyNew?: boolean }} [opts]
*/
async function startManagedConnectionsFromDisk(ctx, env, opts = {}) {
const onlyNew = opts.onlyNew === true
const { state } = await bareHolesailManagedReadState(ctx, env)
const idsSorted = Object.keys(state.connections).sort()
for (const id of idsSorted) {
const idOk = bareHolesailManagedValidateId(id)
if (!idOk.ok) {
logLine(ctx, `managed: skip invalid id ${id}: ${idOk.err}`)
continue
}
if (onlyNew && managedInstances.has(idOk.id)) continue
try {
await startManagedInstance(ctx, env, idOk.id)
} 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 */
}
}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
*/
export async function bareHolesailManagedStartService(ctx, env) {
if (managedServiceRunning) return
managedServiceRunning = true
await startManagedConnectionsFromDisk(ctx, env)
await yieldForStateCoalescence()
await startManagedConnectionsFromDisk(ctx, env, { onlyNew: true })
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 || typeof raw !== 'object')
throw new Error(`holesail: unknown connection ${idOk.id}`)
const row = /** @type {Record<string, unknown>} */ (raw)
const en =
row.enabled === undefined || row.enabled === null
? true
: bareHolesailEnvTruthy(row.enabled)
if (!en) throw new Error(`holesail: ${idOk.id} is disabled`)
await startManagedInstance(ctx, env, idOk.id)
}
/**
* @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,366 @@
/**
* 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)
})
@@ -0,0 +1,238 @@
import {
setupBareOsMeshdropChannel,
PROTOCOL_MESHDROP_CHANNEL_NAME,
BARE_OS_MESHDROP_WIRE_SCHEMA_VERSION,
bareOsProtMuxMeshdropChannelEnabled
} from 'bare-os-protocol'
/**
* @typedef {{ chan: import('protomux').Channel, mux: import('protomux').Protomux, socket: any, id: string | null, meshdropChan?: import('protomux').Channel | null }} SwarmPeer
*/
/**
* @param {Record<string, string | undefined>} [env]
*/
export function bareOsMeshdropMuxEnabled(env = globalThis.process?.env) {
return bareOsProtMuxMeshdropChannelEnabled(env || {})
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {Record<string, string | undefined>} [env]
*/
export function ensureDiskBareOsMeshdropTransport(disk, env = {}) {
if (!disk || !bareOsMeshdropMuxEnabled(globalThis.process?.env)) return
const merged = {
.../** @type {Record<string, string | undefined>} */ (
globalThis.process?.env || {}
),
...env
}
disk.bareOsMeshdropService = createBareOsMeshdropService({ env: merged })
}
/**
* @param {Record<string, string | undefined>} env
*/
function meshdropHistoryMax(env) {
const raw = String(env.BARE_OS_MESHDROP_HISTORY_MAX ?? '').trim()
const n = raw ? Number.parseInt(raw, 10) : NaN
if (Number.isFinite(n) && n >= 32 && n <= 10000) return n
return 2048
}
/**
* @param {Record<string, string | undefined>} env
*/
function meshdropPayloadMaxBytes(env) {
const raw = String(env.BARE_OS_MESHDROP_MAX_PAYLOAD_BYTES ?? '').trim()
const n = raw ? Number.parseInt(raw, 10) : NaN
if (Number.isFinite(n) && n >= 512 && n <= 512 * 1024) return n
return 64 * 1024
}
export function createBareOsMeshdropService(opts = {}) {
const env = opts.env || globalThis.process?.env || {}
const historyMax = meshdropHistoryMax(
/** @type {Record<string, string | undefined>} */ (env)
)
const payloadMaxBytes = meshdropPayloadMaxBytes(
/** @type {Record<string, string | undefined>} */ (env)
)
/** @type {Set<(ev: Record<string, unknown>) => void>} */
const subscribers = new Set()
/** @type {Array<Record<string, unknown>>} */
const history = []
/** @type {Map<string, number>} */
const seenFrame = new Map()
const metrics = {
rxEnvelope: 0,
txEnvelope: 0,
droppedPayload: 0,
deduped: 0
}
function trimSeen() {
const now = Date.now()
for (const [k, exp] of seenFrame) {
if (exp < now) seenFrame.delete(k)
}
}
function pushHistory(rec) {
history.push(rec)
while (history.length > historyMax) history.shift()
}
/**
* @param {Record<string, unknown>} frame
*/
function frameId(frame) {
const p = frame && typeof frame.payload === 'object' ? frame.payload : {}
const transferId = typeof p.transferId === 'string' ? p.transferId : ''
const offerId = typeof p.offerId === 'string' ? p.offerId : ''
const idx = typeof p.index === 'number' ? p.index : -1
const kind = typeof frame.kind === 'string' ? frame.kind : 'unknown'
const frameUid = typeof p.frameId === 'string' ? p.frameId : ''
return frameUid || `${transferId}:${offerId}:${kind}:${idx}`
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {SwarmPeer} fromPeer
* @param {Record<string, unknown>} frame
*/
function relayEnvelope(disk, fromPeer, frame) {
for (const p of disk.peers) {
if (p === fromPeer) continue
const ch = p.meshdropChan
if (!ch || !ch.messages || !ch.messages[0]) continue
try {
ch.messages[0].send(frame)
metrics.txEnvelope++
} catch {
/* ignore */
}
}
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {SwarmPeer} fromPeer
* @param {Record<string, unknown>} frame
*/
function ingestEnvelope(disk, fromPeer, frame) {
if (!frame || typeof frame !== 'object') return
const encodedLen = JSON.stringify(frame).length
if (encodedLen > payloadMaxBytes) {
metrics.droppedPayload++
return
}
trimSeen()
const fid = frameId(frame)
if (fid) {
if (seenFrame.has(fid)) {
metrics.deduped++
return
}
seenFrame.set(fid, Date.now() + 120_000)
}
metrics.rxEnvelope++
const rec = {
...frame,
fromPeerKey: fromPeer.id || '',
receivedAtMs: Date.now()
}
pushHistory(rec)
for (const fn of subscribers) {
try {
fn(rec)
} catch {
/* ignore */
}
}
relayEnvelope(disk, fromPeer, frame)
}
return {
PROTOCOL_MESHDROP_CHANNEL_NAME,
metrics,
history() {
return [...history]
},
subscribe(fn) {
subscribers.add(fn)
return () => subscribers.delete(fn)
},
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {import('protomux').Protomux} mux
* @param {any} _socket
* @param {SwarmPeer} peer
*/
pairOnMux(disk, mux, _socket, peer) {
setupBareOsMeshdropChannel(mux, {
onEnvelope(m, _ch) {
try {
disk.protomuxMeshdropChannelRxTotal =
(disk.protomuxMeshdropChannelRxTotal || 0) + 1
} catch {
/* ignore */
}
ingestEnvelope(disk, peer, m)
},
onChannelOpened(chan) {
peer.meshdropChan = chan
mux.stream?.once?.('close', () => {
peer.meshdropChan = null
})
}
})
},
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {Record<string, unknown>} frame
* @param {{ sender?: string }} [meta]
*/
broadcastLocal(disk, frame, meta = {}) {
const safeFrame = {
schemaVersion: BARE_OS_MESHDROP_WIRE_SCHEMA_VERSION,
sender: String(meta.sender || env.USER || 'local'),
tsMs: Date.now(),
...frame
}
const encodedLen = JSON.stringify(safeFrame).length
if (encodedLen > payloadMaxBytes) {
metrics.droppedPayload++
return { ok: false, reason: 'payload_too_large' }
}
pushHistory({ ...safeFrame, local: true, receivedAtMs: Date.now() })
for (const fn of subscribers) {
try {
fn({ ...safeFrame, local: true })
} catch {
/* ignore */
}
}
for (const p of disk.peers) {
const ch = p.meshdropChan
if (!ch || !ch.messages || !ch.messages[0]) continue
try {
ch.messages[0].send(safeFrame)
metrics.txEnvelope++
} catch {
/* ignore */
}
}
return { ok: true }
},
snapshotMetrics() {
return {
...metrics,
historyMax,
payloadMaxBytes,
protocol: PROTOCOL_MESHDROP_CHANNEL_NAME
}
}
}
}
@@ -0,0 +1,235 @@
/**
* Optional peer admission gate from **`BARE_OS_PEER_ALLOWLIST_HEX`**
* (comma-separated hex public keys; empty = deny unless explicit allow-all).
*
* **`BARE_OS_PEER_DENYLIST_HEX`** — comma-separated hex keys; if the peer matches,
* verdict is **`deny`** before allowlist evaluation (denylist wins).
*
* **`BARE_OS_PEER_REQUIRE_CAPS_JSON`** — JSON array of capability strings; when set,
* **`meta.caps`** must include every required token (hosts pass peer caps when known).
*
* When **`BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST`** is set and callers pass
* **`dhtAddressClass`** (hyperdht-addressstyle hint), the class must appear in
* the allow list after the key gate passes.
*
* Admission results use **`schema: 2`** (v1 was **`schema: 1`** only).
*
* @param {Record<string, unknown> | null | undefined} env
* @param {string} peerKeyHex
* @param {{ dhtAddressClass?: string, caps?: string[] } | null | undefined} [meta]
*/
const ADMISSION_SCHEMA = 2
/**
* @param {Record<string, unknown> | null | undefined} env
*/
function zeroTrustStrictProfile(env) {
const p = String(env?.BARE_OS_ZERO_TRUST_PROFILE || '')
.trim()
.toLowerCase()
return p === 'strict' || p === 'security'
}
/** @param {string} s */
function normKeyHex(s) {
return String(s || '')
.trim()
.toLowerCase()
.replace(/^0x/, '')
}
/** @param {string} raw */
function keySetFromCommaHex(raw) {
return new Set(
raw
.split(/[\s,]+/)
.map((s) => normKeyHex(s))
.filter(Boolean)
)
}
/**
* @param {Record<string, unknown> | null | undefined} env
* @returns {string[] | null}
*/
function parseRequireCaps(env) {
const raw = String(env?.BARE_OS_PEER_REQUIRE_CAPS_JSON || '').trim()
if (!raw) return null
try {
const j = JSON.parse(raw)
if (!Array.isArray(j)) return null
const caps = j
.map((x) => String(x).trim())
.filter(Boolean)
.slice(0, 32)
return caps.length ? caps : null
} catch {
return null
}
}
export function evaluateBareOsPeerAdmission(env, peerKeyHex, meta = undefined) {
const want = normKeyHex(peerKeyHex)
const atMs = () => Date.now()
const denyRaw = String(env?.BARE_OS_PEER_DENYLIST_HEX || '').trim()
if (denyRaw) {
const denySet = keySetFromCommaHex(denyRaw)
if (want && denySet.has(want)) {
return {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'peer_denylist',
denylistSize: denySet.size,
atMs: atMs()
}
}
}
const requireCaps = parseRequireCaps(env)
if (requireCaps && requireCaps.length > 0) {
const peerCapsRaw = meta && Array.isArray(meta.caps) ? meta.caps : []
const peerSet = new Set(
peerCapsRaw.map((c) => String(c).trim()).filter(Boolean)
)
for (const c of requireCaps) {
if (!peerSet.has(c)) {
return {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'peer_missing_cap',
requiredCaps: [...requireCaps],
atMs: atMs()
}
}
}
}
const raw = String(env?.BARE_OS_PEER_ALLOWLIST_HEX || '').trim()
const strict =
env?.BARE_OS_PEER_ALLOWLIST_STRICT === '1' ||
env?.BARE_OS_PEER_ALLOWLIST_STRICT === 'true' ||
zeroTrustStrictProfile(env)
const allowAll =
env?.BARE_OS_PEER_ALLOW_ALL === '1' || env?.BARE_OS_PEER_ALLOW_ALL === 'true'
/** @type {{ schema: number, verdict: string, note?: string, allowlistSize?: number, strictProfile?: boolean, atMs?: number, dhtAddressClassGate?: Record<string, unknown> }} */
let base
if (!raw) {
if (strict || !allowAll) {
base = {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'peer_allowlist_empty',
note:
'Peer allowlist is empty; admission denied unless BARE_OS_PEER_ALLOW_ALL=1.'
}
} else {
base = {
schema: ADMISSION_SCHEMA,
verdict: 'allow',
note: 'No allowlist and BARE_OS_PEER_ALLOW_ALL=1; peers admitted.'
}
}
} else {
const set = keySetFromCommaHex(raw)
const ok = want && set.has(want)
base = {
schema: ADMISSION_SCHEMA,
verdict: ok ? 'allow' : 'deny',
allowlistSize: set.size,
strictProfile: strict || undefined,
atMs: atMs()
}
}
const classRaw = String(env?.BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST || '').trim()
if (!classRaw) {
if (base.atMs == null) base.atMs = atMs()
return base
}
const allowClasses = new Set(
classRaw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean)
)
if (allowClasses.size === 0) {
if (base.atMs == null) base.atMs = atMs()
return base
}
const cls = String(meta?.dhtAddressClass || '')
.trim()
.toLowerCase()
if (!cls) {
return {
...base,
atMs: base.atMs ?? atMs(),
dhtAddressClassGate: {
schema: 1,
mode: 'no_peer_class_hint',
note: 'BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST is set but caller did not supply dhtAddressClass — stock Hyperswarm path does not classify peers; allow key verdict only.'
}
}
}
if (!allowClasses.has(cls)) {
return {
schema: ADMISSION_SCHEMA,
verdict: 'deny',
reason: 'dht_address_class',
dhtAddressClass: cls,
allowedClasses: [...allowClasses],
note: 'Peer DHT address class not listed in BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST.',
atMs: atMs()
}
}
if (base.verdict === 'deny') return base
return {
...base,
atMs: base.atMs ?? atMs(),
dhtAddressClassGate: { schema: 1, mode: 'allow', class: cls }
}
}
/**
* Build an event-bus row for **`peer_admission`** audit (**never** includes full peer keys).
*
* @param {string} peerKeyHex
* @param {{ verdict: string, reason?: string, schema: number }} result
* @param {{ dhtAddressClass?: string } | null | undefined} [meta]
*/
export function bareOsFormatPeerAdmissionAuditEvent(peerKeyHex, result, meta) {
const norm = String(peerKeyHex || '')
.trim()
.toLowerCase()
.replace(/^0x/, '')
const keyShort = norm ? norm.slice(0, 16) : ''
return {
type: 'peer_admission',
verdict: result.verdict,
reason: result.reason,
peerKeyHexPrefix: keyShort || undefined,
admissionSchema: result.schema,
dhtAddressClass:
meta && typeof meta === 'object'
? String(meta.dhtAddressClass || '').trim() || undefined
: undefined
}
}
/**
* Per-bucket rate gate for admission audit (**mutates** **`lastMap`** on allow).
*
* @param {string} bucket
* @param {number} now
* @param {number} rateMs
* @param {Map<string, number>} lastMap
*/
export function bareOsPeerAdmissionAuditRateAllow(bucket, now, rateMs, lastMap) {
if (rateMs <= 0) return true
const last = lastMap.get(bucket) ?? 0
if (now - last < rateMs) return false
lastMap.set(bucket, now)
return true
}
@@ -0,0 +1,246 @@
/**
* Peer-assisted seeding: booted booters may mirror MBR + seed RPC snapshots for
* cold joiners when {@link peerSystemSeedEnvEnabled} (default **on**; opt out with
* **`BARE_OS_PEER_SYSTEM_SEED=0`**, **`false`**, **`no`**, or **`off`**).
*/
import b4a from 'b4a'
import {
BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION,
BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY,
BARE_OS_KERNEL_FEATURES_STOCK_WORD_PRIMARY,
BARE_OS_KERNEL_FEATURE_BITS_DOC,
BARE_OS_PROTOCOL_PACKAGE_VERSION,
PROTOCOL_NAME,
getKernelCapabilityWords
} from 'bare-os-protocol'
import { buildStockKernelCapabilityWords } from './bare-os-capability-registry.js'
/**
* Whether peer system seeding may run (default **enabled** when unset).
* @param {Record<string, string | undefined> | null | undefined} env
* @returns {boolean}
*/
export function peerSystemSeedEnvEnabled(env) {
const v = String(env?.BARE_OS_PEER_SYSTEM_SEED ?? '')
.trim()
.toLowerCase()
if (v === '0' || v === 'false' || v === 'no' || v === 'off') return false
if (v === '1' || v === 'true' || v === 'yes' || v === 'on') return true
const p = String(env?.BARE_OS_ZERO_TRUST_PROFILE || '')
.trim()
.toLowerCase()
if (p === 'strict' || p === 'security') return false
return true
}
/**
* Operator explicitly set **`BARE_OS_PEER_SYSTEM_SEED`** to an affirmative token
* (**`1`**, **`true`**, **`yes`**). Used for host diagnostics when eligibility fails.
* @param {Record<string, string | undefined> | null | undefined} env
*/
export function peerSystemSeedExplicitAffirmative(env) {
const v = String(env?.BARE_OS_PEER_SYSTEM_SEED ?? '')
.trim()
.toLowerCase()
return v === '1' || v === 'true' || v === 'yes'
}
/**
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @returns {Record<string, unknown | null>}
*/
/**
* After a successful swarm boot, fill {@link import('./swarm-disk.js').SwarmDisk#seedCapabilityInfo}
* with stock kernel capability words when the publisher handshake was skipped, failed, or returned
* no words — so {@link computePeerSystemSeedEligibility} can pass and more nodes mirror **block 0**.
*
* Opt out with **`BARE_OS_PEER_SEED_SYNTHETIC_CAPABILITIES`** **`0`** / **`false`** / **`no`** / **`off`**.
*
* **`imageTipId`** on the synthetic object (for tip gates and cold joiners) is taken from, in order:
* **`BARE_OS_SEED_IMAGE_TIP_ID`**, **`BARE_OS_PEER_SEED_ADVERTISE_IMAGE_TIP_ID`**, **`BARE_OS_PEER_SEED_IMAGE_TIP_ID`**,
* then any **`imageTipId`** on a partial non-error capability object.
*
* @param {import('./swarm-disk.js').SwarmDisk} disk
* @param {Record<string, string | undefined> | null | undefined} env
*/
export function maybeSynthesizePeerSeedCapabilityInfo(disk, env) {
const p = String(env?.BARE_OS_ZERO_TRUST_PROFILE || '')
.trim()
.toLowerCase()
if (p === 'strict' || p === 'security') return
const off = String(env?.BARE_OS_PEER_SEED_SYNTHETIC_CAPABILITIES ?? '')
.trim()
.toLowerCase()
if (off === '0' || off === 'false' || off === 'no' || off === 'off') return
const mbr = disk?.bootMbr512
if (!mbr || !(mbr instanceof Uint8Array) || mbr.length !== 512) return
const cap = disk.seedCapabilityInfo
const hasUsableWords =
cap &&
typeof cap === 'object' &&
!('error' in cap && cap.error) &&
getKernelCapabilityWords(cap)
if (hasUsableWords) return
const words = buildStockKernelCapabilityWords(
BARE_OS_KERNEL_FEATURES_STOCK_WORD_PRIMARY
)
const partial =
cap && typeof cap === 'object' && !('error' in cap && cap.error)
? /** @type {Record<string, unknown>} */ (cap)
: null
const fromPartial =
partial && typeof partial.imageTipId === 'string'
? String(partial.imageTipId).trim()
: ''
const tip =
String(env?.BARE_OS_SEED_IMAGE_TIP_ID ?? '').trim() ||
String(env?.BARE_OS_PEER_SEED_ADVERTISE_IMAGE_TIP_ID ?? '').trim() ||
String(env?.BARE_OS_PEER_SEED_IMAGE_TIP_ID ?? '').trim() ||
fromPartial
const base = partial
? {
...partial,
[BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY]: words
}
: {
doc: BARE_OS_KERNEL_FEATURE_BITS_DOC,
featureBitsDoc: BARE_OS_KERNEL_FEATURE_BITS_DOC,
[BARE_OS_KERNEL_CAPABILITY_WORDS_JSON_KEY]: words,
kernelCapabilityWireVersion: BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION,
protocolPackageVersion: BARE_OS_PROTOCOL_PACKAGE_VERSION,
protocol: PROTOCOL_NAME,
role: 'seeder',
note:
'Synthetic capabilities: stock kernelCapabilityWords after successful swarm boot (publisher snapshot missing or incomplete).'
}
if (typeof base.doc !== 'string')
base.doc = BARE_OS_KERNEL_FEATURE_BITS_DOC
if (typeof base.featureBitsDoc !== 'string') {
base.featureBitsDoc =
typeof base.doc === 'string' ? base.doc : BARE_OS_KERNEL_FEATURE_BITS_DOC
}
if (typeof base.kernelCapabilityWireVersion !== 'number') {
base.kernelCapabilityWireVersion = BARE_OS_KERNEL_CAPABILITY_WIRE_VERSION
}
if (typeof base.protocolPackageVersion !== 'string') {
base.protocolPackageVersion = BARE_OS_PROTOCOL_PACKAGE_VERSION
}
if (typeof base.protocol !== 'string') base.protocol = PROTOCOL_NAME
if (typeof base.role !== 'string') base.role = 'seeder'
delete base.error
if (tip) base.imageTipId = tip
else delete base.imageTipId
disk.seedCapabilityInfo = /** @type {Record<string, unknown>} */ (base)
}
export function buildPeerSeedSnapshots(disk) {
return {
replication_status: disk.seedReplicationStatus ?? null,
manifest_hints: disk.seedManifestHints ?? null,
peer_health: disk.seedPeerHealth ?? null,
staging_slot: disk.seedStagingSlot ?? null,
replication_queue: disk.seedReplicationQueue ?? null,
capability_attestation: disk.seedCapabilityAttestation ?? null,
mbr_layout: disk.seedMbrLayout ?? null,
snapshot_hints: disk.seedSnapshotHints ?? null,
peer_firewall_stats: disk.seedPeerFirewallStats ?? null,
replication_plan: disk.seedReplicationPlan ?? null,
dht_bootstrap_hint: disk.seedDhtBootstrapHint ?? null,
snapshot_chain: disk.seedSnapshotChain ?? null,
mirror_compaction_hint: disk.seedMirrorCompactionHint ?? null,
updater_state: disk.seedUpdaterState ?? null,
blind_peer_topology_v2: disk.seedBlindPeerTopologyV2 ?? null,
compact_ping: disk.seedCompactPing ?? null,
corestore_stats: disk.seedCorestoreStats ?? null,
snapshot_manifest_slice: disk.seedSnapshotManifestSlice ?? null,
mirror_drive_hint_v2: disk.seedMirrorDriveHintV2 ?? null,
hrpc_registry_summary: disk.seedHrpcRegistrySummary ?? null,
protomux_capability_ad: disk.seedProtomuxCapabilityAd ?? null,
dht_address_book: disk.seedDhtAddressBook ?? null,
replication_throttle_hint: disk.seedReplicationThrottleHint ?? null,
bundlebee_stage: disk.seedBundlebeeStage ?? null,
http_dht_proxy_hint: disk.seedHttpDhtProxyHint ?? null,
protomux_rpc_pool_hint: disk.seedProtomuxRpcPoolHint ?? null,
hyperblob_store_hint: disk.seedHyperblobStoreHint ?? null,
signing_request_queue_hint: disk.seedSigningRequestQueueHint ?? null,
core_storage_layout_hint: disk.seedCoreStorageLayoutHint ?? null,
mirror_drive_compaction_v3: disk.seedMirrorDriveCompactionV3 ?? null,
bundlebee_cli_stage: disk.seedBundlebeeCliStage ?? null,
ready_guard_v2: disk.seedReadyGuardV2 ?? null,
blind_relay_circuit_hint: disk.seedBlindRelayCircuitHint ?? null,
http_dht_proxy_routes: disk.seedHttpDhtProxyRoutes ?? null
}
}
/**
* @param {{
* disk: import('./swarm-disk.js').SwarmDisk
* systemRevision: Readonly<{ currentId?: string; pendingId?: string; slot?: string }> | null | undefined
* env: Record<string, string | undefined> | null | undefined
* }} args
* @returns {{ ok: true } | { ok: false, reason: string }}
*/
export function computePeerSystemSeedEligibility(args) {
const { disk, systemRevision, env } = args
if (!peerSystemSeedEnvEnabled(env)) {
return { ok: false, reason: 'env_disabled' }
}
const cap = disk.seedCapabilityInfo
if (!cap || typeof cap !== 'object') {
return { ok: false, reason: 'no_seed_capability_info' }
}
if ('error' in cap && cap.error) {
return { ok: false, reason: 'seed_capability_error' }
}
if (cap.role === 'offline-lkg') {
return { ok: false, reason: 'offline_lkg' }
}
const words = getKernelCapabilityWords(cap)
if (!words) {
return { ok: false, reason: 'no_kernel_capability_words' }
}
const mbr = disk.bootMbr512
if (!mbr || !(mbr instanceof Uint8Array) || mbr.length !== 512) {
return { ok: false, reason: 'no_boot_mbr' }
}
const drive = disk.drive
if (!drive || typeof drive.id === 'undefined') {
return { ok: false, reason: 'no_system_drive' }
}
const primaryHex = Array.isArray(disk.mbrKeysHex) ? disk.mbrKeysHex[0] : ''
const idHex = b4a.toString(drive.id, 'hex').toLowerCase()
if (!primaryHex || idHex !== String(primaryHex).toLowerCase()) {
return { ok: false, reason: 'drive_key_not_mbr_primary' }
}
const wantTip = String(env?.BARE_OS_PEER_SEED_IMAGE_TIP_ID ?? '').trim()
if (wantTip) {
const got = String(
/** @type {Record<string, unknown>} */ (cap).imageTipId ?? ''
).trim()
if (got !== wantTip) {
return { ok: false, reason: 'image_tip_mismatch' }
}
}
const wantRev = String(env?.BARE_OS_PEER_SEED_REQUIRE_REVISION_ID ?? '').trim()
if (wantRev) {
const cur = String(systemRevision?.currentId ?? '').trim()
if (cur !== wantRev) {
return { ok: false, reason: 'system_revision_mismatch' }
}
}
return { ok: true }
}
@@ -0,0 +1,108 @@
/**
* Validated Protomux channel alias registry: logical name → actual channel name.
* Each actual channel name may be claimed by at most one logical alias at a time.
* Re-registering the same logical with a different actual overwrites and records history.
*/
const NAME_RE = /^[a-zA-Z][a-zA-Z0-9._-]{0,127}$/
/**
* @param {string} s
*/
export function assertValidProtomuxAliasName(s) {
const t = String(s || '').trim()
if (!t || t.length > 128) {
throw new Error('protomux alias: name length must be 1..128')
}
if (!NAME_RE.test(t)) {
throw new Error(
'protomux alias: name must match ' + NAME_RE.source + ' (logical/actual)'
)
}
return t
}
export function createBareOsProtomuxAliasRegistry() {
/** @type {Map<string, string>} */
const logicalToActual = new Map()
/** @type {Record<string, string>} */
const legacyMap = Object.create(null)
/** @type {{ atMs: number, logical: string, previousActual: string | null, actual: string }[]} */
const changeLog = []
/** @type {Map<string, string[]>} */
const actualToLogicals = new Map()
function reindexActual(logical, oldAct, newAct) {
if (oldAct) {
const arr = actualToLogicals.get(oldAct)
if (arr) {
const i = arr.indexOf(logical)
if (i >= 0) arr.splice(i, 1)
if (!arr.length) actualToLogicals.delete(oldAct)
}
}
if (newAct) {
const arr = actualToLogicals.get(newAct) || []
if (!arr.includes(logical)) arr.push(logical)
actualToLogicals.set(newAct, arr)
}
}
return {
/**
* @param {string} logicalName
* @param {string} actualName
* @returns {{ ok: boolean, conflict?: string, replaced?: boolean }}
*/
register(logicalName, actualName) {
const a = assertValidProtomuxAliasName(logicalName)
const b = assertValidProtomuxAliasName(actualName)
const prev = logicalToActual.get(a) || null
if (prev === b) {
return { ok: true, replaced: false }
}
const others = actualToLogicals.get(b) || []
if (others.some((o) => o !== a)) {
return {
ok: false,
conflict: `actual ${b} already mapped from logical: ${others.filter((o) => o !== a).join(', ')}`
}
}
reindexActual(a, prev, b)
logicalToActual.set(a, b)
legacyMap[a] = b
changeLog.push({
atMs: Date.now(),
logical: a,
previousActual: prev,
actual: b
})
if (changeLog.length > 256) changeLog.splice(0, changeLog.length - 256)
return { ok: true, replaced: Boolean(prev) }
},
get(logicalName) {
const a = String(logicalName || '').trim()
return logicalToActual.get(a) || null
},
snapshot() {
const aliases = {}
for (const [k, v] of logicalToActual.entries()) aliases[k] = v
return {
schema: 2,
aliases,
changeLogTail: changeLog.slice(-32),
reverseIndex: Object.fromEntries(
[...actualToLogicals.entries()].map(([act, logs]) => [act, [...logs]])
),
atMs: Date.now()
}
},
/** @returns {Record<string, string>} same object reference; updated on each register */
legacyMapView() {
return legacyMap
}
}
}
@@ -0,0 +1,78 @@
/**
* `/proc/bare_os/protomux_extensions.json` — policy-gated operator view of Protomux
* channel hints for kernel extensions (no wire secrets).
*/
/**
* @param {Record<string, string | undefined>} env
*/
function protomuxExtensionsRegistryExposed(env) {
const v = String(env?.BARE_OS_PROC_PROTOMUX_EXTENSIONS_REGISTRY ?? '')
.trim()
.toLowerCase()
return v === '1' || v === 'true' || v === 'yes'
}
/**
* @param {{
* env?: Record<string, string | undefined> | null,
* registrySnapshot?: Record<string, unknown> | null,
* ctxApiVersion?: string
* }} opts
*/
export function buildBareOsProtomuxExtensionsProcJson(opts = {}) {
const env = opts.env && typeof opts.env === 'object' ? opts.env : {}
const now = Date.now()
if (!protomuxExtensionsRegistryExposed(env)) {
return {
schema: 1,
exposed: false,
note:
'Set BARE_OS_PROC_PROTOMUX_EXTENSIONS_REGISTRY=1 to expose extension mux hints (alias registry snapshot + stable logical channel names).',
atMs: now
}
}
const snap =
opts.registrySnapshot && typeof opts.registrySnapshot === 'object'
? opts.registrySnapshot
: {}
return {
schemaVersion: 4,
guestContractVersion: 1,
exposed: true,
ctxApiVersion: String(opts.ctxApiVersion || ''),
registry: snap,
muxWirePackage: 'protomux',
muxWireMajor: 3,
channelLifecycle: {
schema: 1,
states: ['paired', 'opening', 'open', 'closing', 'closed'],
backpressureEvent: 'bare-os:protomux-backpressure',
note:
'Guest-facing contract: logical channels follow Protomux createChannel/open/close; framed streams must preserve message boundaries (length-prefixed / secret-stream).'
},
holepunchCompanionPackages: {
rpcPool: 'protomux-rpc-client-pool',
note:
'Logical npm names in the Holepunch org; stock guest does not bundle them unless merged via ctx.bare.'
},
extensionLogicalChannels: [
{
name: 'bare-os-kernel-ext-handshake',
note: 'Reserved logical label for extension pairing over Protomux-framed streams (P2P).'
},
{
name: 'bare-os-extension-telemetry',
note: 'Optional NDJSON-shaped diagnostic channel; host must open explicitly.'
},
{
name: 'bare-os-cap-v1',
protocol: 'bare-os-cap-v1',
envGate: 'BARE_OS_PROTOMUX_CAP_CHANNEL',
note: 'Opaque buffer side channel; application-layer auth required.'
}
],
wireReference: '/proc/bare_os/protomux.json',
atMs: now
}
}
@@ -0,0 +1,22 @@
/**
* Lexicographic replication priority tiers (boot-critical paths first).
* @param {string[]} paths
*/
export function prioritizeReplicationPaths(paths) {
const boot = ['/boot/', '/etc/bare-os/', '/lib/bare-os/', '/bin/']
/** @type {{ path: string, tier: number }[]} */
const scored = []
for (const p of paths) {
const path = String(p || '').replace(/\\/g, '/')
let tier = 99
for (let i = 0; i < boot.length; i++) {
if (path.startsWith(boot[i])) {
tier = i
break
}
}
scored.push({ path, tier })
}
scored.sort((a, b) => a.tier - b.tier || a.path.localeCompare(b.path))
return scored.map((s) => s.path)
}
@@ -0,0 +1,90 @@
/**
* Live Hyperdrive / core length hints for `/proc/bare_os/replication` and
* `metrics_live.replicationLive` (non-secret progress; not full per-peer throughput).
*
* @param {{ drive?: unknown, personalDrive?: unknown } | null | undefined} disk
* @param {number} peerCount
*/
export function buildBareOsReplicationLiveSketch(disk, peerCount) {
let systemCoreLength = null
let personalCoreLength = null
try {
const c =
disk &&
disk.drive &&
/** @type {{ core?: { length?: number } }} */ (disk.drive).core
if (c && typeof c.length === 'number') systemCoreLength = c.length
} catch {
systemCoreLength = null
}
try {
const c =
disk &&
disk.personalDrive &&
/** @type {{ core?: { length?: number } }} */ (disk.personalDrive).core
if (c && typeof c.length === 'number') personalCoreLength = c.length
} catch {
personalCoreLength = null
}
let auxiliaryDriveCount = 0
try {
if (disk && Array.isArray(disk.auxiliaryDrives))
auxiliaryDriveCount = disk.auxiliaryDrives.length
} catch {
auxiliaryDriveCount = 0
}
const stallHint =
peerCount === 0
? 'no_peers'
: systemCoreLength == null
? 'length_unavailable'
: 'ok'
/** @type {{ schema: number, download?: number, upload?: number, peers?: number, source?: string } | null} */
let monitorProgress = null
try {
const m = disk && disk.drive && typeof disk.drive.monitor === 'function'
? disk.drive.monitor()
: null
if (m && typeof m === 'object') {
const dl =
typeof m.downloadedBlocks === 'number'
? m.downloadedBlocks
: typeof m.download === 'number'
? m.download
: null
const ul =
typeof m.uploadedBlocks === 'number'
? m.uploadedBlocks
: typeof m.upload === 'number'
? m.upload
: null
const peers = typeof m.peers === 'number' ? m.peers : null
if (dl != null || ul != null || peers != null) {
monitorProgress = {
schema: 1,
...(dl != null ? { download: dl } : {}),
...(ul != null ? { upload: ul } : {}),
...(peers != null ? { peers } : {}),
source: 'hyperdrive.monitor'
}
}
}
} catch {
monitorProgress = null
}
return {
schema: 4,
systemCoreLength,
personalCoreLength,
auxiliaryDriveCount,
stallHint,
...(monitorProgress ? { monitorProgress } : {}),
corestoreSnapshotSurface: {
schema: 1,
operatorWorkflow:
'Pause or quiesce writers → replicate cores → export snapshot (corestore-snapshot style). Guest **`guestSuspendResume`** remains **ENOTSUP** unless the host registers Corestore suspend/resume hooks.',
readOnly: true
},
note: 'Schema 4 keeps schema 3 fields and optionally exposes monitorProgress when Hyperdrive monitor() returns counters (download/upload/peers).'
}
}
@@ -0,0 +1,210 @@
/**
* Swarm connection manager: delegates to {@link BareOsSwarmPeerPolicyEngine} for
* peer health, scoring, backoff/ban windows, and replication scheduling hints.
*/
import { BareOsSwarmPeerPolicyEngine } from './bare-os-swarm-peer-policy.js'
export class BareOsSwarmConnectionManager {
/**
* @param {import('hyperswarm').default | null} swarm
* @param {{ env?: Record<string, string | undefined> | null }} [opts]
*/
constructor(swarm, opts = {}) {
this._engine = new BareOsSwarmPeerPolicyEngine(swarm, opts)
/** Protomux RPC poolstyle reuse counter (call {@link recordRpcPoolReuse} from hot paths). */
this._rpcPoolReuse = 0
/** @type {Map<string, { openedAtMs: number, lastActivityAtMs: number, initiator: boolean }>} */
this._activeConnections = new Map()
this._duplicateDrops = 0
/** @type {Map<'short'|'medium'|'long'|'xlong', Set<string>>} */
this._retryBuckets = new Map([
['short', new Set()],
['medium', new Set()],
['long', new Set()],
['xlong', new Set()]
])
this._bucketTimer = null
this._retryDequeued = 0
}
/** Increment when a logical RPC channel is reused instead of opened fresh. */
recordRpcPoolReuse() {
this._rpcPoolReuse++
}
get swarm() {
return this._engine.swarm
}
/**
* @param {string} peerKey
* @param {boolean} ok
* @param {{ latencyMs?: number }} [probeMeta]
*/
noteProbe(peerKey, ok, probeMeta) {
this._engine.noteProbe(peerKey, ok, probeMeta)
}
/**
* @param {string} peerKey
* @param {{ dhtAddressClass?: string } | null | undefined} [meta]
*/
shouldAttemptPeer(peerKey, meta) {
return this._engine.shouldAttemptPeer(peerKey, meta)
}
/**
* @param {string} peerKey
*/
consumeReconnectBudget(peerKey) {
this._engine.consumeReconnectBudget(peerKey)
}
/**
* @param {string} peerKey
* @param {number} [amount]
*/
replenishReconnectBudget(peerKey, amount) {
this._engine.replenishReconnectBudget(peerKey, amount)
}
/**
* @param {string} peerKey
*/
score(peerKey) {
return this._engine.score(peerKey)
}
snapshot() {
const base = this._engine.snapshot()
return {
...base,
protomuxPoolMetrics: {
schema: 1,
rpcPoolReuseCount: this._rpcPoolReuse,
note: 'Aligns with protomux-rpc-client-pool-style accounting; stock booter increments only when callers invoke recordRpcPoolReuse.'
},
duplicateArbitration: {
schema: 1,
activeConnectionCount: this._activeConnections.size,
duplicateDrops: this._duplicateDrops,
note: 'Deterministic tie-break: keep older open; when equal age keep initiator.'
},
groupedRetry: {
schema: 1,
queued: {
short: this._retryBuckets.get('short')?.size || 0,
medium: this._retryBuckets.get('medium')?.size || 0,
long: this._retryBuckets.get('long')?.size || 0,
xlong: this._retryBuckets.get('xlong')?.size || 0
},
dequeuedTotal: this._retryDequeued
}
}
}
/**
* @param {string[]} peerKeys
*/
rankPeers(peerKeys) {
return this._engine.rankPeers(peerKeys)
}
/**
* Deterministic duplicate arbitration. Returns true when caller should keep the incoming edge.
* @param {string} peerKey
* @param {{ initiator?: boolean }} [incoming]
*/
shouldAcceptConnection(peerKey, incoming = {}) {
const key = String(peerKey || '').slice(0, 128) || 'unknown'
const now = Date.now()
const cur = this._activeConnections.get(key)
if (!cur) return true
const incomingInitiator = incoming.initiator === true
if (now - cur.lastActivityAtMs <= 15000) {
this._duplicateDrops++
return false
}
if (cur.initiator && !incomingInitiator) {
this._duplicateDrops++
return false
}
return true
}
/**
* @param {string} peerKey
* @param {{ initiator?: boolean }} [meta]
*/
markConnectionOpen(peerKey, meta = {}) {
const key = String(peerKey || '').slice(0, 128) || 'unknown'
const now = Date.now()
this._activeConnections.set(key, {
openedAtMs: now,
lastActivityAtMs: now,
initiator: meta.initiator === true
})
}
/**
* @param {string} peerKey
*/
markConnectionClosed(peerKey) {
const key = String(peerKey || '').slice(0, 128) || 'unknown'
this._activeConnections.delete(key)
}
/**
* @param {string} peerKey
*/
markConnectionActivity(peerKey) {
const key = String(peerKey || '').slice(0, 128) || 'unknown'
const row = this._activeConnections.get(key)
if (!row) return
row.lastActivityAtMs = Date.now()
}
/**
* Queue peer for grouped retry scheduling.
* @param {string} peerKey
* @param {'short'|'medium'|'long'|'xlong'} [tier]
*/
queueRetry(peerKey, tier = 'short') {
const k = String(peerKey || '').slice(0, 128) || 'unknown'
const t = this._retryBuckets.get(tier)
if (!t) return
t.add(k)
if (this._bucketTimer) return
this._bucketTimer = setInterval(() => {
for (const bucket of this._retryBuckets.values()) {
const first = bucket.values().next()
if (!first.done) {
bucket.delete(first.value)
this._retryDequeued++
break
}
}
const allEmpty = [...this._retryBuckets.values()].every((b) => b.size === 0)
if (allEmpty && this._bucketTimer) {
clearInterval(this._bucketTimer)
this._bucketTimer = null
}
}, 50)
}
/**
* Trigger synthetic keepalive probe accounting for all active peers.
* @param {boolean} ok
* @param {{ latencyMs?: number }} [meta]
*/
emitKeepaliveProbeSweep(ok, meta = {}) {
for (const key of this._activeConnections.keys()) {
this.noteProbe(key, ok, {
latencyMs: meta.latencyMs,
failClass: ok ? undefined : 'timeout',
transportClass: 'tcp'
})
}
}
}
@@ -0,0 +1,49 @@
/**
* Swarm session lifecycle labels for operators (non-authoritative; driven by env + peer count).
* @param {Record<string, unknown> | null | undefined} env
* @param {number} peerCount
*/
export function bareOsSwarmLifecycleSnapshot(env, peerCount) {
const peers = Math.max(0, Math.floor(peerCount))
const syncing =
env &&
(env.BARE_OS_SWARM_SYNCING === '1' || env.BARE_OS_SWARM_SYNCING === 'true')
const degraded =
env &&
(env.BARE_OS_SWARM_DEGRADED === '1' || env.BARE_OS_SWARM_DEGRADED === 'true')
/** @type {'discovering' | 'syncing' | 'steady' | 'degraded'} */
let state = 'steady'
if (degraded) state = 'degraded'
else if (syncing) state = 'syncing'
else if (peers === 0) state = 'discovering'
/** @type {number | undefined} */
let dhtActiveQueries
/** @type {number | undefined} */
let udxEphemeralPort
const raw = env && env.BARE_OS_HYPERDHT_STATS_JSON
if (raw != null && String(raw).trim()) {
try {
const j = JSON.parse(String(raw))
if (typeof j.activeQueries === 'number') dhtActiveQueries = j.activeQueries
if (typeof j.udpPort === 'number') udxEphemeralPort = j.udpPort
} catch {
/* ignore invalid operator JSON */
}
}
return {
schema: 1,
state,
peerCount: peers,
note:
'Lifecycle is derived from BARE_OS_SWARM_* env hints and live peer count; host may override via env.',
hyperdhtSketch: {
schema: 1,
note: 'Capped non-secret hints from BARE_OS_HYPERDHT_STATS_JSON when the host sets it.',
dhtActiveQueries,
udxEphemeralPort
},
atMs: Date.now()
}
}
@@ -0,0 +1,333 @@
/**
* Production swarm peer policy: EWMA latency, success/fail scoring, exponential backoff,
* and temporary ban windows for abusive or failing peers.
*
* Tunable via optional env (read by caller): BARE_OS_SWARM_BAN_FAIL_THRESHOLD,
* BARE_OS_SWARM_BAN_MS_INITIAL, BARE_OS_SWARM_BAN_MS_MAX, BARE_OS_SWARM_EWMA_ALPHA.
* Optional **`BARE_OS_PEER_ALLOWLIST_HEX`** is enforced here so swarm scheduling aligns with
* **`ctx.bareOsEvaluatePeerAdmission`** (deny before reconnect budget is spent).
*/
import { evaluateBareOsPeerAdmission } from './bare-os-peer-admission.js'
const DEFAULT_FAILS_BEFORE_BAN = 4
const DEFAULT_BAN_MS_INITIAL = 5000
const DEFAULT_BAN_MS_MAX = 300000
const DEFAULT_EWMA_ALPHA = 0.25
/**
* @param {Record<string, string | undefined> | null | undefined} env
*/
function readSwarmPolicyEnv(env) {
const fails = Number.parseInt(
String(env?.BARE_OS_SWARM_BAN_FAIL_THRESHOLD || ''),
10
)
const ban0 = Number.parseInt(
String(env?.BARE_OS_SWARM_BAN_MS_INITIAL || ''),
10
)
const banMax = Number.parseInt(
String(env?.BARE_OS_SWARM_BAN_MS_MAX || ''),
10
)
const alpha = Number.parseFloat(
String(env?.BARE_OS_SWARM_EWMA_ALPHA || '')
)
return {
failsBeforeBan:
Number.isFinite(fails) && fails >= 1 ? Math.min(32, fails) : DEFAULT_FAILS_BEFORE_BAN,
banMsInitial:
Number.isFinite(ban0) && ban0 >= 500
? Math.min(DEFAULT_BAN_MS_MAX, ban0)
: DEFAULT_BAN_MS_INITIAL,
banMsMax:
Number.isFinite(banMax) && banMax >= 1000
? Math.min(3600000, banMax)
: DEFAULT_BAN_MS_MAX,
ewmaAlpha:
Number.isFinite(alpha) && alpha > 0 && alpha <= 1
? alpha
: DEFAULT_EWMA_ALPHA
}
}
/**
* @typedef {{
* ok: number,
* fail: number,
* lastMs: number,
* consecutiveFail: number,
* banCount: number,
* banUntilMs: number,
* ewmaLatencyMs: number,
* lastLatencyMs: number | null,
* reconnectBudget: number,
* earliestRetryAtMs: number,
* retryTier: 'none' | 'short' | 'medium' | 'long' | 'xlong',
* failByClass: Record<string, number>,
* transportByClass: Record<string, number>
* }} PeerState
*/
export class BareOsSwarmPeerPolicyEngine {
/**
* @param {import('hyperswarm').default | null} swarm
* @param {{ env?: Record<string, string | undefined> | null }} [opts]
*/
constructor(swarm, opts = {}) {
this.swarm = swarm
this._env = opts.env && typeof opts.env === 'object' ? opts.env : null
this._cfg = readSwarmPolicyEnv(opts.env)
/** @type {Map<string, PeerState>} */
this._peers = new Map()
/** @type {number} */
this._globalReconnectBudget = 256
/** @type {{ start: number, n: number } | null} */
this._attemptBurstWindow = null
}
_key(peerKey) {
return String(peerKey || '').slice(0, 128) || 'unknown'
}
_getOrCreate(k) {
let st = this._peers.get(k)
if (!st) {
st = {
ok: 0,
fail: 0,
lastMs: 0,
consecutiveFail: 0,
banCount: 0,
banUntilMs: 0,
ewmaLatencyMs: 0,
lastLatencyMs: null,
reconnectBudget: 32,
earliestRetryAtMs: 0,
retryTier: 'none',
failByClass: {},
transportByClass: {}
}
this._peers.set(k, st)
}
return st
}
_retryDelayFor(st) {
const n = Math.max(0, Number(st.consecutiveFail) || 0)
const tier =
n >= 9 ? 'xlong' : n >= 6 ? 'long' : n >= 4 ? 'medium' : n >= 2 ? 'short' : 'none'
const base =
tier === 'xlong'
? 15000
: tier === 'long'
? 8000
: tier === 'medium'
? 3000
: tier === 'short'
? 800
: 0
const jitter = base > 0 ? Math.floor(base * 0.3 * Math.random()) : 0
return { tier, delayMs: base + jitter }
}
/**
* @param {string} peerKey
* @param {boolean} ok
* @param {{ latencyMs?: number, failClass?: string, transportClass?: string }} [probeMeta]
*/
noteProbe(peerKey, ok, probeMeta = {}) {
const k = this._key(peerKey)
const st = this._getOrCreate(k)
const now = Date.now()
st.lastMs = now
if (ok) {
const tc = String(probeMeta.transportClass || 'unknown')
.trim()
.slice(0, 24) || 'unknown'
st.transportByClass[tc] = (st.transportByClass[tc] || 0) + 1
st.ok++
st.consecutiveFail = 0
st.retryTier = 'none'
st.earliestRetryAtMs = 0
const lat =
typeof probeMeta.latencyMs === 'number' && Number.isFinite(probeMeta.latencyMs)
? Math.max(0, probeMeta.latencyMs)
: null
st.lastLatencyMs = lat
if (lat != null) {
const a = this._cfg.ewmaAlpha
st.ewmaLatencyMs =
st.ewmaLatencyMs === 0
? lat
: a * lat + (1 - a) * st.ewmaLatencyMs
}
if (now >= st.banUntilMs) {
st.banUntilMs = 0
}
} else {
st.fail++
st.consecutiveFail++
const failClass = String(probeMeta.failClass || 'unknown')
.trim()
.slice(0, 32) || 'unknown'
st.failByClass[failClass] = (st.failByClass[failClass] || 0) + 1
const retry = this._retryDelayFor(st)
st.retryTier = retry.tier
st.earliestRetryAtMs = now + retry.delayMs
if (st.consecutiveFail >= this._cfg.failsBeforeBan) {
st.banCount++
const base = Math.min(
this._cfg.banMsMax,
this._cfg.banMsInitial * Math.pow(2, Math.min(8, st.banCount - 1))
)
const jitter = Math.floor(base * 0.2 * Math.random())
st.banUntilMs = now + base + jitter
st.consecutiveFail = 0
st.retryTier = 'xlong'
st.earliestRetryAtMs = st.banUntilMs
}
}
}
/**
* @param {string} peerKey
* @param {{ dhtAddressClass?: string } | null | undefined} [meta]
* @returns {boolean} false when peer is in ban window or reconnect budget exhausted
*/
shouldAttemptPeer(peerKey, meta) {
const k = this._key(peerKey)
if (this._env) {
const adm = evaluateBareOsPeerAdmission(this._env, k, meta)
if (adm.verdict === 'deny') return false
const burstMax = Number.parseInt(
String(this._env.BARE_OS_SWARM_ATTEMPT_BURST_PER_SEC || ''),
10
)
if (Number.isFinite(burstMax) && burstMax > 0) {
const now = Date.now()
const w = this._attemptBurstWindow
if (w && now - w.start <= 1000 && w.n >= burstMax) return false
}
}
const st = this._peers.get(k)
const now = Date.now()
if (!st) return true
if (this._globalReconnectBudget <= 0) return false
if (st.banUntilMs > now) return false
if (st.earliestRetryAtMs > now) return false
if (st.reconnectBudget <= 0) return false
return true
}
/**
* Called when initiating a connection attempt (consumes budget).
* @param {string} peerKey
*/
consumeReconnectBudget(peerKey) {
const k = this._key(peerKey)
const st = this._getOrCreate(k)
if (st.reconnectBudget > 0) st.reconnectBudget--
if (this._globalReconnectBudget > 0) this._globalReconnectBudget--
if (this._env) {
const burstMax = Number.parseInt(
String(this._env.BARE_OS_SWARM_ATTEMPT_BURST_PER_SEC || ''),
10
)
if (Number.isFinite(burstMax) && burstMax > 0) {
const now = Date.now()
if (
!this._attemptBurstWindow ||
now - this._attemptBurstWindow.start > 1000
) {
this._attemptBurstWindow = { start: now, n: 0 }
}
this._attemptBurstWindow.n++
}
}
}
/**
* Replenish reconnect budget periodically (e.g. on successful session).
* @param {string} peerKey
* @param {number} [amount]
*/
replenishReconnectBudget(peerKey, amount = 8) {
const k = this._key(peerKey)
const st = this._getOrCreate(k)
st.reconnectBudget = Math.min(32, st.reconnectBudget + amount)
this._globalReconnectBudget = Math.min(256, this._globalReconnectBudget + amount)
}
/**
* Higher is better for replication throttle hints (0..1 scale, may go slightly negative).
* @param {string} peerKey
*/
score(peerKey) {
const k = this._key(peerKey)
const st = this._peers.get(k)
if (!st) return 0
const total = st.ok + st.fail
if (!total) return 0
const successRate = st.ok / total
const failPenalty = Math.min(0.5, st.fail * 0.05)
const protocolPenalty = Math.min(0.25, (st.failByClass.protocol || 0) * 0.03)
const transportReward = Math.min(
0.15,
((st.transportByClass.ipc || 0) +
(st.transportByClass.tcp || 0) * 0.7 +
(st.transportByClass.udx || 0) * 0.5) *
0.01
)
const policyPenalty = Math.min(0.2, (st.failByClass.policy || 0) * 0.02)
const timeoutPenalty = Math.min(0.15, (st.failByClass.timeout || 0) * 0.01)
const now = Date.now()
const banned = st.banUntilMs > now ? 0.35 : 0
const lat = st.ewmaLatencyMs
const latPenalty =
lat > 0 ? Math.min(0.25, Math.log10(1 + lat / 50) * 0.08) : 0
return (
successRate +
transportReward -
failPenalty -
protocolPenalty -
policyPenalty -
timeoutPenalty -
banned -
latPenalty
)
}
snapshot() {
const now = Date.now()
return {
schema: 2,
cfg: { ...this._cfg },
attemptBurstEnv: 'BARE_OS_SWARM_ATTEMPT_BURST_PER_SEC',
attemptBurstWindow: this._attemptBurstWindow
? { ...this._attemptBurstWindow }
: null,
globalReconnectBudget: this._globalReconnectBudget,
peers: [...this._peers.entries()].map(([id, st]) => ({
id,
...st,
banned: st.banUntilMs > now,
banRemainingMs: st.banUntilMs > now ? st.banUntilMs - now : 0,
retryWaitMs:
st.earliestRetryAtMs > now ? st.earliestRetryAtMs - now : 0
})),
atMs: now
}
}
/**
* Rank peer keys by score descending (for replication scheduling hints).
* @param {string[]} peerKeys
*/
rankPeers(peerKeys) {
return [...new Set(peerKeys.map((p) => this._key(p)))].sort(
(a, b) => this.score(b) - this.score(a)
)
}
}
@@ -0,0 +1,166 @@
/**
* Per-file JSON for /proc/bare_os/swarm_*_status.json (and internal bare_os_swarm_*_status).
* Surfaces always; “absent file” is not a mode — use `status` + `note` when a layer is not wired.
*/
/**
* @param {string} fileKey e.g. `bare_os_swarm_replication_status`
* @param {{
* disk: { peers?: { size: number } | null, [k: string]: unknown } | null,
* shellEnv: Record<string, string | undefined>,
* swarmConnectionManager: { snapshot?: () => unknown } | null,
* buildBareOsReplicationLiveSketch: (d: unknown, n: number) => unknown
* }} d
* @returns {string} JSON + newline
*/
export function bareOsFormatSwarmSubsystemProcJson(fileKey, d) {
const disk = d.disk
const shellEnv = d.shellEnv || {}
const peers = disk && disk.peers && typeof disk.peers.size === 'number'
? disk.peers.size
: 0
const now = Date.now()
/** @returns {unknown} */
const snap = () => {
try {
const scm = d.swarmConnectionManager
if (!scm || typeof scm.snapshot !== 'function') return null
return scm.snapshot()
} catch {
return null
}
}
const repLive =
typeof d.buildBareOsReplicationLiveSketch === 'function' && disk
? d.buildBareOsReplicationLiveSketch(disk, peers)
: null
switch (fileKey) {
case 'bare_os_swarm_replication_status':
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'replication',
peerCount: peers,
replicationLiveSketch: repLive ?? null,
seedReplicationStatus:
disk &&
disk.seedReplicationStatus &&
typeof disk.seedReplicationStatus === 'object'
? disk.seedReplicationStatus
: null,
note:
'Focused replication slice. Full blob: /proc/bare_os/replication and metrics_live.replicationLive.'
}) + '\n'
)
case 'bare_os_swarm_relay_status': {
const raw = String(shellEnv.BARE_OS_BLIND_RELAY_TOPOLOGY_JSON || '').trim()
let topology = null
if (raw) {
try {
topology = JSON.parse(raw)
} catch {
topology = { parseError: true }
}
}
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'relay',
status: topology ? 'env_present' : 'env_unset',
blindRelayTopology: topology,
envGate: 'BARE_OS_BLIND_RELAY_TOPOLOGY_JSON',
note:
topology
? 'Relay topology sketch from env (same idea as swarm.blindRelayTopology).'
: 'Set BARE_OS_BLIND_RELAY_TOPOLOGY_JSON on the host for relay sketches.'
}) + '\n'
)
}
case 'bare_os_swarm_datagrams_status':
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'datagrams',
status: 'logical',
guestDatagramSockets: 'POSIX DGRAM bridges / USOCK when ctx wires them',
metricsHint: 'See metrics_live.ipcTelemetry and env BARE_OS_POSIX_DGRAM_*',
note:
'No separate on-disk swarm datagram counters in stock booter; ipc + metrics_live carry hints.'
}) + '\n'
)
case 'bare_os_swarm_connection_manager_status': {
const s = snap()
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'connection_manager',
snapshotPresent: !!s,
snapshot: s,
note:
'BareOsSwarmConnectionManager snapshot (peer policy + protomux pool counters). No peer keys.'
}) + '\n'
)
}
case 'bare_os_swarm_key_broker_status':
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'key_broker',
status: 'not_exposed',
note:
'Stock Bare OS does not expose a standalone Hyperswarm “key broker” daemon. Identity flows through Hypercore/Hyperdrive + Pear configuration.'
}) + '\n'
)
case 'bare_os_swarm_holepunch_status':
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'holepunch',
natTraversalMode: String(
shellEnv.BARE_OS_NAT_TRAVERSAL_MODE || 'auto'
).trim(),
dhtAllowlistClasses: String(
shellEnv.BARE_OS_DHT_ADDRESS_CLASS_ALLOWLIST || ''
).trim(),
deeperDetailPath: '/proc/bare_os/net_summary.json',
note:
'NAT/holepunch outcomes are summarized under net_summary.transport when the host wires stats.'
}) + '\n'
)
case 'bare_os_swarm_datagram_replication_status':
return (
JSON.stringify({
schema: 1,
atMs: now,
subsystem: 'datagram_replication',
replicationQueue:
disk &&
disk.seedReplicationQueue &&
typeof disk.seedReplicationQueue === 'object'
? disk.seedReplicationQueue
: null,
peerCount: peers,
note:
'Correlation of UDP/logical datagram paths with replication is deployment-specific; seed replication queue shown when present.'
}) + '\n'
)
default:
return (
JSON.stringify({
schema: 1,
atMs: now,
fileKey,
status: 'unknown_key',
note:
'Unrecognized swarm subsystem proc key; regenerate booter / report upstream.'
}) + '\n'
)
}
}
@@ -0,0 +1,49 @@
/**
* Parse `BARE_OS_REPLICATION_SYNC_WINDOWS` (UTC, `HH:MM-HH:MM` ranges, comma-separated).
* @param {Record<string, string>} env
* @returns {{ active: boolean, windows: string[], nowUtc: string, note?: string }}
*/
export function computeReplicationSyncWindow(env) {
const raw = String(env.BARE_OS_REPLICATION_SYNC_WINDOWS || '').trim()
const now = new Date()
const pad = (n) => (n < 10 ? '0' : '') + n
const nowUtc = `${pad(now.getUTCHours())}:${pad(now.getUTCMinutes())}`
if (!raw) {
return {
active: true,
windows: [],
nowUtc,
note: 'no windows; replication always allowed'
}
}
const parts = raw
.split(',')
.map((s) => s.trim())
.filter(Boolean)
/** @type {string[]} */
const windows = []
let active = false
const toMin = (h, m) => h * 60 + m
const cur = toMin(now.getUTCHours(), now.getUTCMinutes())
for (const p of parts) {
const m = p.match(/^(\d{1,2}):(\d{2})\s*-\s*(\d{1,2}):(\d{2})$/)
if (!m) continue
const a = toMin(Number(m[1]), Number(m[2]))
const b = toMin(Number(m[3]), Number(m[4]))
windows.push(p)
if (a <= b) {
if (cur >= a && cur <= b) active = true
} else {
if (cur >= a || cur <= b) active = true
}
}
if (windows.length === 0) {
return {
active: true,
windows: [],
nowUtc,
note: 'no valid windows parsed; default allow'
}
}
return { active, windows, nowUtc }
}
@@ -0,0 +1,39 @@
/**
* Pure argv helpers for hdms (no bare-crypto / hyperdrive) so brittle-node can test parsing.
*/
/**
* @param {string[]} rest argv slice after `hdms pair`
* @returns {{ ok: true, persist: boolean, inviteTok: string } | { ok: false, error: string }}
*/
export function parseHdmsPairArgv(rest) {
let persist = true
let inviteTok = null
for (const x of rest) {
if (x === '--persist') continue
if (x === '--no-persist') {
persist = false
continue
}
if (String(x).startsWith('-')) {
return { ok: false, error: 'hdms: unknown flag: ' + x }
}
if (inviteTok) {
return { ok: false, error: 'hdms: pair expects a single invite string' }
}
inviteTok = x
}
if (rest.includes('--persist') && rest.includes('--no-persist')) {
return {
ok: false,
error: 'hdms: pair: use only one of --persist and --no-persist'
}
}
if (!inviteTok) {
return {
ok: false,
error: 'hdms: Usage: hdms pair [--persist|--no-persist] <invite>'
}
}
return { ok: true, persist, inviteTok }
}
@@ -0,0 +1,982 @@
/**
* Hyperdrive Management System: registry on personal drive, extra drives + swarm,
* Autopass for invite/pair. Use a static ESM import (not createRequire) so Pear can
* trace and stage `autopass`; bare-module require() from pear:// URLs does not resolve node_modules.
*/
import Autopass from 'autopass'
import b4a from 'b4a'
import { randomBytes } from 'bare-crypto'
import Hyperbee from 'hyperbee'
import hcCrypto from 'hypercore-crypto'
import idEnc from 'hypercore-id-encoding'
import { parseHdmsPairArgv } from './hdms-cli-argv.js'
import { bareOsHostBooterWarn } from './bare-os-host-booter-log.js'
import {
HDMS_AUTOPASS_SHARE_KEY,
HDMS_AUTOPASS_SHARE_RW_KEY,
decodeRwShareOffer,
decodeShareOffer,
removeBothHdmsShareKeys,
writerSecretBytesFromHex
} from './hdms-share-offer.js'
export const HDMS_REGISTRY_PATH = '/.bare/hdms/registry.json'
/**
* After BlindPairing, the HyperDB view may lag behind membership; autopass's own tests
* wait for `base.system.members === 2` before reading records.
* @param {{ base: { system?: { members?: number }, on: Function, off: Function, update?: () => Promise<void> } }} pass
* @param {number} timeoutMs
*/
async function waitForAutopassMembersReady(pass, timeoutMs) {
const deadline = Date.now() + timeoutMs
return new Promise((resolve) => {
let iv = null
const cleanup = () => {
if (iv) clearInterval(iv)
iv = null
try {
pass.base?.off?.('update', onUpdate)
} catch (_) {}
}
const tryOk = () => {
try {
const n = pass.base?.system?.members
if (typeof n === 'number' && n >= 2) {
cleanup()
resolve()
return true
}
} catch (_) {}
if (Date.now() >= deadline) {
cleanup()
resolve()
return true
}
return false
}
const onUpdate = () => {
void pass.base?.update?.().catch(() => {})
tryOk()
}
try {
pass.base?.on?.('update', onUpdate)
} catch (_) {}
iv = setInterval(() => {
void pass.base?.update?.().catch(() => {})
tryOk()
}, 100)
void pass.base?.update?.().catch(() => {})
tryOk()
})
}
/**
* Prefer RW offer (separate Autopass key) so legacy clients never treat RW as RO-only.
* @param {{ get: (k: string) => Promise<{ value?: unknown } | null>, base?: { update?: () => Promise<void> } }} pass
* @param {number} timeoutMs
* @returns {Promise<{ kind: 'rw', label: string, key: string, signerKey: string, writerSecretHex: string } | { kind: 'ro', label: string, key: string } | null>}
*/
async function waitForAutopassShare(pass, timeoutMs) {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
try {
await pass.base?.update?.()
} catch (_) {}
const rw = decodeRwShareOffer(await pass.get(HDMS_AUTOPASS_SHARE_RW_KEY))
if (rw) return { kind: 'rw', ...rw }
const ro = decodeShareOffer(await pass.get(HDMS_AUTOPASS_SHARE_KEY))
if (ro) return { kind: 'ro', ...ro }
await new Promise((r) => setTimeout(r, 300))
}
return null
}
/** Max ms to wait for Autobase membership after pair before polling share records. */
function parseHdmsPairReadyMs() {
const raw = globalThis.process?.env?.BARE_OS_HDMS_PAIR_READY_MS
let ms = 45_000
if (raw != null && String(raw).trim() !== '') {
const n = Number(raw)
if (Number.isFinite(n) && n >= 0) ms = Math.min(120_000, n)
}
return ms
}
/** Max ms to wait for `@autopass/invite` to disappear from the view after deleteInvite. */
function parseHdmsInviteClearMs() {
const raw = globalThis.process?.env?.BARE_OS_HDMS_INVITE_CLEAR_MS
let ms = 30_000
if (raw != null && String(raw).trim() !== '') {
const n = Number(raw)
if (Number.isFinite(n) && n > 0) ms = Math.min(120_000, n)
}
return ms
}
/**
* Always mint a brand-new BlindPairing z32: clear any open invite row from the view first.
* Autopass `createInvite()` returns the existing token while a row remains; after
* `deleteInvite()` the HyperDB view can lag until `base.update()` applies the delete.
*
* @param {import('autopass')} ap
* @param {boolean} readOnly
* @returns {Promise<string>}
*/
async function mintFreshHdmsAutopassInvite(ap, readOnly) {
await ap.deleteInvite()
if (ap.member) await ap.member.flushed()
try {
await ap.base.update()
} catch (_) {}
const deadline = Date.now() + parseHdmsInviteClearMs()
while (Date.now() < deadline) {
const existing = await ap.base.view.findOne('@autopass/invite', {})
if (existing === null) break
await ap.deleteInvite()
if (ap.member) await ap.member.flushed()
try {
await ap.base.update()
} catch (_) {}
await new Promise((r) => setTimeout(r, 50))
}
const left = await ap.base.view.findOne('@autopass/invite', {})
if (left !== null) {
throw new Error(
'HDMS: prior Autopass invite did not clear from view (try hdms invite again or check swarm/replication)'
)
}
return await ap.createInvite({ readOnly })
}
/** @param {string} label */
export function assertValidHdmsLabel(label) {
if (!label || typeof label !== 'string') throw new Error('Invalid label')
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(label)) {
throw new Error(
'Label must start with alphanumeric; allowed: . _ - (max 63 chars)'
)
}
}
function randomNsSuffix() {
return b4a.toString(randomBytes(8), 'hex')
}
/** Max time to wait for BlindPairing to complete (inviter must be reachable). */
function parseHdmsPairWaitMs() {
const raw = globalThis.process?.env?.BARE_OS_HDMS_PAIR_WAIT_MS
let ms = 120_000
if (raw != null && String(raw).trim() !== '') {
const n = Number(raw)
if (Number.isFinite(n) && n > 0) ms = Math.min(600_000, n)
}
return ms
}
/**
* Prefer inviter's label; if already mounted locally, try label-2 … label-99 (63-char cap).
* @param {Map<string, unknown>} byLabel
* @param {string} base
*/
function pickUniqueHdmsMountLabel(byLabel, base) {
assertValidHdmsLabel(base)
if (!byLabel.has(base)) return base
for (let n = 2; n <= 99; n++) {
const suffix = '-' + n
if (base.length + suffix.length > 63) break
const candidate = base + suffix
assertValidHdmsLabel(candidate)
if (!byLabel.has(candidate)) return candidate
}
throw new Error(
'HDMS mount label in use: ' + base + ' (remove it or ask inviter to use another name)'
)
}
/**
* Writable replica: `key` (drive z32) + `signerKey` (writer pk z32) + `writerSecretHex`, no `ns`.
* @typedef {{ id: string, label: string, mode: 'writable' | 'readonly', key?: string, ns?: string, signerKey?: string, writerSecretHex?: string }} HdmsRegistryEntry
* @typedef {{ version: 1, drives: HdmsRegistryEntry[] }} HdmsRegistryFile
*/
/**
* @param {import('hyperdrive').default} personalDrive
* @returns {Promise<HdmsRegistryFile>}
*/
export async function loadHdmsRegistry(personalDrive) {
const buf = await personalDrive.get(HDMS_REGISTRY_PATH, { follow: true })
if (!buf || buf.length === 0) {
return { version: 1, drives: [] }
}
try {
const j = JSON.parse(b4a.toString(buf))
if (j && j.version === 1 && Array.isArray(j.drives)) return j
} catch {
/* fallthrough */
}
return { version: 1, drives: [] }
}
/**
* @param {import('hyperdrive').default} personalDrive
* @param {HdmsRegistryFile} data
*/
export async function saveHdmsRegistry(personalDrive, data) {
const json = JSON.stringify(data, null, 0)
await personalDrive.put(HDMS_REGISTRY_PATH, b4a.from(json))
}
export class HdmsController {
constructor() {
/** @type {import('corestore').default | null} */
this.store = null
/** @type {import('hyperswarm').default | null} */
this.swarm = null
/** @type {typeof import('hyperdrive').default | null} */
this.Hyperdrive = null
/** @type {import('hyperdrive').default | null} */
this.personalDrive = null
/** @type {import('./swarm-disk.js').SwarmDisk | null} */
this.disk = null
/** @type {{ getMounts: () => Map<string, { drive: import('hyperdrive').default, writable: boolean }> } | null} */
this.vfsMountRef = null
/** @type {string[] | null} */
this.bootstrap = null
/** @type {HdmsRegistryFile | null} */
this.registry = null
/** @type {Map<string, { drive: import('hyperdrive').default, entry: HdmsRegistryEntry, writable: boolean, ephemeral?: boolean }>} */
this.byLabel = new Map()
/** @type {import('autopass') | null} */
this.autopass = null
this.active = false
}
/**
* @param {{
* store: import('corestore').default,
* swarm: import('hyperswarm').default,
* Hyperdrive: typeof import('hyperdrive').default,
* personalDrive: import('hyperdrive').default,
* disk: import('./swarm-disk.js').SwarmDisk,
* vfsMountRef: { getMounts: () => Map<string, { drive: import('hyperdrive').default, writable: boolean }> },
* bootstrap?: string[] | null,
* onAfterActivate?: (info: { labels: string[] }) => void | Promise<void>
* }} opts
*/
async activate(opts) {
if (this.active) await this.deactivate()
this.store = opts.store
this.swarm = opts.swarm
this.Hyperdrive = opts.Hyperdrive
this.personalDrive = opts.personalDrive
this.disk = opts.disk
this.vfsMountRef = opts.vfsMountRef
const b = opts.bootstrap
this.bootstrap =
Array.isArray(b) && b.length ? b : (parseBootstrapEnv() ?? null)
this.registry = await loadHdmsRegistry(this.personalDrive)
this.byLabel.clear()
this.disk.auxiliaryDrives = []
for (const entry of this.registry.drives) {
try {
await this._openEntry(entry)
} catch (err) {
bareOsHostBooterWarn(
'hdms_skip_drive',
'skip drive ' + entry.label,
err?.message || String(err)
)
}
}
this.vfsMountRef.getMounts = () => this.getMountMap()
this.active = true
if (typeof opts.onAfterActivate === 'function') {
try {
await opts.onAfterActivate({ labels: [...this.byLabel.keys()] })
} catch (e) {
bareOsHostBooterWarn(
'hdms_on_after_activate',
'onAfterActivate failed',
e?.message || String(e)
)
}
}
}
async deactivate() {
if (this.autopass) {
try {
await this.autopass.close()
} catch (_) {}
this.autopass = null
}
for (const { drive, entry } of this.byLabel.values()) {
try {
if (this.swarm && drive.discoveryKey) {
this.swarm.leave(drive.discoveryKey)
}
} catch (_) {}
try {
await drive.close()
} catch (_) {}
}
this.byLabel.clear()
if (this.disk) this.disk.auxiliaryDrives = []
if (this.vfsMountRef) {
this.vfsMountRef.getMounts = () => new Map()
}
this.registry = null
this.active = false
}
/**
* @param {HdmsRegistryEntry} entry
*/
async _openEntry(entry) {
const Hyperdrive = this.Hyperdrive
const store = this.store
const swarm = this.swarm
if (!Hyperdrive || !store || !swarm) throw new Error('HDMS not configured')
let drive
if (entry.mode === 'writable' && entry.ns) {
const ns = store.namespace(entry.ns, { writable: true })
drive = new Hyperdrive(ns)
} else if (
entry.mode === 'writable' &&
entry.key &&
entry.signerKey &&
entry.writerSecretHex &&
!entry.ns
) {
const driveKey = idEnc.decode(entry.key)
const signerPk = idEnc.decode(entry.signerKey)
const secretKey = writerSecretBytesFromHex(entry.writerSecretHex)
if (!hcCrypto.validateKeyPair({ publicKey: signerPk, secretKey })) {
throw new Error('writer secret does not match signer key (re-pair with a fresh invite)')
}
const core = store.get({
key: driveKey,
keyPair: { publicKey: signerPk, secretKey },
exclusive: true
})
const bee = new Hyperbee(core, {
keyEncoding: 'utf-8',
valueEncoding: 'json',
metadata: { contentFeed: null }
})
drive = new Hyperdrive(store, null, { _db: bee })
} else if (entry.mode === 'readonly' && entry.key) {
const key = idEnc.decode(entry.key)
drive = new Hyperdrive(store, key)
} else {
throw new Error('Bad registry entry')
}
await drive.ready()
swarm.join(drive.discoveryKey)
const done = drive.findingPeers()
// Hyperdrive blocks ops until findingPeers `done()` runs; `swarm.flush()` can
// hang when DHT/announce never settles — always finish after a bounded wait.
const flushMsRaw = globalThis.process?.env?.BARE_OS_HDMS_SWARM_FLUSH_MS
let flushMs = 8000
if (flushMsRaw != null && String(flushMsRaw).trim() !== '') {
const n = Number(flushMsRaw)
if (Number.isFinite(n) && n >= 0) flushMs = Math.min(120_000, n)
}
const finishFinding = () => {
try {
done()
} catch (_) {}
}
const t = flushMs > 0 ? setTimeout(finishFinding, flushMs) : null
swarm.flush().then(
() => {
if (t) clearTimeout(t)
finishFinding()
},
() => {
if (t) clearTimeout(t)
finishFinding()
}
)
if (this.disk) {
this.disk.auxiliaryDrives.push(drive)
}
this.byLabel.set(entry.label, {
drive,
entry,
writable: entry.mode === 'writable'
})
for (const peer of this.disk?.peers || []) {
try {
drive.replicate(peer.mux.stream, { live: true, download: true })
} catch (_) {}
}
}
getMountMap() {
/** @type {Map<string, { drive: import('hyperdrive').default, writable: boolean }>} */
const m = new Map()
for (const [label, x] of this.byLabel) {
m.set(label, { drive: x.drive, writable: x.writable })
}
return m
}
/**
* @param {Record<string, unknown>} ctx
*/
assertLoggedIn(ctx) {
if (ctx.identity?.state !== 'unlocked') {
throw new Error('Log in to use Hyperdrive management (hdms)')
}
if (!this.active) throw new Error('HDMS inactive')
}
/**
* @param {Record<string, unknown>} ctx
*/
async list(ctx) {
this.assertLoggedIn(ctx)
const reg = this.registry
const inReg = new Set(
reg && Array.isArray(reg.drives) ? reg.drives.map((d) => d.label) : []
)
const lines = []
if (reg && reg.drives.length) {
for (const d of reg.drives) {
const k = d.key || '(local)'
lines.push(`${d.label}\t${d.mode}\t${k}`)
}
}
for (const [label, x] of this.byLabel) {
if (inReg.has(label)) continue
const d = x.entry
const k = d.key || '(local)'
lines.push(`${d.label}\t${d.mode}\t${k}\tephemeral`)
}
if (!lines.length) {
ctx.console.log('(no extra drives)')
return
}
for (const line of lines) ctx.console.log(line)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} label
*/
async create(ctx, label) {
this.assertLoggedIn(ctx)
assertValidHdmsLabel(label)
if (this.byLabel.has(label)) throw new Error('Label already exists')
const id = randomNsSuffix()
const ns = `bare-os-hdms-w-${id}`
/** @type {HdmsRegistryEntry} */
const entry = {
id,
label,
mode: 'writable',
ns,
key: ''
}
// Single Hyperdrive open: corestore uses exclusive db cores per namespace; a
// second `new Hyperdrive(ns)` while the first is still open deadlocks on ready().
await this._openEntry(entry)
const opened = this.byLabel.get(label)
if (!opened?.drive?.key) {
throw new Error('HDMS create failed (no drive key)')
}
entry.key = idEnc.encode(opened.drive.key)
this.registry.drives.push(entry)
await saveHdmsRegistry(this.personalDrive, this.registry)
const mounted = this.getMountMap().has(label)
if (!mounted) {
throw new Error(
'HDMS create succeeded but mount is not visible at /mnt/' + label
)
}
ctx.console.log(`Created ${label} key=${entry.key} mounted=/mnt/${label}`)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} label
* @param {string} keyZ32
* @param {{ persist?: boolean }} [opts]
*/
async addReadonly(ctx, label, keyZ32, opts = {}) {
const persist = opts.persist !== false
this.assertLoggedIn(ctx)
assertValidHdmsLabel(label)
if (this.byLabel.has(label)) throw new Error('Label already exists')
idEnc.decode(keyZ32)
const id = randomNsSuffix()
/** @type {HdmsRegistryEntry} */
const entry = {
id,
label,
mode: 'readonly',
key: keyZ32
}
if (persist) {
this.registry.drives.push(entry)
await saveHdmsRegistry(this.personalDrive, this.registry)
}
await this._openEntry(entry)
if (!persist) {
const slot = this.byLabel.get(label)
if (slot) slot.ephemeral = true
}
ctx.console.log(`Added readonly ${label}${persist ? '' : ' (ephemeral)'}`)
}
/**
* Writable Hyperdrive replica: `keyZ32` is drive id (manifest), `signerKeyZ32` is metadata signer pk.
* @param {Record<string, unknown>} ctx
* @param {string} label
* @param {string} keyZ32 drive public key (z32)
* @param {string} signerKeyZ32 metadata writer public key (z32)
* @param {string} writerSecretHex
* @param {{ persist?: boolean }} [opts]
*/
async addWritableReplica(
ctx,
label,
keyZ32,
signerKeyZ32,
writerSecretHex,
opts = {}
) {
const persist = opts.persist !== false
this.assertLoggedIn(ctx)
assertValidHdmsLabel(label)
if (this.byLabel.has(label)) throw new Error('Label already exists')
idEnc.decode(keyZ32)
const signerPk = idEnc.decode(signerKeyZ32)
const secretKey = writerSecretBytesFromHex(writerSecretHex)
if (!hcCrypto.validateKeyPair({ publicKey: signerPk, secretKey })) {
throw new Error('writer secret does not match signer key')
}
const id = randomNsSuffix()
/** @type {HdmsRegistryEntry} */
const entry = {
id,
label,
mode: 'writable',
key: keyZ32,
signerKey: signerKeyZ32,
writerSecretHex: b4a.toString(secretKey, 'hex')
}
if (persist) {
this.registry.drives.push(entry)
await saveHdmsRegistry(this.personalDrive, this.registry)
}
await this._openEntry(entry)
if (!persist) {
const slot = this.byLabel.get(label)
if (slot) slot.ephemeral = true
}
ctx.console.log(`Added writable ${label}${persist ? '' : ' (ephemeral)'}`)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} label
*/
async remove(ctx, label) {
this.assertLoggedIn(ctx)
const open = this.byLabel.get(label)
if (open?.ephemeral) {
try {
if (this.swarm && open.drive.discoveryKey) {
this.swarm.leave(open.drive.discoveryKey)
}
} catch (_) {}
try {
await open.drive.close()
} catch (_) {}
if (this.disk?.auxiliaryDrives) {
this.disk.auxiliaryDrives = this.disk.auxiliaryDrives.filter(
(d) => d !== open.drive
)
}
this.byLabel.delete(label)
ctx.console.log('Removed ' + label)
return
}
const idx = this.registry.drives.findIndex((d) => d.label === label)
if (idx < 0) throw new Error('Unknown label: ' + label)
const openReg = this.byLabel.get(label)
if (openReg) {
try {
if (this.swarm && openReg.drive.discoveryKey) {
this.swarm.leave(openReg.drive.discoveryKey)
}
} catch (_) {}
try {
await openReg.drive.close()
} catch (_) {}
if (this.disk?.auxiliaryDrives) {
this.disk.auxiliaryDrives = this.disk.auxiliaryDrives.filter(
(d) => d !== openReg.drive
)
}
this.byLabel.delete(label)
}
this.registry.drives.splice(idx, 1)
await saveHdmsRegistry(this.personalDrive, this.registry)
ctx.console.log('Removed ' + label)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} label
*/
async show(ctx, label) {
this.assertLoggedIn(ctx)
let e = this.registry.drives.find((d) => d.label === label)
if (!e) {
const open = this.byLabel.get(label)
if (open?.ephemeral) {
e = { ...open.entry, ephemeral: true }
}
}
if (!e) throw new Error('Unknown label: ' + label)
ctx.console.log(JSON.stringify(e, null, 2))
}
/**
* @param {Record<string, unknown>} ctx
* @param {boolean} readOnly Autopass writer role for the peer (not Hyperdrive R/W).
* @param {string} [driveLabel] Writable HDMS label to attach to the invite.
* @param {boolean} [shareReadWrite] When true with `driveLabel`, publish writer secret on `bare-os-hdms/pending-share-rw`.
*/
async invite(ctx, readOnly, driveLabel, shareReadWrite = false) {
this.assertLoggedIn(ctx)
await this._ensureAutopass()
const ap = this.autopass
if (!ap) throw new Error('HDMS autopass unavailable')
await removeBothHdmsShareKeys(ap)
if (driveLabel != null && String(driveLabel).trim() !== '') {
assertValidHdmsLabel(driveLabel)
const open = this.byLabel.get(driveLabel)
if (!open) throw new Error('Unknown label: ' + driveLabel)
if (open.entry.mode !== 'writable') {
throw new Error('hdms invite <label> requires a writable HDMS drive')
}
const keyZ32 = idEnc.encode(open.drive.key)
if (shareReadWrite) {
const kp = open.drive.core.keyPair
if (!kp?.secretKey || kp.secretKey.length !== 64) {
throw new Error('HDMS: drive has no writer secret')
}
const writerSecretHex = b4a.toString(kp.secretKey, 'hex')
const signerKeyZ32 = idEnc.encode(kp.publicKey)
await ap.add(
HDMS_AUTOPASS_SHARE_RW_KEY,
JSON.stringify({
label: driveLabel,
key: keyZ32,
signerKey: signerKeyZ32,
writerSecretHex
})
)
} else {
await ap.add(
HDMS_AUTOPASS_SHARE_KEY,
JSON.stringify({ label: driveLabel, key: keyZ32 })
)
}
if (ap.member) await ap.member.flushed()
} else if (ap.member) {
await ap.member.flushed()
}
const inv = await mintFreshHdmsAutopassInvite(ap, readOnly)
ctx.console.log(inv)
if (driveLabel != null && String(driveLabel).trim() !== '') {
if (shareReadWrite) {
ctx.console.log(
'Invite includes HDMS drive "' +
driveLabel +
'" with read/write (writer secret on Autopass ledger bare-os-hdms/pending-share-rw). Peer: hdms pair <invite>'
)
} else {
ctx.console.log(
'Invite includes HDMS drive "' +
driveLabel +
'". Peer: hdms pair <invite> (mounts read-only at /mnt/' +
driveLabel +
' or label-2… if that name exists locally; same swarm/bootstrap).'
)
}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} inviteZ32
* @param {{ persist?: boolean }} [pairOpts] Default persist true; false skips `registry.json` (ephemeral until removed).
*/
async pair(ctx, inviteZ32, pairOpts = {}) {
this.assertLoggedIn(ctx)
const persist = pairOpts.persist !== false
if (!inviteZ32 || typeof inviteZ32 !== 'string') {
throw new Error('Usage: hdms pair [--persist|--no-persist] <invite>')
}
const pairNs = this.store.namespace(
'bare-os-hdms-pair-' + randomNsSuffix(),
{
writable: true
}
)
const pairer = Autopass.pair(pairNs, inviteZ32.trim(), {
bootstrap: this.bootstrap
})
const pairWaitMs = parseHdmsPairWaitMs()
let pairTimeout = 0
const pairTimeoutP = new Promise((_, reject) => {
pairTimeout = setTimeout(() => {
reject(
new Error(
'hdms pair: timed out after ' +
pairWaitMs +
'ms waiting for inviter (inviter must be online with matching HYPERSWARM_BOOTSTRAP; set BARE_OS_HDMS_PAIR_WAIT_MS to adjust)'
)
)
}, pairWaitMs)
})
const finishedP = pairer.finished()
let pass
try {
pass = await Promise.race([finishedP, pairTimeoutP])
} catch (e) {
clearTimeout(pairTimeout)
try {
await pairer.close()
} catch (_) {}
void finishedP.catch(() => {})
throw e
}
clearTimeout(pairTimeout)
await pass.ready()
try {
const wk = pass.writerKey
ctx.console.log(
'Paired Autopass. writerKey=' +
(wk ? b4a.toString(wk, 'hex').slice(0, 16) + '…' : '?')
)
const pairReadyMs = parseHdmsPairReadyMs()
if (pairReadyMs > 0) {
await waitForAutopassMembersReady(pass, pairReadyMs)
}
const offer = await waitForAutopassShare(
pass,
Number(
globalThis.process?.env?.BARE_OS_HDMS_PAIR_SHARE_WAIT_MS ?? 25000
) || 25000
)
if (offer) {
const lbl = pickUniqueHdmsMountLabel(this.byLabel, offer.label)
const renamed = lbl !== offer.label
if (offer.kind === 'rw') {
await this.addWritableReplica(
ctx,
lbl,
offer.key,
offer.signerKey,
offer.writerSecretHex,
{
persist
}
)
ctx.console.log(
'HDMS read/write mount ready at /mnt/' +
lbl +
(renamed
? ' (inviter label "' + offer.label + '" was in use locally)'
: '') +
(persist ? '' : ' (ephemeral; not saved to registry)') +
' — same swarm/bootstrap as inviter.'
)
} else {
await this.addReadonly(ctx, lbl, offer.key, { persist })
ctx.console.log(
'HDMS read-only mount ready at /mnt/' +
lbl +
(renamed
? ' (inviter label "' + offer.label + '" was in use locally)'
: '') +
(persist ? '' : ' (ephemeral; not saved to registry)') +
' — replicate from swarm; not the same as Autopass R/W.'
)
}
} else {
ctx.console.log(
'No HDMS drive on this invite. Inviter can run: hdms invite [--read-only] [--rw] <label>'
)
}
} finally {
try {
await pass.close()
} catch (_) {}
}
}
async _ensureAutopass() {
if (this.autopass) return
const ns = this.store.namespace('bare-os-hdms-autopass', { writable: true })
this.autopass = new Autopass(ns, {
swarm: this.swarm,
replicate: true,
bootstrap: this.bootstrap
})
await this.autopass.ready()
attachAutopassReplicateOnce(this.swarm, () => this.autopass?.base)
try {
this.autopass.on('error', (err) => {
bareOsHostBooterWarn(
'hdms_autopass',
'Autopass error',
err?.message || String(err)
)
})
} catch (_) {}
}
}
/**
* @param {import('hyperswarm').default} swarm
* @param {() => unknown} getBase
*/
function attachAutopassReplicateOnce(swarm, getBase) {
const s = /** @type {{ _bareOsHdmsAutopassRepl?: boolean }} */ (swarm)
if (s._bareOsHdmsAutopassRepl) return
s._bareOsHdmsAutopassRepl = true
swarm.on('connection', (socket) => {
const base = getBase()
if (base && typeof base.replicate === 'function') {
try {
base.replicate(socket, { live: true })
} catch (_) {}
}
})
}
function parseBootstrapEnv() {
const raw = globalThis.process?.env?.HYPERSWARM_BOOTSTRAP
if (!raw || typeof raw !== 'string') return null
const parts = raw
.split(',')
.map((x) => x.trim())
.filter(Boolean)
return parts.length ? parts : null
}
/**
* @param {HdmsController} hdms
* @param {string[]} argv
* @param {Record<string, unknown>} ctx
*/
export async function runHdmsCli(hdms, argv, ctx) {
try {
const sub = argv[1]
const rest = argv.slice(2)
if (!sub || sub === 'help' || sub === '--help') {
ctx.console.log(
'hdms list | create <label> | add <label> <key> | remove <label> | show <label> | invite [--read-only] [--rw] [<label>] | pair [--persist|--no-persist] <invite>'
)
ctx.console.log(
'invite [--read-only] [--rw] <label>: each run clears the prior BlindPairing invite from Autopass, waits for the view to catch up, then mints an all-new z32. Omit --read-only for pairing (see man hdms).'
)
ctx.console.log(
'pair: default saves mount to registry (survives reboot). --no-persist keeps mount ephemeral. --persist is explicit default (no-op).'
)
ctx.console.log(
'pair waits for inviter (BARE_OS_HDMS_PAIR_WAIT_MS default 120s); then for drive offer (BARE_OS_HDMS_PAIR_SHARE_WAIT_MS default 25s).'
)
return
}
if (sub === 'list' || sub === 'ls') {
await hdms.list(ctx)
return
}
if (sub === 'create') {
await hdms.create(ctx, rest[0])
return
}
if (sub === 'add') {
await hdms.addReadonly(ctx, rest[0], rest[1])
return
}
if (sub === 'remove') {
await hdms.remove(ctx, rest[0])
return
}
if (sub === 'show') {
await hdms.show(ctx, rest[0])
return
}
if (sub === 'invite') {
const ro = rest.includes('--read-only')
const rwDrive = rest.includes('--rw')
const pos = rest.filter((x) => x !== '--read-only' && x !== '--rw')
const driveLabel = pos[0]
if (rwDrive && (!driveLabel || String(driveLabel).trim() === '')) {
ctx.console.error('hdms: invite --rw requires a writable drive <label>')
return
}
await hdms.invite(ctx, ro, driveLabel, rwDrive)
return
}
if (sub === 'pair') {
const parsed = parseHdmsPairArgv(rest)
if (!parsed.ok) {
ctx.console.error(parsed.error)
return
}
await hdms.pair(ctx, parsed.inviteTok, { persist: parsed.persist })
return
}
ctx.console.error('hdms: unknown subcommand (try hdms help)')
} catch (e) {
ctx.console.error('hdms: ' + (e?.message || e))
ctx.exitCode = 1
}
}
@@ -0,0 +1,111 @@
/**
* Autopass payload for HDMS drive sharing (invite → pair → mount).
* Kept separate from hdms-manager.js for small, Node-testable surface.
*/
import b4a from 'b4a'
import idEnc from 'hypercore-id-encoding'
/** Autopass record key: read-only replica offer (public key only). */
export const HDMS_AUTOPASS_SHARE_KEY = 'bare-os-hdms/pending-share'
/** Autopass record key: read/write replica (public key + writer secret). Legacy peers ignore this key. */
export const HDMS_AUTOPASS_SHARE_RW_KEY = 'bare-os-hdms/pending-share-rw'
/** Expected hex length for a Hypercore Ed25519 secret key (64 bytes). */
export const WRITER_SECRET_HEX_LEN = 128
/**
* @param {string} hex
* @returns {string} lowercase hex
*/
export function normalizeWriterSecretHex(hex) {
if (typeof hex !== 'string') throw new Error('Invalid writer secret')
const h = hex.trim().toLowerCase()
if (!/^[0-9a-f]{128}$/.test(h)) {
throw new Error('writerSecretHex must be 128 hex chars (64 bytes)')
}
return h
}
/**
* @param {string} hex
* @returns {Uint8Array}
*/
export function writerSecretBytesFromHex(hex) {
return b4a.from(normalizeWriterSecretHex(hex), 'hex')
}
/** @param {{ remove: (k: string) => Promise<unknown>, member?: { flushed: () => Promise<void> }, base?: { update?: () => Promise<void> } }} ap */
export async function removeBothHdmsShareKeys(ap) {
for (const k of [HDMS_AUTOPASS_SHARE_KEY, HDMS_AUTOPASS_SHARE_RW_KEY]) {
try {
await ap.remove(k)
if (ap.member) await ap.member.flushed()
try {
await ap.base?.update?.()
} catch (_) {}
} catch {
/* no prior offer */
}
}
}
/** @param {{ value?: unknown } | null | undefined} offer */
export function decodeShareOffer(offer) {
if (!offer || offer.value == null) return null
const v = offer.value
const raw =
typeof v === 'string' ? v : b4a.toString(/** @type {Uint8Array} */ (v))
try {
const j = JSON.parse(raw)
if (
j &&
typeof j.key === 'string' &&
typeof j.label === 'string' &&
j.key.length > 0
) {
return { label: j.label, key: j.key }
}
} catch {
/* ignore */
}
return null
}
/**
* RW payload uses `key` = Hyperdrive public key (manifest id, z32) for discovery/open,
* and `signerKey` = metadata writer Ed25519 public key (z32) matching `writerSecretHex`.
* @param {{ value?: unknown } | null | undefined} offer
* @returns {{ label: string, key: string, signerKey: string, writerSecretHex: string } | null}
*/
export function decodeRwShareOffer(offer) {
if (!offer || offer.value == null) return null
const v = offer.value
const raw =
typeof v === 'string' ? v : b4a.toString(/** @type {Uint8Array} */ (v))
try {
const j = JSON.parse(raw)
if (
j &&
typeof j.key === 'string' &&
typeof j.label === 'string' &&
typeof j.signerKey === 'string' &&
typeof j.writerSecretHex === 'string' &&
j.key.length > 0 &&
j.signerKey.length > 0
) {
idEnc.decode(j.signerKey)
idEnc.decode(j.key)
return {
label: j.label,
key: j.key,
signerKey: j.signerKey,
writerSecretHex: normalizeWriterSecretHex(j.writerSecretHex)
}
}
} catch {
/* ignore */
}
return null
}
@@ -0,0 +1,528 @@
/**
* Drive-resident /bin/holesail → ctx.bareOsRunHolesailCli (booter).
* Manages persisted tunnels under **`BARE_OS_HOLESAIL_STATE`** or **`~/.holesail/state.json`** (per **`$HOME`**).
* Live start/stop uses the **bare-holesail** initd unit after login (managed mode is the stock default).
*/
import {
bareHolesailManagedEnvForConnectionId,
bareHolesailManagedNormalizeEntry,
bareHolesailManagedReadState,
bareHolesailManagedRuntimeRunning,
bareHolesailManagedRuntimeUrl,
bareHolesailManagedServiceIsRunning,
bareHolesailManagedStartOne,
bareHolesailManagedStatePath,
bareHolesailManagedStopOne,
bareHolesailManagedSyncPersistedServerKey,
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>} */ ({})
}
/**
* @param {Record<string, unknown>} ctx
* @param {Record<string, string | undefined>} env
*/
async function holesailCliReadStateMergedForList(ctx, env) {
return bareHolesailManagedReadState(ctx, env)
}
function printHelp(ctx, prog) {
ctx.console.log(
`Usage: ${prog} help|path|list|status
${prog} show ID
${prog} add ID --server|--client [--key KEY] [--port N] [--host H] [--udp] [--secure] [--no-secure] [--log N|true|false]
${prog} edit ID [--port N] [--host H] [--clear-host] [--udp|--no-udp|--tcp] [--secure|--no-secure] [--key KEY] [--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.
show never prints seed material (ctor secret). list/show print the shareable hs:// URL.
edit updates fields in place (does not change server/client or seed). Live tunnels restart.
Upstream holesail is AGPL-3.0; see handbook/04-the-booter-runtime.md.`
)
}
/**
* @param {Record<string, unknown>} entry
* @param {string} id
* @param {boolean} live
* @param {string} runtimeUrl
*/
export function holesailCliFormatListRow(id, entry, live, runtimeUrl) {
const mode = entry.server ? 'server' : entry.client ? 'client' : '?'
const persistedUrl = String(entry.key ?? '').trim()
const url = (live ? String(runtimeUrl || '').trim() : '') || persistedUrl
const port = entry.port != null && entry.port !== '' ? String(entry.port) : ''
const host = String(entry.host ?? '').trim()
const udp = entry.udp != null ? String(!!entry.udp) : ''
const secure = entry.secure != null ? String(!!entry.secure) : ''
const bits = [
id,
mode,
`enabled=${String(!!entry.enabled)}`,
`live=${String(!!live)}`
]
if (port) bits.push(`port=${port}`)
if (host) bits.push(`host=${host}`)
if (udp) bits.push(`udp=${udp}`)
if (secure) bits.push(`secure=${secure}`)
if (url) bits.push(`url=${url.slice(0, 120)}`)
return bits.join('\t')
}
/**
* 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 {string[]} flagArgs tokens including ID for "edit"
* @returns {{ ok: true, id: string, patch: Record<string, unknown> } | { ok: false, err: string }}
*/
export function holesailCliParseEdit(flagArgs) {
if (flagArgs.length < 1) {
return { ok: false, err: 'holesail edit: missing ID' }
}
const idOk = bareHolesailManagedValidateId(flagArgs[0])
if (!idOk.ok) return { ok: false, err: `holesail edit: ${idOk.err}` }
/** @type {Record<string, unknown>} */
const patch = {}
let touched = false
for (let i = 1; i < flagArgs.length; i++) {
const a = flagArgs[i]
if (holesailCliTokenLooksLikeHolesailConnectionString(a)) {
if (patch.key) return { ok: false, err: 'holesail edit: duplicate key' }
patch.key = a
touched = true
} else if (a === '--key' && flagArgs[i + 1]) {
patch.key = flagArgs[++i]
touched = true
} else if (a === '--port' && flagArgs[i + 1]) {
patch.port = flagArgs[++i]
touched = true
} else if (a === '--host' && flagArgs[i + 1]) {
patch.host = flagArgs[++i]
touched = true
} else if (a === '--clear-host') {
patch.host = ''
touched = true
} else if (a === '--udp') {
patch.udp = true
touched = true
} else if (a === '--no-udp' || a === '--tcp') {
patch.udp = false
touched = true
} else if (a === '--secure') {
patch.secure = true
touched = true
} else if (a === '--no-secure') {
patch.secure = false
touched = true
} else if (a === '--log' && flagArgs[i + 1]) {
patch.log = flagArgs[++i]
touched = true
} else if (a === '--server' || a === '--client') {
return { ok: false, err: 'holesail edit: cannot change server/client (remove and add)' }
} else {
return { ok: false, err: `holesail edit: unknown or incomplete flag: ${a}` }
}
}
if (!touched) return { ok: false, err: 'holesail edit: no fields to change' }
return { ok: true, id: idOk.id, patch }
}
/**
* @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') {
let { path, state } = await holesailCliReadStateMergedForList(ctx, env)
const idsSorted = Object.keys(state.connections).sort()
let resynced = false
for (const id of idsSorted) {
if (!bareHolesailManagedRuntimeRunning(id)) continue
const raw0 = state.connections[id]
const norm0 = bareHolesailManagedNormalizeEntry(raw0)
if (norm0.ok && norm0.entry.server) {
const w = await bareHolesailManagedSyncPersistedServerKey(
ctx,
bareHolesailManagedEnvForConnectionId(env, id),
id
)
if (w) resynced = true
}
}
if (resynced) {
;({ path, state } = await holesailCliReadStateMergedForList(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 live = bareHolesailManagedRuntimeRunning(id)
const runtimeUrl = live ? bareHolesailManagedRuntimeUrl(id).trim() : ''
if (norm.ok) {
ctx.console.log(holesailCliFormatListRow(id, norm.entry, live, runtimeUrl))
} else {
ctx.console.log(`${id}\t?\tenabled=?\tlive=${live}\tINVALID: ${norm.err}`)
}
}
ctx.exitCode = 0
return
}
if (sub === 'status') {
const { path, state } = await holesailCliReadStateMergedForList(ctx, env)
const ids = Object.keys(state.connections).sort()
let enabled = 0
let live = 0
let servers = 0
let clients = 0
for (const id of ids) {
const norm = bareHolesailManagedNormalizeEntry(state.connections[id])
if (!norm.ok) continue
if (norm.entry.enabled) enabled++
if (norm.entry.server) servers++
if (norm.entry.client) clients++
if (bareHolesailManagedRuntimeRunning(id)) live++
}
const daemon = bareHolesailManagedServiceIsRunning()
ctx.console.log(`state: ${path}`)
ctx.console.log(`daemon: ${daemon ? 'managed bare-holesail running' : 'no managed daemon (live ops limited)'}`)
ctx.console.log(
`connections: ${ids.length}\tenabled=${enabled}\tlive=${live}\tserver=${servers}\tclient=${clients}`
)
ctx.exitCode = 0
return
}
if (sub === 'show') {
const idOk = bareHolesailManagedValidateId(rest[0] || '')
if (!idOk.ok) {
ctx.console.error(`holesail show: ${idOk.err}`)
ctx.exitCode = 2
return
}
const envW = bareHolesailManagedEnvForConnectionId(env, idOk.id)
const { path, state } = await bareHolesailManagedReadState(ctx, envW)
const raw = state.connections[idOk.id]
if (!raw || typeof raw !== 'object') {
ctx.console.error(`holesail show: unknown ${idOk.id}`)
ctx.exitCode = 1
return
}
const norm = bareHolesailManagedNormalizeEntry(raw)
if (!norm.ok) {
ctx.console.error(`holesail show: ${norm.err}`)
ctx.exitCode = 1
return
}
const live = bareHolesailManagedRuntimeRunning(idOk.id)
const persistedUrl = String(norm.entry.key ?? '').trim()
const runtimeUrl = live ? bareHolesailManagedRuntimeUrl(idOk.id).trim() : ''
const url = runtimeUrl || persistedUrl
const seedSet = String(norm.entry.seed ?? '').trim() ? 'yes' : 'no'
const daemon = bareHolesailManagedServiceIsRunning()
ctx.console.log(`id\t${idOk.id}`)
ctx.console.log(`mode\t${norm.entry.server ? 'server' : 'client'}`)
ctx.console.log(`enabled\t${String(!!norm.entry.enabled)}`)
ctx.console.log(`live\t${String(!!live)}`)
if (norm.entry.port != null) ctx.console.log(`port\t${norm.entry.port}`)
if (norm.entry.host) ctx.console.log(`host\t${norm.entry.host}`)
if (norm.entry.udp != null) ctx.console.log(`udp\t${String(!!norm.entry.udp)}`)
if (norm.entry.secure != null) ctx.console.log(`secure\t${String(!!norm.entry.secure)}`)
if (norm.entry.log != null) ctx.console.log(`log\t${norm.entry.log}`)
ctx.console.log(`seed\t${seedSet}`)
if (url) ctx.console.log(`url\t${url}`)
ctx.console.log(`state\t${path}`)
ctx.console.log(`daemon\t${daemon ? 'running' : 'stopped'}`)
ctx.exitCode = 0
return
}
if (sub === 'add') {
const parsed = holesailCliParseAdd(rest)
if (!parsed.ok) {
ctx.console.error(parsed.err)
ctx.exitCode = 2
return
}
const envW = bareHolesailManagedEnvForConnectionId(env, parsed.id)
const { path, state } = await bareHolesailManagedReadState(ctx, envW)
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, envW, state)
ctx.console.log(`wrote ${path}`)
if (bareHolesailManagedServiceIsRunning()) {
try {
await bareHolesailManagedStartOne(ctx, envW, 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 envW = bareHolesailManagedEnvForConnectionId(env, idOk.id)
const { path, state } = await bareHolesailManagedReadState(ctx, envW)
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, envW, 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,
bareHolesailManagedEnvForConnectionId(env, idOk.id),
idOk.id
)
ctx.console.log(`started ${idOk.id}`)
ctx.exitCode = 0
return
}
if (sub === 'edit') {
const parsed = holesailCliParseEdit(rest)
if (!parsed.ok) {
ctx.console.error(parsed.err)
ctx.exitCode = 2
return
}
const envW = bareHolesailManagedEnvForConnectionId(env, parsed.id)
const { path, state } = await bareHolesailManagedReadState(ctx, envW)
const raw = state.connections[parsed.id]
if (!raw || typeof raw !== 'object') {
ctx.console.error(`holesail edit: unknown ${parsed.id}`)
ctx.exitCode = 1
return
}
const merged = { .../** @type {Record<string, unknown>} */ (raw), ...parsed.patch }
if (Object.prototype.hasOwnProperty.call(parsed.patch, 'host') && parsed.patch.host === '') {
delete merged.host
}
const norm = bareHolesailManagedNormalizeEntry(merged)
if (!norm.ok) {
ctx.console.error(`holesail edit: ${norm.err}`)
ctx.exitCode = 2
return
}
const prev = /** @type {Record<string, unknown>} */ (raw)
if (prev.seed) norm.entry.seed = prev.seed
if (prev.server) {
norm.entry.server = true
norm.entry.client = false
} else if (prev.client) {
norm.entry.client = true
norm.entry.server = false
}
if (prev.enabled !== undefined && parsed.patch.enabled === undefined) {
norm.entry.enabled = prev.enabled
}
state.connections[parsed.id] = norm.entry
await bareHolesailManagedWriteState(ctx, envW, state)
ctx.console.log(`wrote ${path}`)
const wasLive = bareHolesailManagedRuntimeRunning(parsed.id)
if (wasLive) {
await bareHolesailManagedStopOne(ctx, parsed.id)
if (norm.entry.enabled && bareHolesailManagedServiceIsRunning()) {
try {
await bareHolesailManagedStartOne(ctx, envW, parsed.id)
ctx.console.log(`restarted ${parsed.id}`)
} catch (e) {
const msg = (e && /** @type {{ message?: string }} */ (e).message) || String(e)
ctx.console.error(`holesail: saved but restart failed: ${msg}`)
ctx.exitCode = 1
return
}
}
}
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 envW = bareHolesailManagedEnvForConnectionId(env, idOk.id)
const { path, state } = await bareHolesailManagedReadState(ctx, envW)
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, envW, 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, envW, 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
}
}
@@ -0,0 +1,919 @@
import b4a from 'b4a'
import c from 'compact-encoding'
import {
PROTOCOL_NAME,
PROTOCOL_APP_CHANNEL_NAME,
PROTOCOL_CAP_CHANNEL_NAME,
PROTOCOL_CHAT_CHANNEL_NAME,
PROTOCOL_MESHDROP_CHANNEL_NAME
} from 'bare-os-protocol/constants.js'
import { bareOsChatMuxEnabled } from './bare-os-chat-service.js'
import { bareOsMeshdropMuxEnabled } from './bare-os-meshdrop-service.js'
import {
bareOsHostBooterInfo,
bareOsHostBooterWarn
} from './bare-os-host-booter-log.js'
import { getKernelCapabilityWords } from 'bare-os-protocol'
/**
* Host-side booter log for swarm disk (stderr JSON when BARE_OS_BOOT_TRACE=json|ndjson; else stderr or console.warn).
* @param {'info'|'warn'} level
* @param {string} message
* @param {Record<string, unknown> | null} [detail]
*/
function emitSwarmDiskHostLog(level, message, detail = null) {
const env = globalThis.process?.env
const trace =
env &&
(env.BARE_OS_BOOT_TRACE === 'json' || env.BARE_OS_BOOT_TRACE === 'ndjson')
const err = globalThis.process?.stderr
if (trace && err && typeof err.write === 'function') {
err.write(
`${JSON.stringify({
type: 'booterHost',
bootTraceSchemaVersion: 2,
component: 'swarm_disk',
level,
message,
detail,
ts: Date.now()
})}\n`
)
}
if (level === 'warn') {
bareOsHostBooterWarn(
'swarm_disk',
message,
detail ? JSON.stringify(detail) : ''
)
return
}
bareOsHostBooterInfo(
'swarm_disk',
message,
detail ? JSON.stringify(detail) : ''
)
}
/** Default max wait for block 0 (MBR) before failing boot (override **`BARE_OS_MBR_READ_TIMEOUT_MS`**). */
const MBR_READ_TIMEOUT_MS_DEFAULT = 60_000
/** Cap-channel (`bare-os-cap-v1`) message size bound when **`BARE_OS_PROTOMUX_CAP_CHANNEL`** is enabled. */
export const BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES = 65536
/**
* Max wait (ms) for block 0 / MBR replication before boot failure.
* Exported for microbench / unit introspection (same rules as booter).
* @param {SwarmDisk} disk
*/
export function mbrReadTimeoutMsForDisk(disk) {
const env = globalThis.process?.env
const raw = String(env?.BARE_OS_MBR_READ_TIMEOUT_MS ?? '').trim()
if (raw) {
const n = Math.floor(Number(raw))
if (Number.isFinite(n) && n >= 3000) return Math.min(n, 600_000)
}
const adapt =
env?.BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE === '1' ||
env?.BARE_OS_MBR_READ_TIMEOUT_ADAPTIVE === 'true'
if (adapt && disk && disk.peers && typeof disk.peers.size === 'number') {
const n = disk.peers.size
if (n <= 1) return Math.min(120_000, MBR_READ_TIMEOUT_MS_DEFAULT + 30_000)
if (n >= 4) return Math.max(45_000, MBR_READ_TIMEOUT_MS_DEFAULT - 15_000)
}
return MBR_READ_TIMEOUT_MS_DEFAULT
}
/**
* Interval (ms) to re-send pending block read requests to all peers while waiting for MBR / block data.
* **`0`** disables periodic rebroadcast (initial + per-connect sends only).
* Override **`BARE_OS_MBR_READ_REBROADCAST_MS`** (defaults **5000**, clamp **2000****60000**).
* @param {Record<string, string | undefined> | null | undefined} env
*/
export function mbrReadRebroadcastMsFromEnv(env) {
const raw = String(env?.BARE_OS_MBR_READ_REBROADCAST_MS ?? '').trim()
if (raw === '0' || raw.toLowerCase() === 'false') return 0
const n = Math.floor(Number(raw))
if (Number.isFinite(n) && n >= 0) {
if (n === 0) return 0
return Math.min(60_000, Math.max(2_000, n))
}
return 5_000
}
export class SwarmDisk {
constructor() {
this.localRAM = new Map()
this.peers = new Set()
this.pendingReads = new Map()
this.pendingSearches = new Map()
this.pendingRpc = new Map()
this.searchIdCounter = 0
this.rpcIdCounter = 0
this.drive = null
this.personalDrive = null
this.os = null
/** @type {import('hyperdrive').default[]} */
this.auxiliaryDrives = []
/** @type {Record<string, unknown> | null} Last `bare_os.capabilities` RPC result (or error object). */
this.seedCapabilityInfo = null
/** @type {Record<string, unknown> | null} Last `bare_os.replication_status` RPC result when available. */
this.seedReplicationStatus = null
/** @type {Record<string, unknown> | null} */
this.seedManifestHints = null
/** @type {Record<string, unknown> | null} */
this.seedPeerHealth = null
/** @type {Record<string, unknown> | null} */
this.seedStagingSlot = null
/** @type {Record<string, unknown> | null} */
this.seedReplicationQueue = null
/** @type {Record<string, unknown> | null} */
this.seedCapabilityAttestation = null
/** @type {Record<string, unknown> | null} */
this.seedMbrLayout = null
/** @type {Record<string, unknown> | null} */
this.seedSnapshotHints = null
/** @type {Record<string, unknown> | null} */
this.seedPeerFirewallStats = null
/** @type {Record<string, unknown> | null} */
this.seedReplicationPlan = null
/** @type {Record<string, unknown> | null} */
this.seedDhtBootstrapHint = null
/** @type {Record<string, unknown> | null} */
this.seedSnapshotChain = null
/** @type {Record<string, unknown> | null} */
this.seedMirrorCompactionHint = null
/** @type {Record<string, unknown> | null} */
this.seedUpdaterState = null
/** @type {Record<string, unknown> | null} */
this.seedBlindPeerTopologyV2 = null
/** @type {Record<string, unknown> | null} */
this.seedCompactPing = null
/** @type {Record<string, unknown> | null} */
this.seedCorestoreStats = null
/** @type {Record<string, unknown> | null} */
this.seedSnapshotManifestSlice = null
/** @type {Record<string, unknown> | null} */
this.seedMirrorDriveHintV2 = null
/** @type {Record<string, unknown> | null} */
this.seedHrpcRegistrySummary = null
/** @type {Record<string, unknown> | null} */
this.seedProtomuxCapabilityAd = null
/** Cumulative count of buffers received on optional Protomux app-channel pairs (operator observability). */
this.protomuxAppChannelRxTotal = 0
/** Cumulative count of buffers received on optional Protomux cap-channel pairs (`bare-os-cap-v1`). */
this.protomuxCapChannelRxTotal = 0
/** Cumulative chat `event` messages received on `bare-os-chat-v1` (when enabled). */
this.protomuxChatChannelRxTotal = 0
/** Cumulative meshdrop envelopes received on `bare-os-meshdrop-v1` (when enabled). */
this.protomuxMeshdropChannelRxTotal = 0
/** @type {ReturnType<import('./bare-os-chat-service.js').createBareOsChatService> | null} */
this.bareOsChatService = null
/** @type {ReturnType<import('./bare-os-meshdrop-service.js').createBareOsMeshdropService> | null} */
this.bareOsMeshdropService = null
/** @type {Record<string, unknown> | null} Host-requested Hyperswarm caps (from env); surfaced on disk.os RPC. */
this.swarmConnectionBudget = null
/** @type {Record<string, unknown> | null} */
this.seedDhtAddressBook = null
/** @type {Record<string, unknown> | null} */
this.seedReplicationThrottleHint = null
/** @type {Record<string, unknown> | null} */
this.seedBundlebeeStage = null
/** @type {Record<string, unknown> | null} */
this.seedHttpDhtProxyHint = null
/** @type {Record<string, unknown> | null} */
this.seedProtomuxRpcPoolHint = null
/** @type {Record<string, unknown> | null} */
this.seedHyperblobStoreHint = null
/** @type {Record<string, unknown> | null} */
this.seedSigningRequestQueueHint = null
/** @type {Record<string, unknown> | null} */
this.seedCoreStorageLayoutHint = null
/** @type {Record<string, unknown> | null} */
this.seedMirrorDriveCompactionV3 = null
/** @type {Record<string, unknown> | null} */
this.seedBundlebeeCliStage = null
/** @type {Record<string, unknown> | null} */
this.seedReadyGuardV2 = null
/** @type {Record<string, unknown> | null} */
this.seedBlindRelayCircuitHint = null
/** @type {Record<string, unknown> | null} */
this.seedHttpDhtProxyRoutes = null
/** @type {string[]} MBR-derived drive key hex list (primary + failovers). */
this.mbrKeysHex = []
/**
* 512-byte MBR block used at boot (copy); enables peer block-0 service when mirrored to {@link SwarmDisk#localRAM}.
* @type {Uint8Array | null}
*/
this.bootMbr512 = null
/** True after peer system seed eligibility passed and MBR was published to `localRAM`. */
this.peerSystemSeedActive = false
/**
* Local Noise wire static key (32 bytes), last seen from `Hyperswarm` socket `publicKey`.
* Peers validate chat `senderPk` against `socket.remotePublicKey`, which matches **this** — not `swarm.keyPair.publicKey`.
*/
this.localNoiseWirePk = /** @type {Uint8Array | null} */ (null)
}
/**
* One JSON-RPC-style round-trip to a specific peer.
* @param {unknown} peer
* @param {string} module
* @param {string} method
* @param {string[]} args
* @param {number} timeoutMs
* @returns {Promise<unknown>}
*/
_rpcOne(peer, module, method, args, timeoutMs) {
const id = this.rpcIdCounter++
return new Promise((resolve, reject) => {
const to = setTimeout(() => {
this.pendingRpc.delete(id)
reject(new Error('swarm-disk rpc: timeout'))
}, timeoutMs)
this.pendingRpc.set(id, (m) => {
clearTimeout(to)
if (m.success) {
const r = m.result || ''
try {
resolve(r ? JSON.parse(r) : null)
} catch {
resolve(r)
}
} else {
reject(new Error(m.error || 'swarm-disk rpc failed'))
}
})
const chan =
/** @type {{ chan: { messages: { send: (m: unknown) => void }[] } }} */ (
peer
).chan
void chan.fullyOpened().then((opened) => {
if (!opened) return
if (!this.pendingRpc.has(id)) return
try {
chan.messages[5].send({ id, module, method, args })
} catch (err) {
this.pendingRpc.delete(id)
clearTimeout(to)
reject(
err instanceof Error
? err
: new Error((err && err.message) || String(err))
)
}
})
})
}
/**
* JSON-RPC-style call to swarm peers. For **`bare_os`**, tries each peer in turn when
* **`BARE_OS_SWARM_RPC_TRY_PEERS`** is not **`0`/`false`** (default: try all).
* **`capabilities`** responses must include **`kernelCapabilityWords`** (wire v2).
* @param {string} module
* @param {string} method
* @param {string[]} [args]
* @param {number} [timeoutMs]
* @returns {Promise<unknown>}
*/
async rpc(module, method, args = [], timeoutMs = 8000) {
if (!this.peers.size) throw new Error('swarm-disk rpc: no peers')
const peers = [...this.peers]
const mod = String(module || '')
const meth = String(method || '')
const env = globalThis.process?.env
const tryAll =
String(env?.BARE_OS_SWARM_RPC_TRY_PEERS ?? '1').toLowerCase() !== '0' &&
String(env?.BARE_OS_SWARM_RPC_TRY_PEERS ?? '1').toLowerCase() !==
'false' &&
mod === 'bare_os' &&
peers.length > 1
const perPeerMs = tryAll
? Math.min(
2500,
Math.max(800, Math.floor(timeoutMs / Math.min(peers.length, 4)))
)
: timeoutMs
if (tryAll) {
/** @type {Error | null} */
let lastErr = null
for (const peer of peers) {
try {
const r = await this._rpcOne(peer, mod, meth, args, perPeerMs)
if (meth === 'capabilities') {
const words = getKernelCapabilityWords(r)
if (!words) {
lastErr = new Error(
'swarm-disk rpc: capabilities missing kernelCapabilityWords'
)
continue
}
}
return r
} catch (e) {
lastErr =
e instanceof Error ? e : new Error((e && e.message) || String(e))
}
}
throw lastErr || new Error('swarm-disk rpc: all peers failed')
}
return this._rpcOne(peers[0], mod, meth, args, timeoutMs)
}
/**
* @param {import('corestore').default} store
* @param {import('hyperswarm').default} swarm
* @param {import('hyperdrive').default} Hyperdrive
*/
async initPersonalDrive(store, swarm, Hyperdrive) {
const localStore = store.namespace('bare-os-personal-v1', {
writable: true
})
this.personalDrive = new Hyperdrive(localStore)
await this.personalDrive.ready()
if (!this.personalDrive.writable) {
try {
await this.personalDrive.close()
} catch (_) {}
this.personalDrive = new Hyperdrive(localStore)
await this.personalDrive.ready()
}
if (!this.personalDrive.writable) {
emitSwarmDiskHostLog(
'warn',
'[bare-os-booter] Personal Hyperdrive is not writable — identity and $HOME writes will fail. Check Corestore path permissions and that no other process holds the store read-only.',
{
writable: false,
idPrefix: b4a.toString(this.personalDrive.id, 'hex').slice(0, 16)
}
)
}
emitSwarmDiskHostLog(
'info',
'Personal Hyperdrive mounted; joining swarm discovery',
{
idPrefix: b4a.toString(this.personalDrive.id, 'hex').slice(0, 16),
writable: this.personalDrive.writable
}
)
swarm.join(this.personalDrive.discoveryKey)
}
addPeer(mux, socket) {
const disk = this
/** @type {any} */
let chan
const context = {
onread(index) {
const data = disk.localRAM.get(index)
if (data) chan.messages[1].send({ index, data })
},
ondata(m) {
const cb = disk.pendingReads.get(m.index)
if (cb) {
disk.pendingReads.delete(m.index)
cb(m.data)
}
},
ongossip() {},
async onsearchreq(m) {
const matches = disk.os ? await disk.os.searchLocal?.(m.query) : []
chan.messages[4].send({ id: m.id, matches: matches || [] })
},
onsearchres(m) {
const st = disk.pendingSearches.get(m.id)
if (!st || st.settled) return
st.results.push(m.matches || [])
st.remaining--
if (st.remaining <= 0) st.finish()
},
async onrpcreq(m) {
if (!disk.os) {
chan.messages[6].send({
id: m.id,
success: false,
result: '',
error: 'OS not initialized'
})
return
}
try {
const result = await disk.os.execRpc?.(m.module, m.method, m.args)
chan.messages[6].send({
id: m.id,
success: true,
result: String(result ?? ''),
error: ''
})
} catch (err) {
chan.messages[6].send({
id: m.id,
success: false,
result: '',
error: err.message
})
}
},
onrpcres(m) {
const cb = disk.pendingRpc.get(m.id)
if (cb) {
disk.pendingRpc.delete(m.id)
cb(m)
}
}
}
chan = mux.createChannel({ protocol: PROTOCOL_NAME, userData: context })
chan.addMessage({
encoding: c.uint32,
onmessage: (index, ch) => ch.userData.onread(index)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.index)
c.buffer.preencode(state, m.data)
},
encode(state, m) {
c.uint32.encode(state, m.index)
c.buffer.encode(state, m.data)
},
decode(state) {
return { index: c.uint32.decode(state), data: c.buffer.decode(state) }
}
},
onmessage: (m, ch) => ch.userData.ondata(m)
})
chan.addMessage({
encoding: c.buffer,
onmessage: (bitfield, ch) => ch.userData.ongossip(bitfield)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.string.preencode(state, m.query)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.string.encode(state, m.query)
},
decode(state) {
return { id: c.uint32.decode(state), query: c.string.decode(state) }
}
},
onmessage: (m, ch) => ch.userData.onsearchreq(m)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.array(c.string).preencode(state, m.matches)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.array(c.string).encode(state, m.matches)
},
decode(state) {
return {
id: c.uint32.decode(state),
matches: c.array(c.string).decode(state)
}
}
},
onmessage: (m, ch) => ch.userData.onsearchres(m)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.string.preencode(state, m.module)
c.string.preencode(state, m.method)
c.array(c.string).preencode(state, m.args)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.string.encode(state, m.module)
c.string.encode(state, m.method)
c.array(c.string).encode(state, m.args)
},
decode(state) {
return {
id: c.uint32.decode(state),
module: c.string.decode(state),
method: c.string.decode(state),
args: c.array(c.string).decode(state)
}
}
},
onmessage: (m, ch) => ch.userData.onrpcreq(m)
})
chan.addMessage({
encoding: {
preencode(state, m) {
c.uint32.preencode(state, m.id)
c.bool.preencode(state, m.success)
c.string.preencode(state, m.result)
c.string.preencode(state, m.error)
},
encode(state, m) {
c.uint32.encode(state, m.id)
c.bool.encode(state, m.success)
c.string.encode(state, m.result || '')
c.string.encode(state, m.error || '')
},
decode(state) {
return {
id: c.uint32.decode(state),
success: c.bool.decode(state),
result: c.string.decode(state),
error: c.string.decode(state)
}
}
},
onmessage: (m, ch) => ch.userData.onrpcres(m)
})
chan.open()
const appChOn =
globalThis.process &&
globalThis.process.env &&
(globalThis.process.env.BARE_OS_PROTOMUX_APP_CHANNEL === '1' ||
globalThis.process.env.BARE_OS_PROTOMUX_APP_CHANNEL === 'true')
if (appChOn) {
mux.pair({ protocol: PROTOCOL_APP_CHANNEL_NAME }, (achan) => {
achan.addMessage({
encoding: c.buffer,
onmessage: () => {
try {
this.protomuxAppChannelRxTotal++
} catch {
/* ignore */
}
}
})
achan.open()
})
}
const capChOn =
globalThis.process &&
globalThis.process.env &&
(globalThis.process.env.BARE_OS_PROTOMUX_CAP_CHANNEL === '1' ||
globalThis.process.env.BARE_OS_PROTOMUX_CAP_CHANNEL === 'true')
if (capChOn) {
mux.pair({ protocol: PROTOCOL_CAP_CHANNEL_NAME }, (cchan) => {
cchan.addMessage({
encoding: c.buffer,
onmessage: (buf) => {
try {
const n = buf ? buf.byteLength : 0
if (n > BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES) {
emitSwarmDiskHostLog('warn', 'protomux_cap_payload_oversized', {
byteLength: n,
maxBytes: BARE_OS_PROTOMUX_CAP_CHANNEL_MESSAGE_MAX_BYTES
})
return
}
this.protomuxCapChannelRxTotal++
} catch {
/* ignore */
}
}
})
cchan.open()
})
}
const peer = {
chan,
mux,
socket,
id: null,
chatChan: null,
meshdropChan: null
}
if (
bareOsChatMuxEnabled(globalThis.process?.env) &&
this.bareOsChatService &&
mux.stream &&
!mux.stream.destroyed
) {
try {
this.bareOsChatService.pairOnMux(this, mux, socket, peer)
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_chat_pair_on_connect_failed', {
message:
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
if (
bareOsMeshdropMuxEnabled(globalThis.process?.env) &&
this.bareOsMeshdropService &&
mux.stream &&
!mux.stream.destroyed
) {
try {
this.bareOsMeshdropService.pairOnMux(this, mux, socket, peer)
} catch (e) {
emitSwarmDiskHostLog(
'warn',
'bare_os_meshdrop_pair_on_connect_failed',
{
message:
(e && /** @type {{ message?: string }} */ (e).message) ||
String(e)
}
)
}
}
/** Sync local Noise static key once the secret stream exposes `publicKey` (may follow handshake). */
const cacheLocalNoiseWirePk = () => {
if (socket.publicKey && socket.publicKey.byteLength === 32) {
disk.localNoiseWirePk = b4a.from(socket.publicKey)
}
}
cacheLocalNoiseWirePk()
this.peers.add(peer)
const pendingIndices = [...this.pendingReads.keys()]
for (const idx of pendingIndices) {
this._sendReadIndexToPeer(peer, idx)
}
const collabNd =
globalThis.process &&
globalThis.process.env &&
(globalThis.process.env.BARE_OS_COLLAB_SESSION_NDJSON === '1' ||
globalThis.process.env.BARE_OS_COLLAB_SESSION_NDJSON === 'true')
if (collabNd) {
emitSwarmDiskHostLog('info', 'collab_session_peer', {
peerCount: this.peers.size,
capChannelEnabled: capChOn,
appChannelEnabled: appChOn,
note: 'Host NDJSON preview; mirror to personal-drive tooling off-guest if policy allows.'
})
}
const setPeerId = () => {
if (socket.remotePublicKey && !peer.id) {
peer.id = b4a.toString(socket.remotePublicKey, 'hex')
return true
}
if (socket.handshakeHash && !peer.id) {
peer.id = b4a.toString(socket.handshakeHash, 'hex')
return true
}
return false
}
socket.on('handshake', () => {
setPeerId()
cacheLocalNoiseWirePk()
})
if (!setPeerId()) {
let attempts = 0
const tryAgain = () => {
attempts++
if (attempts > 10) {
if (!peer.id) peer.id = 'peer-' + Date.now().toString(36)
return
}
if (!setPeerId()) setTimeout(tryAgain, attempts * 50)
}
setTimeout(tryAgain, 50)
}
mux.stream.on('close', () => {
this.peers.delete(peer)
})
if (this.drive)
this.drive.replicate(mux.stream, { live: true, download: true })
if (this.personalDrive) this.personalDrive.replicate(mux.stream)
for (const d of this.auxiliaryDrives || []) {
try {
d.replicate(mux.stream, { live: true, download: true })
} catch (_) {}
}
}
/**
* Protomux message 0: request block index (MBR). Only send after channel pairing.
* @param {unknown} peer
* @param {number} index
*/
_sendReadIndexToPeer(peer, index) {
const chan =
/** @type {{ chan: { messages: { send: (idx: number) => void }[] } }} */ (
peer
).chan
void chan.fullyOpened().then((opened) => {
if (!opened) return
if (!this.pendingReads.has(index)) return
try {
chan.messages[0].send(index)
} catch {
/* ignore — channel closed or not ready */
}
})
}
/**
* Re-broadcast a pending block read to every connected peer (lossy links, slow pairing).
* @param {number} index
*/
_broadcastPendingReadIndex(index) {
for (const p of this.peers) {
this._sendReadIndexToPeer(p, index)
}
}
async read(index) {
if (this.localRAM.has(index)) return this.localRAM.get(index)
return new Promise((resolve, reject) => {
const timeoutMs = mbrReadTimeoutMsForDisk(this)
/** @type {ReturnType<typeof setInterval> | null} */
let rebroadcastIv = null
const timeout = setTimeout(() => {
if (rebroadcastIv) clearInterval(rebroadcastIv)
this.pendingReads.delete(index)
reject(new Error('MBR read timeout'))
}, timeoutMs)
this.pendingReads.set(index, (data) => {
if (rebroadcastIv) clearInterval(rebroadcastIv)
clearTimeout(timeout)
resolve(data)
})
const rbMs = mbrReadRebroadcastMsFromEnv(globalThis.process?.env)
this._broadcastPendingReadIndex(index)
if (rbMs > 0) {
rebroadcastIv = setInterval(() => {
if (!this.pendingReads.has(index)) {
if (rebroadcastIv) clearInterval(rebroadcastIv)
return
}
this._broadcastPendingReadIndex(index)
}, rbMs)
}
})
}
async search(query) {
const id = this.searchIdCounter++
const peerList = [...this.peers]
const n = peerList.length
if (!n) return []
const self = this
return new Promise((resolve) => {
/** @type {unknown[][]} */
const results = []
const state = {
remaining: n,
results,
settled: false,
timeout: /** @type {ReturnType<typeof setTimeout> | null} */ (null),
finish() {
if (state.settled) return
state.settled = true
if (state.timeout) clearTimeout(state.timeout)
self.pendingSearches.delete(id)
resolve(results.flat())
}
}
state.timeout = setTimeout(() => state.finish(), 3000)
self.pendingSearches.set(id, state)
for (const peer of peerList) {
void peer.chan.fullyOpened().then((opened) => {
if (!opened) return
if (state.settled) return
try {
peer.chan.messages[3].send({ id, query })
} catch {
/* ignore */
}
})
}
})
}
/**
* (Re)pair `bare-os-chat-v1` on every live mux — used after `ensureDiskBareOsChatTransport` replaces
* the service (guest → user, logout → guest) so `peer.chatChan` and handlers match the active instance.
*/
async pairBareOsChatExistingPeers() {
if (!bareOsChatMuxEnabled(globalThis.process?.env)) return
const svc = this.bareOsChatService
if (!svc || typeof svc.pairOnMux !== 'function') return
for (const peer of [...this.peers]) {
const st = peer.mux && peer.mux.stream
if (!st || st.destroyed) continue
try {
const mux = peer.mux
const wired = peer.chatChan
/** @type {Promise<unknown>[]} */
const closing = []
if (wired && typeof wired.close === 'function') {
try {
wired.close()
if (typeof wired.fullyClosed === 'function')
closing.push(wired.fullyClosed())
} catch {
/* ignore */
}
}
peer.chatChan = null
const prevCh =
mux &&
typeof mux.getLastChannel === 'function' &&
mux.getLastChannel({ protocol: PROTOCOL_CHAT_CHANNEL_NAME })
if (prevCh && typeof prevCh.close === 'function' && prevCh !== wired) {
try {
prevCh.close()
if (typeof prevCh.fullyClosed === 'function')
closing.push(prevCh.fullyClosed())
} catch {
/* ignore */
}
}
if (closing.length) await Promise.all(closing)
} catch {
/* ignore */
}
try {
if (typeof peer.mux.unpair === 'function') {
try {
peer.mux.unpair({ protocol: PROTOCOL_CHAT_CHANNEL_NAME })
} catch {
/* ignore */
}
}
svc.pairOnMux(this, peer.mux, peer.socket, peer)
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_chat_late_pair_failed', {
message:
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
}
/**
* (Re)pair `bare-os-meshdrop-v1` on every live mux — used after meshdrop service env/user transitions.
*/
pairBareOsMeshdropExistingPeers() {
if (!bareOsMeshdropMuxEnabled(globalThis.process?.env)) return
const svc = this.bareOsMeshdropService
if (!svc || typeof svc.pairOnMux !== 'function') return
for (const peer of [...this.peers]) {
const st = peer.mux && peer.mux.stream
if (!st || st.destroyed) continue
try {
const mux = peer.mux
const wired = peer.meshdropChan
if (wired && typeof wired.close === 'function') {
try {
wired.close()
} catch {
/* ignore */
}
}
peer.meshdropChan = null
const prevCh =
mux &&
typeof mux.getLastChannel === 'function' &&
mux.getLastChannel({ protocol: PROTOCOL_MESHDROP_CHANNEL_NAME })
if (prevCh && typeof prevCh.close === 'function' && prevCh !== wired) {
try {
prevCh.close()
} catch {
/* ignore */
}
}
} catch {
/* ignore */
}
try {
svc.pairOnMux(this, peer.mux, peer.socket, peer)
if (typeof peer.mux.unpair === 'function') {
try {
peer.mux.unpair({ protocol: PROTOCOL_MESHDROP_CHANNEL_NAME })
} catch {
/* ignore */
}
}
} catch (e) {
emitSwarmDiskHostLog('warn', 'bare_os_meshdrop_late_pair_failed', {
message:
(e && /** @type {{ message?: string }} */ (e).message) || String(e)
})
}
}
}
}
@@ -0,0 +1,63 @@
/**
* Parse optional Hyperswarm connection-budget knobs from env.
* @param {Record<string, string | undefined> | null | undefined} env
* @returns {Record<string, unknown>}
*/
export function bareOsHyperswarmOptsFromEnv(env) {
/** @type {Record<string, unknown>} */
const o = {}
const peerCap = String(env?.BARE_OS_SWARM_MAX_PEERS ?? '').trim()
if (peerCap) {
const x = Number(peerCap)
if (Number.isFinite(x) && x >= 1) o.maxPeers = Math.min(4096, Math.floor(x))
}
const mcc = String(env?.BARE_OS_SWARM_MAX_CLIENT_CONNECTIONS ?? '').trim()
if (mcc) {
const x = Number(mcc)
if (Number.isFinite(x) && x >= 1) {
o.maxClientConnections = Math.min(1_000_000, Math.floor(x))
}
}
const msc = String(env?.BARE_OS_SWARM_MAX_SERVER_CONNECTIONS ?? '').trim()
if (msc) {
const x = Number(msc)
if (Number.isFinite(x) && x >= 1) {
o.maxServerConnections = Math.min(1_000_000, Math.floor(x))
}
}
const mpar = String(env?.BARE_OS_SWARM_MAX_PARALLEL ?? '').trim()
if (mpar) {
const x = Number(mpar)
if (Number.isFinite(x) && x >= 1) o.maxParallel = Math.min(64, Math.floor(x))
}
return o
}
/**
* Optional direct-peer target for Hyperswarm joinPeer().
* @param {Record<string, string | undefined> | null | undefined} env
* @returns {string | null}
*/
export function bareOsBootJoinPeerHexFromEnv(env) {
const raw = String(env?.BARE_OS_BOOT_JOIN_PEER_HEX ?? '')
.trim()
.toLowerCase()
if (!raw) return null
if (!/^[0-9a-f]{64}$/.test(raw)) return null
return raw
}
/**
* Optional operator JSON merged into `replication_operator_sketch.protomuxOperatorSketch`.
* @param {Record<string, string | undefined> | null | undefined} env
*/
export function bareOsProtomuxTuningFromEnv(env) {
const raw = String(env?.BARE_OS_PROTOMUX_TUNING_JSON ?? '').trim()
if (!raw) return null
try {
const o = JSON.parse(raw)
return o && typeof o === 'object' && !Array.isArray(o) ? o : null
} catch {
return { schema: 1, parseError: true }
}
}