/** * Optional `/bin` manifest on the personal drive for large images (`BARE_OS_VFS_BIN_INDEX_BUILD=1`). */ /** * Deterministic SHA-256 over UTF-8 names with `\0` terminators (Web Crypto — Node 20 + Bare). * @param {string[]} sortedNames */ async function bareOsBinIndexNamesDigestAsync(sortedNames) { const enc = new TextEncoder() let totalLen = 0 for (const n of sortedNames) { totalLen += enc.encode(String(n)).byteLength + 1 } const u8 = new Uint8Array(totalLen) let o = 0 for (const n of sortedNames) { const s = enc.encode(String(n)) u8.set(s, o) o += s.byteLength u8[o++] = 0 } const subtle = globalThis.crypto && globalThis.crypto.subtle if (!subtle || typeof subtle.digest !== 'function') { throw new Error('bare-os bin index: Web Crypto subtle.digest unavailable') } const digest = await subtle.digest('SHA-256', u8) return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0') ).join('') } /** * When **`BARE_OS_BIN_HYPERBEE_INDEX=1`**, writes **`/.bare-os/index/bin-hyperbee-hint.json`** * (module resolution probe + name list) for operators integrating Hyperbee/Corestore offline. */ export async function maybeBareOsBuildBinHyperbeeHint(ctx) { const e = ctx.env if ( e.BARE_OS_BIN_HYPERBEE_INDEX !== '1' && e.BARE_OS_BIN_HYPERBEE_INDEX !== 'true' ) { return } const vfs = ctx.vfs const drive = ctx.drive const b4 = ctx.b4a if (!vfs || !drive || typeof drive.readdir !== 'function' || !b4) return let moduleResolves = false try { await import('hyperbee') moduleResolves = true } catch { moduleResolves = false } const names = [] try { const stream = drive.readdir('/bin') for await (const n of stream) names.push(String(n)) } catch { return } names.sort() const digest = await bareOsBinIndexNamesDigestAsync(names) const body = JSON.stringify({ schema: 2, atMs: Date.now(), namesDigest: digest, namesDigestAlgo: 'sha256-null-suffixed-utf8', moduleResolves, count: names.length, names: names.slice(0, 8000), invalidation: { schema: 1, triggers: ['bareOsVfsBatchWrite bin/*', 'BARE_OS_VFS_BIN_INDEX_BUILD'], note: 'Rebuild when batch writes touch /bin or on boot when index env flags are set.' }, note: 'Hyperbee-backed KV index is host-wired; this file lists /bin names and whether hyperbee imports on the booter runtime.' }) + '\n' try { await vfs.writeFile('/.bare-os/index/bin-hyperbee-hint.json', b4.from(body)) } catch (err) { ctx.console?.error?.( '[bare-os] bin hyperbee hint: ' + ((err && err.message) || String(err)) ) } try { const idx = JSON.stringify({ schema: 2, atMs: Date.now(), namesDigest: digest, primaryHint: '/.bare-os/index/bin-hyperbee-hint.json', note: 'Secondary metadata path for `/.bare-os/indexes/` namespace (Hyperbee operator integration).' }) + '\n' await vfs.writeFile('/.bare-os/indexes/hyperbee_status.json', b4.from(idx)) } catch { /* personal tree may deny path on some images */ } } export async function maybeBareOsBuildBinManifest(ctx) { const e = ctx.env if ( e.BARE_OS_VFS_BIN_INDEX_BUILD !== '1' && e.BARE_OS_VFS_BIN_INDEX_BUILD !== 'true' ) { return } const vfs = ctx.vfs const drive = ctx.drive const b4 = ctx.b4a if (!vfs || !drive || typeof drive.readdir !== 'function' || !b4) return /** @type {string[]} */ const names = [] try { const stream = drive.readdir('/bin') for await (const n of stream) names.push(String(n)) } catch { return } names.sort() const digest = await bareOsBinIndexNamesDigestAsync(names) const body = JSON.stringify({ schema: 2, atMs: Date.now(), path: '/bin', namesDigest: digest, namesDigestAlgo: 'sha256-null-suffixed-utf8', count: names.length, names: names.slice(0, 8000), note: 'Sorted lexicographic /bin listing; digest covers full sorted set before truncation.' }) + '\n' try { await vfs.writeFile('/.bare-os/index/bin-manifest.json', b4.from(body)) } catch (err) { ctx.console?.error?.( '[bare-os] bin index: ' + ((err && err.message) || String(err)) ) } }