Files
bare-operating-system/packages/bare-os-seeder/lib/pear-multisig-hint.js
T
snxraven d3aa66b0cc
Release rolling / release (push) Successful in 10m20s
Updates
2026-08-18 20:01:31 -04:00

155 lines
5.2 KiB
JavaScript

import { readFile } from '#host-fs-promises'
import path from '#host-path'
import b4a from 'b4a'
import { pearMultisigShapeOk } from 'bare-os-protocol/bare-os-pear-multisig-shape.js'
import { seedLog } from './host-logger.mjs'
/**
* @returns {Promise<((cmd: string, args: string[], opts?: object) => object) | null>}
*/
async function loadSpawnSync() {
try {
const m = await import('bare-subprocess')
if (m && typeof m.spawnSync === 'function') return m.spawnSync
if (m && m.default && typeof m.default.spawnSync === 'function') {
return m.default.spawnSync
}
} catch {
/* bare-subprocess missing or native addon unavailable */
}
return null
}
/**
* @param {object} r spawnSync result (Node or bare-subprocess)
* @param {'stdout' | 'stderr'} key
*/
function pipeText(r, key) {
const v = r[key]
if (v == null) return ''
if (typeof v === 'string') return v
try {
return b4a.toString(v)
} catch {
return String(v)
}
}
/**
* When `pear.multisig.json` exists next to the kernel tree, validate shape and log status.
* Mirrors Holepunch `pear-multisig-link` style metadata (operational hint, not cryptography).
* Subprocess: **`bare-subprocess`** only (Bare/Pear). Do not import Node `child_process`.
* @param {string} kernelRoot
*/
export async function logPearMultisigKernelHint(kernelRoot) {
const envOut = globalThis.process?.env
const f = path.join(kernelRoot, 'pear.multisig.json')
try {
const raw = await readFile(f, 'utf8')
const j = JSON.parse(raw)
if (!pearMultisigShapeOk(j)) {
seedLog(
'warn',
'pear.multisig.json: expected { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length',
{ code: 'pear_multisig_shape' }
)
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped'
return
}
const signers = j.signers
const quorum = j.quorum
seedLog(
'info',
`pear.multisig.json OK (${signers.length} signers, quorum ${quorum})`,
{ code: 'pear_multisig_ok' }
)
const env = globalThis.process?.env
if (
env?.BARE_OS_HYPER_MULTISIG_VERIFY === '1' ||
env?.BARE_OS_HYPER_MULTISIG_VERIFY === 'true'
) {
const spawnSync = await loadSpawnSync()
if (!spawnSync) {
seedLog(
'warn',
'hyper-multisig verify skipped (no subprocess: install bare-subprocess or use Node)',
{ code: 'hyper_multisig_no_subprocess' }
)
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped'
return
}
const r = spawnSync('hyper-multisig', ['verify', f], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe']
})
if (r.error) {
seedLog(
'warn',
'hyper-multisig verify skipped (CLI not on PATH): ' +
String(r.error.message || r.error),
{ code: 'hyper_multisig_cli_missing' }
)
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped'
} else if (r.status !== 0) {
const msg = (pipeText(r, 'stderr') || pipeText(r, 'stdout')).trim().slice(0, 400)
seedLog('warn', 'hyper-multisig verify failed: ' + (msg || `exit ${r.status}`), {
code: 'hyper_multisig_verify_failed',
status: r.status
})
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'failed'
} else {
seedLog('info', 'hyper-multisig verify OK', { code: 'hyper_multisig_verify_ok' })
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'ok'
}
} else if (envOut) {
envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped'
}
} catch (e) {
if (/** @type {NodeJS.ErrnoException} */ (e).code === 'ENOENT') return
seedLog('warn', 'pear.multisig.json: ' + (e?.message || String(e)), {
code: 'pear_multisig_read_error'
})
}
}
/**
* Same shape checks as {@link logPearMultisigKernelHint}, reading **`/kernel/pear.multisig.json`**
* via Pear app drive IPC (**`pear-appdrive`**). Subprocess **`hyper-multisig verify`** is skipped.
* @param {{ get: (key: string) => Promise<Uint8Array | Buffer | null> }} appDrive
*/
export async function logPearMultisigKernelHintFromPearAppDrive(appDrive) {
const envOut = globalThis.process?.env
let raw
try {
const buf = await appDrive.get('/kernel/pear.multisig.json')
if (!buf) return
raw = b4a.toString(buf, 'utf8')
} catch {
return
}
try {
const j = JSON.parse(raw)
if (!pearMultisigShapeOk(j)) {
seedLog(
'warn',
'pear.multisig.json: expected { signers: string[], quorum: number } with 1 ≤ quorum ≤ signers.length',
{ code: 'pear_multisig_shape_bundle' }
)
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped'
return
}
const signers = j.signers
const quorum = j.quorum
seedLog(
'info',
`pear.multisig.json OK (${signers.length} signers, quorum ${quorum}) [from Pear bundle]`,
{ code: 'pear_multisig_ok_bundle' }
)
if (envOut) envOut.BARE_OS_SEEDER_MULTISIG_VERIFY_RESULT = 'skipped'
} catch (e) {
seedLog('warn', 'pear.multisig.json (bundle): ' + (e?.message || String(e)), {
code: 'pear_multisig_bundle_parse'
})
}
}