first commit

This commit is contained in:
Raven Scott
2026-04-02 21:38:07 -04:00
commit 1af6dbc9b3
24 changed files with 3063 additions and 0 deletions
+187
View File
@@ -0,0 +1,187 @@
import Hyperswarm from 'hyperswarm'
import Protomux from 'protomux'
import b4a from 'b4a'
import safetyCatch from 'safety-catch'
import Hyperdrive from 'hyperdrive'
import Corestore from 'corestore'
import path from 'path'
import { fileURLToPath } from 'url'
import { topicKey, parseMbr } from 'bare-os-protocol'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.join(__dirname, '..', '..')
import { SwarmDisk } from './lib/swarm-disk.js'
import { runKernelFromSource, runBinCommand } from './lib/kernel-runner.js'
function bootStorePath() {
return process.env.BARE_OS_BOOT_STORE || path.join(repoRoot, 'data', 'corestore-booter')
}
async function createReadLine() {
if (process.env.BARE_OS_SKIP_REPL === '1') {
return async () => null
}
try {
const { createInterface } = await import('node:readline')
return (prompt) =>
new Promise((resolve) => {
const rl = createInterface({
input: process.stdin,
output: process.stdout
})
rl.question(prompt, (line) => {
rl.close()
resolve(line)
})
})
} catch {
console.warn(
'[bare-os-booter] No node:readline; exiting REPL. Set BARE_OS_SKIP_REPL=1 for non-interactive kernel or run under Node.'
)
return async () => null
}
}
/**
* @param {SwarmDisk} disk
* @param {import('corestore').default} store
* @param {import('hyperswarm').default} swarm
* @param {Uint8Array} initSource
*/
async function executeKernel(disk, store, swarm, initSource) {
const readLine = await createReadLine()
const ctx = {
disk,
drive: disk.drive,
personalDrive: disk.personalDrive,
console,
b4a,
topic: topicKey(),
readLine,
async execLine(line) {
const parts = line.trim().split(/\s+/)
if (!parts[0]) return
await runBinCommand(disk.drive, parts, ctx)
}
}
disk.os = {
async searchLocal() {
return []
},
async execRpc() {
return ''
}
}
await runKernelFromSource(b4a.toString(initSource), ctx)
}
async function bootFromPeers(disk, store, swarm) {
const topic = topicKey()
console.log('Loading MBR from peers...')
const mbr = await disk.read(0)
const { keys } = parseMbr(mbr)
let initSource = null
for (const driveKey of keys) {
try {
console.log('Mounting drive', b4a.toString(driveKey, 'hex').slice(0, 16) + '...')
disk.drive = new Hyperdrive(store, driveKey)
await disk.drive.ready()
for (const peer of disk.peers) {
disk.drive.replicate(peer.mux.stream, { live: true, download: true })
}
swarm.join(disk.drive.discoveryKey)
const done = disk.drive.findingPeers()
swarm.flush().then(done, done)
for (let i = 0; i < 30; i++) {
initSource = await disk.drive.get('/boot/init.js')
if (initSource) break
await new Promise((r) => setTimeout(r, 200))
}
if (initSource) break
console.log('Kernel not ready on this drive key, trying next...')
} catch (err) {
console.log('Drive error:', err.message)
}
}
if (!initSource) throw new Error('Kernel not found after replication')
await disk.initPersonalDrive(store, swarm, Hyperdrive)
console.log('Starting kernel...')
await executeKernel(disk, store, swarm, initSource)
}
async function bootLocal(disk, store, swarm) {
const seedPath =
process.env.BARE_OS_LOCAL_SEED || path.join(repoRoot, 'data', 'corestore-seeder')
console.log('Local boot from', seedPath)
const seedStore = new Corestore(seedPath)
disk.drive = new Hyperdrive(seedStore)
await disk.drive.ready()
let initSource = null
for (let i = 0; i < 10; i++) {
initSource = await disk.drive.get('/boot/init.js')
if (initSource) break
await new Promise((r) => setTimeout(r, 200))
}
if (!initSource) throw new Error('Kernel missing in local seed store')
await disk.initPersonalDrive(store, swarm, Hyperdrive)
await executeKernel(disk, store, swarm, initSource)
}
async function main() {
console.log('--- bare-os-booter ---')
const store = new Corestore(bootStorePath())
const swarm = new Hyperswarm()
const disk = new SwarmDisk()
const topic = topicKey()
swarm.on('connection', (socket) => {
const mux = new Protomux(socket)
disk.addPeer(mux, socket)
})
swarm.join(topic)
const maxWait = Number(process.env.BARE_OS_PEER_WAIT_MS || 8000)
let waited = 0
while (disk.peers.size === 0 && waited < maxWait) {
await new Promise((r) => setTimeout(r, 500))
waited += 500
}
console.log('Peers:', disk.peers.size)
try {
if (disk.peers.size > 0) {
await bootFromPeers(disk, store, swarm)
} else {
await bootLocal(disk, store, swarm)
}
} finally {
try {
if (disk.personalDrive) await disk.personalDrive.close()
} catch (_) {}
try {
if (disk.drive) await disk.drive.close()
} catch (_) {}
try {
await swarm.destroy()
} catch (_) {}
try {
await store.close()
} catch (_) {}
}
}
main().catch(safetyCatch)
@@ -0,0 +1,38 @@
import b4a from 'b4a'
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor
/**
* Execute kernel source from Hyperdrive (trusted). Expects top-level `async function start(ctx)`.
* @param {string} source
* @param {Record<string, unknown>} ctx
*/
export async function runKernelFromSource(source, ctx) {
const fn = new AsyncFunction(
'ctx',
`${source}\nif (typeof start !== 'function') throw new Error('kernel must define async function start')\nreturn start(ctx)\n`
)
return fn(ctx)
}
/**
* Run /bin/<cmd> script with `async function run(ctx, argv)`.
* @param {import('hyperdrive').default} drive
* @param {string[]} argv
* @param {Record<string, unknown>} ctx
*/
export async function runBinCommand(drive, argv, ctx) {
const cmd = argv[0]
const buf = await drive.get('/bin/' + cmd)
if (!buf) {
ctx.console.log('unknown command: ' + cmd)
return
}
const src = b4a.toString(buf)
const fn = new AsyncFunction(
'ctx',
'argv',
`${src}\nif (typeof run !== 'function') throw new Error('missing run() in /bin/${cmd}')\nreturn run(ctx, argv)\n`
)
return fn(ctx, argv)
}
+279
View File
@@ -0,0 +1,279 @@
import b4a from 'b4a'
import c from 'compact-encoding'
import { PROTOCOL_NAME } from 'bare-os-protocol/constants.js'
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
}
/**
* @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')
this.personalDrive = new Hyperdrive(localStore)
await this.personalDrive.ready()
if (!this.personalDrive.writable) {
this.personalDrive = new Hyperdrive(localStore)
await this.personalDrive.ready()
}
console.log('Personal drive:', this.personalDrive.id.slice(0, 16) + '...')
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 cb = disk.pendingSearches.get(m.id)
if (cb) {
disk.pendingSearches.delete(m.id)
cb(m.matches)
}
},
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 peer = { chan, mux, socket, id: null }
this.peers.add(peer)
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)
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)
}
async read(index) {
if (this.localRAM.has(index)) return this.localRAM.get(index)
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error('MBR read timeout')), 10000)
this.pendingReads.set(index, (data) => {
clearTimeout(timeout)
resolve(data)
})
for (const peer of this.peers) peer.chan.messages[0].send(index)
})
}
async search(query) {
const id = this.searchIdCounter++
const promises = []
for (const peer of this.peers) {
promises.push(
new Promise((resolve) => {
const to = setTimeout(() => resolve([]), 3000)
this.pendingSearches.set(id, (matches) => {
clearTimeout(to)
resolve(matches)
})
peer.chan.messages[3].send({ id, query })
})
)
}
const results = await Promise.all(promises)
return results.flat()
}
}
+51
View File
@@ -0,0 +1,51 @@
{
"name": "bare-os-booter",
"version": "0.1.0",
"description": "Pear/Bare booter: Hyperswarm client, MBR, system + personal Hyperdrive, kernel",
"type": "module",
"main": "./index.js",
"scripts": {
"start": "bare index.js",
"dev": "bare index.js",
"test": "brittle-node test.js"
},
"dependencies": {
"bare-os-protocol": "*",
"b4a": "^1.6.7",
"compact-encoding": "^2.18.0",
"corestore": "^7.2.1",
"hyperdrive": "^13.3.2",
"hyperswarm": "^4.16.0",
"protomux": "^3.10.1",
"safety-catch": "^1.0.2"
},
"devDependencies": {
"brittle": "^3.1.0"
},
"engines": {
"bare": ">=2.0.0"
},
"pear": {
"name": "bare-os-booter",
"stage": {
"ignore": [
".git",
"test",
"coverage",
".DS_Store",
"node_modules/.bin",
"node_modules/.package-lock.json"
]
}
},
"imports": {
"path": {
"bare": "bare-path",
"default": "node:path"
},
"url": {
"bare": "bare-url",
"default": "node:url"
}
}
}
+60
View File
@@ -0,0 +1,60 @@
import test from 'brittle'
import b4a from 'b4a'
import Hyperdrive from 'hyperdrive'
import Corestore from 'corestore'
import { mkdirSync, rmSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { runKernelFromSource, runBinCommand } from './lib/kernel-runner.js'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
function testCorestoreDir(name) {
const dir = path.join(__dirname, '.test-data', name + '-' + process.pid + '-' + Math.random().toString(36).slice(2))
mkdirSync(path.dirname(dir), { recursive: true })
return dir
}
test('runKernelFromSource invokes start(ctx)', async (t) => {
const calls = []
const source = `
async function start(ctx) {
ctx.calls.push('ok')
}
`
await runKernelFromSource(source, { calls })
t.is(calls[0], 'ok')
})
test('runBinCommand runs /bin helper', async (t) => {
const dir = testCorestoreDir('bin')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
await drive.ready()
await drive.put(
'/bin/hello',
b4a.from(`
async function run(ctx, argv) {
ctx.out.push(argv.join(' '))
}
`)
)
const out = []
await runBinCommand(drive, ['hello', 'a', 'b'], { out, console })
t.is(out[0], 'hello a b')
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('Hyperdrive roundtrips /boot/init.js on Corestore', async (t) => {
const dir = testCorestoreDir('seed')
const store = new Corestore(dir)
const drive = new Hyperdrive(store)
await drive.ready()
await drive.put('/boot/init.js', b4a.from('async function start() {}'))
const buf = await drive.get('/boot/init.js')
t.ok(buf)
t.is(b4a.toString(buf), 'async function start() {}')
await drive.close()
rmSync(dir, { recursive: true, force: true })
})
+40
View File
@@ -0,0 +1,40 @@
import b4a from 'b4a'
import crypto from 'hypercore-crypto'
export const PROTOCOL_NAME = 'bare-os-v1'
export const TOPIC_STRING = 'bare-os-v1'
export const BLOCK_SIZE = 512
export const MBR_MAGIC = b4a.from('BIOS')
export function topicKey(b4aMod = b4a) {
return crypto.hash(b4aMod.from(TOPIC_STRING))
}
export function buildMbr(primaryKey, failoverKeys = []) {
if (primaryKey.byteLength !== 32) {
throw new Error('primaryKey must be 32 bytes')
}
const mbr = b4a.alloc(BLOCK_SIZE)
mbr.set(MBR_MAGIC)
mbr.set(primaryKey, 8)
const slots = [40, 72]
for (let i = 0; i < slots.length; i++) {
const k = failoverKeys[i]
if (k) {
if (k.byteLength !== 32) throw new Error('failover key must be 32 bytes')
mbr.set(k, slots[i])
}
}
return mbr
}
export function parseMbr(mbr) {
if (mbr.byteLength < 104) throw new Error('MBR too short')
if (b4a.toString(mbr.slice(0, 4)) !== 'BIOS') {
throw new Error('Invalid MBR magic')
}
const keys = [mbr.slice(8, 40), mbr.slice(40, 72), mbr.slice(72, 104)].filter(
(k) => !b4a.equals(k, b4a.alloc(32))
)
return { keys }
}
+11
View File
@@ -0,0 +1,11 @@
export {
PROTOCOL_NAME,
TOPIC_STRING,
BLOCK_SIZE,
MBR_MAGIC,
topicKey,
buildMbr,
parseMbr
} from './constants.js'
export { setupSeedChannel } from './lib/channel.js'
+68
View File
@@ -0,0 +1,68 @@
import b4a from 'b4a'
import c from 'compact-encoding'
import { PROTOCOL_NAME } from '../constants.js'
import {
msgDataEncoding,
msgSearchReqEncoding,
msgSearchResEncoding,
msgRpcReqEncoding,
msgRpcResEncoding
} from './messages.js'
/**
* Seeder-side Protomux channel: MBR block service + stub search/RPC + drive replication.
* @param {import('protomux')} mux
* @param {Map<number, Uint8Array>} localRAM
* @param {(stream: any) => void} replicateDrive - e.g. (s) => drive.replicate(s)
*/
export function setupSeedChannel(mux, localRAM, replicateDrive) {
const chan = mux.createChannel({ protocol: PROTOCOL_NAME })
chan.addMessage({
encoding: c.uint32,
onmessage(index) {
const data = localRAM.get(index)
if (data) chan.messages[1].send({ index, data })
}
})
chan.addMessage({
encoding: msgDataEncoding
})
chan.addMessage({ encoding: c.buffer })
chan.addMessage({
encoding: msgSearchReqEncoding,
onmessage(m) {
chan.messages[4].send({ id: m.id, matches: [] })
}
})
chan.addMessage({
encoding: msgSearchResEncoding
})
chan.addMessage({
encoding: msgRpcReqEncoding,
onmessage(m) {
chan.messages[6].send({
id: m.id,
success: false,
result: '',
error: 'RPC not implemented on seeder'
})
}
})
chan.addMessage({
encoding: msgRpcResEncoding
})
chan.open()
const bitfield = b4a.alloc(250)
bitfield[0] |= 1
chan.messages[2].send(bitfield)
replicateDrive(mux.stream)
}
+96
View File
@@ -0,0 +1,96 @@
import c from 'compact-encoding'
/** Read request: block index */
export const msgRead = {
encoding: c.uint32,
onmessage: null
}
/** Read response: index + data */
export const msgDataEncoding = {
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) }
}
}
export const msgSearchReqEncoding = {
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) }
}
}
export const msgSearchResEncoding = {
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) }
}
}
export const msgRpcReqEncoding = {
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)
}
}
}
export const msgRpcResEncoding = {
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)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "bare-os-protocol",
"version": "0.1.0",
"description": "MBR layout, swarm topic, and Protomux encodings for bare-operating-system",
"type": "module",
"main": "./index.js",
"exports": {
".": "./index.js",
"./constants.js": "./constants.js",
"./messages": "./lib/messages.js"
},
"scripts": {
"test": "brittle-bare test.js",
"test:node": "brittle-node test.js"
},
"engines": {
"bare": ">=2.0.0"
},
"dependencies": {
"b4a": "^1.6.7",
"compact-encoding": "^2.18.0",
"hypercore-crypto": "^3.6.1"
},
"devDependencies": {
"brittle": "^3.1.0"
},
"imports": {
"fs": {
"bare": "bare-fs",
"default": "fs"
},
"path": {
"bare": "bare-path",
"default": "path"
},
"events": {
"bare": "bare-events",
"default": "events"
}
}
}
+62
View File
@@ -0,0 +1,62 @@
import test from 'brittle'
import b4a from 'b4a'
import crypto from 'hypercore-crypto'
import {
topicKey,
buildMbr,
parseMbr,
BLOCK_SIZE,
TOPIC_STRING
} from './constants.js'
import { msgDataEncoding, msgSearchReqEncoding } from './lib/messages.js'
test('topicKey is deterministic 32-byte hash', (t) => {
const a = topicKey()
const b = topicKey()
t.is(a.byteLength, 32)
t.alike(a, b)
})
test('topicKey differs from other string', (t) => {
const k1 = topicKey()
const k2 = crypto.hash(b4a.from('other-topic'))
t.unlike(k1, k2)
})
test('buildMbr + parseMbr roundtrip', (t) => {
const key = crypto.hash(b4a.from('primary-drive'))
const mbr = buildMbr(key)
t.is(mbr.byteLength, BLOCK_SIZE)
const { keys } = parseMbr(mbr)
t.is(keys.length, 1)
t.alike(keys[0], key)
})
test('msgDataEncoding roundtrip', (t) => {
const m = { index: 0, data: b4a.from('hello') }
const pre = { buffer: null, start: 0, end: 0 }
msgDataEncoding.preencode(pre, m)
const buf = b4a.alloc(pre.end)
const state = { buffer: buf, start: 0, end: 0 }
msgDataEncoding.encode(state, m)
const decState = { buffer: buf, start: 0, end: buf.byteLength }
const dec = msgDataEncoding.decode(decState)
t.is(dec.index, 0)
t.alike(dec.data, m.data)
})
test('msgSearchReqEncoding roundtrip', (t) => {
const m = { id: 7, query: 'foo' }
const pre = { buffer: null, start: 0, end: 0 }
msgSearchReqEncoding.preencode(pre, m)
const buf = b4a.alloc(pre.end)
const state = { buffer: buf, start: 0, end: 0 }
msgSearchReqEncoding.encode(state, m)
const decState = { buffer: buf, start: 0, end: buf.byteLength }
const dec = msgSearchReqEncoding.decode(decState)
t.is(dec.id, 7)
t.is(dec.query, 'foo')
})
test('TOPIC_STRING is bare-os-v1', (t) => {
t.is(TOPIC_STRING, 'bare-os-v1')
})
+101
View File
@@ -0,0 +1,101 @@
import Hyperswarm from 'hyperswarm'
import Protomux from 'protomux'
import b4a from 'b4a'
import safetyCatch from 'safety-catch'
import Hyperdrive from 'hyperdrive'
import Corestore from 'corestore'
import { readdir, readFile, stat } from 'fs/promises'
import path from 'path'
import { fileURLToPath } from 'url'
import {
topicKey,
buildMbr,
setupSeedChannel,
BLOCK_SIZE
} from 'bare-os-protocol'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const repoRoot = path.join(__dirname, '..', '..')
function corestorePath() {
return process.env.BARE_OS_SEED_STORE || path.join(repoRoot, 'data', 'corestore-seeder')
}
/** @param {import('hyperdrive').default} drive */
async function stageKernelTree(drive, kernelRoot) {
async function walk(rel) {
const abs = path.join(kernelRoot, rel)
const entries = await readdir(abs, { withFileTypes: true })
for (const ent of entries) {
const name = ent.name
const subRel = rel ? path.join(rel, name) : name
const subAbs = path.join(kernelRoot, subRel)
if (ent.isDirectory()) {
await walk(subRel)
} else {
const raw = await readFile(subAbs)
let drivePath
if (subRel === 'init.js') {
drivePath = '/boot/init.js'
} else if (subRel.startsWith('bin' + path.sep)) {
drivePath = '/bin/' + subRel.slice(4).split(path.sep).join('/')
} else if (subRel.startsWith('etc' + path.sep)) {
drivePath = '/etc/' + subRel.slice(4).split(path.sep).join('/')
} else {
drivePath = '/' + subRel.split(path.sep).join('/')
}
await drive.put(drivePath, b4a.from(raw))
console.log(' [+] ' + drivePath)
}
}
}
const st = await stat(kernelRoot).catch(() => null)
if (!st || !st.isDirectory()) {
throw new Error('kernel directory missing: ' + kernelRoot)
}
console.log('Staging kernel from', kernelRoot)
await walk('')
}
async function main() {
console.clear?.()
console.log('--- bare-os-seeder (Hyperdrive + MBR) ---')
const kernelRoot = process.env.BARE_OS_KERNEL_ROOT
? path.resolve(process.env.BARE_OS_KERNEL_ROOT)
: path.join(repoRoot, 'kernel')
const store = new Corestore(corestorePath())
const drive = new Hyperdrive(store)
await drive.ready()
console.log('Drive ready:', drive.id)
await stageKernelTree(drive, kernelRoot)
const localRAM = new Map()
const mbr = buildMbr(drive.key)
if (mbr.byteLength !== BLOCK_SIZE) throw new Error('MBR size mismatch')
localRAM.set(0, mbr)
console.log(' [+] MBR block 0 (BIOS + primary key)')
const swarm = new Hyperswarm()
const topic = topicKey()
swarm.on('connection', (socket) => {
const mux = new Protomux(socket)
setupSeedChannel(mux, localRAM, (stream) => {
drive.replicate(stream)
})
})
swarm.join(topic)
swarm.join(drive.discoveryKey)
await swarm.flush()
console.log('\nSeeder active. Topic (discovery):', b4a.toString(topic, 'hex').slice(0, 16) + '...')
console.log('Drive key:', b4a.toString(drive.key, 'hex'))
console.log('(Kernel read directly into Hyperdrive.) Corestore:', corestorePath())
}
main().catch(safetyCatch)
+55
View File
@@ -0,0 +1,55 @@
{
"name": "bare-os-seeder",
"version": "0.1.0",
"description": "Pear/Bare seeder: publishes system Hyperdrive and MBR over Hyperswarm",
"type": "module",
"main": "./index.js",
"scripts": {
"start": "bare index.js",
"dev": "bare index.js"
},
"dependencies": {
"bare-os-protocol": "*",
"b4a": "^1.6.7",
"compact-encoding": "^2.18.0",
"corestore": "^7.2.1",
"hyperdrive": "^13.3.2",
"hyperswarm": "^4.16.0",
"protomux": "^3.10.1",
"safety-catch": "^1.0.2"
},
"engines": {
"bare": ">=2.0.0"
},
"pear": {
"name": "bare-os-seeder",
"stage": {
"ignore": [
".git",
"test",
"coverage",
".DS_Store",
"node_modules/.bin",
"node_modules/.package-lock.json"
]
}
},
"imports": {
"fs": {
"bare": "bare-fs",
"default": "node:fs"
},
"fs/promises": {
"bare": "bare-fs/promises",
"default": "node:fs/promises"
},
"path": {
"bare": "bare-path",
"default": "node:path"
},
"url": {
"bare": "bare-url",
"default": "node:url"
}
}
}