updates
CI / test (push) Failing after 4m58s

This commit is contained in:
Raven Scott
2026-04-02 23:37:27 -04:00
parent 45cf20ea3c
commit 3b6cb97997
6 changed files with 217 additions and 26 deletions
+2 -2
View File
@@ -344,7 +344,7 @@ Pear-safe path resolution (same idea as Holepunch [pear-rti](https://github.com/
**`main()`**
- `Corestore(bootStorePath())`, `Hyperswarm`, `SwarmDisk`, join `topicKey()`.
- Wait loop: 500ms steps until `disk.peers.size > 0` or `BARE_OS_PEER_WAIT_MS` (default 8000).
- Wait loop: 500ms steps until `disk.peers.size > 0` or `BARE_OS_PEER_WAIT_MS` (default 60000, same as boot budget).
- If peers: `bootFromPeers`; else `bootLocal`.
- **`finally`**: close `personalDrive`, `disk.drive`, `swarm.destroy()`, `store.close()` (each in try/catch).
@@ -467,7 +467,7 @@ sequenceDiagram
| `BARE_OS_SEED_STORE` | Seeder | Corestore directory (default: `repo/data/corestore-seeder`) |
| `BARE_OS_BOOT_STORE` | Booter | Corestore for boot side (default: `repo/data/corestore-booter`) |
| `BARE_OS_LOCAL_SEED` | Booter | Corestore path for local boot (default: `repo/data/corestore-seeder`) |
| `BARE_OS_PEER_WAIT_MS` | Booter | Max ms to wait for ≥1 peer before local boot (default `8000`) |
| `BARE_OS_PEER_WAIT_MS` | Booter | Max ms to wait for ≥1 peer before local boot (default `60000`) |
| `BARE_OS_SKIP_REPL` | Booter | If `1`, readline returns null — non-interactive exit |
| `BARE_OS_FISH` | Booter | If `0`, disable Fish-style raw TTY line editor (use classic readline) |
| `BARE_OS_FISH_HISTORY_MAX` | Booter / fish-readline | Max history entries persisted to `/.bare_history` (default `1000`) |
+1 -1
View File
@@ -58,7 +58,7 @@ Runs workspace tests (`brittle-node` / `brittle-bare` where configured).
Optional:
- `BARE_OS_PEER_WAIT_MS` — ms to wait for peers (default `8000`).
- `BARE_OS_PEER_WAIT_MS` — ms to wait for peers before local seed (default `60000`).
- `BARE_OS_LOCAL_SEED` — Corestore path for **local boot** when no peers (default `./data/corestore-seeder`).
- `BARE_OS_SKIP_REPL=1` — non-interactive kernel (CI / automation).
+116 -22
View File
@@ -21,6 +21,12 @@ import {
} from './lib/cli-readline.js'
import { resolveStdio } from './lib/resolve-stdio.js'
import { createFishReadLine, disableFishRawMode } from './lib/fish-readline.js'
import {
BOOT_TIMEOUT_MESSAGE,
BOOT_TIMEOUT_MS,
sleepReject,
startBootSplash
} from './lib/boot-splash.js'
const _pkg = packageRootDir(import.meta.url)
@@ -28,6 +34,23 @@ function bootStorePath() {
return defaultBootCorestorePath(_pkg, import.meta.url)
}
/** Kernel / replication phase budget (after peer wait). */
function parseLoadTimeoutMs() {
const raw = globalThis.process?.env?.BARE_OS_BOOT_TIMEOUT_MS
const n = Number(raw)
if (Number.isFinite(n) && n > 0) return Math.min(Math.trunc(n), 600_000)
return BOOT_TIMEOUT_MS
}
/** Max ms to wait for first peer before falling back to local seed (default 1 min). */
function parsePeerWaitMs() {
const raw = globalThis.process?.env?.BARE_OS_PEER_WAIT_MS
if (raw === undefined || raw === '') return BOOT_TIMEOUT_MS
const n = Number(raw)
if (!Number.isFinite(n) || n < 0) return BOOT_TIMEOUT_MS
return Math.min(Math.trunc(n), 600_000)
}
/** @returns {Promise<{ readLine: (p: string) => Promise<string | null>, interactiveAvailable: boolean, skipInteractive: boolean, stdout: import('stream').Writable | null, stdin: import('stream').Readable | null }>} */
async function createReadLine() {
const { stdin, stdout } = await resolveStdio()
@@ -148,6 +171,15 @@ async function executeKernel(disk, store, swarm, initSource) {
globalThis.console.clear?.()
}
if (
interactiveAvailable &&
!skipInteractive &&
sessionStdout &&
typeof sessionStdout.write === 'function'
) {
writeScreen('\x1b[H\x1b[2J\x1b[3J')
}
const shellEnv = {
HOME: '/home/user',
PATH: '/bin',
@@ -229,16 +261,26 @@ async function executeKernel(disk, store, swarm, initSource) {
}
}
async function bootFromPeers(disk, store, swarm) {
/**
* @param {SwarmDisk} disk
* @param {import('corestore').default} store
* @param {import('hyperswarm').default} swarm
* @param {{ silent?: boolean }} [opts]
* @returns {Promise<Uint8Array>}
*/
async function bootFromPeers(disk, store, swarm, opts = {}) {
const { silent = false } = opts
const log = silent ? () => {} : console.log.bind(console)
const topic = topicKey()
console.log('Loading MBR from peers...')
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(
log(
'Mounting drive',
b4a.toString(driveKey, 'hex').slice(0, 16) + '...'
)
@@ -259,22 +301,32 @@ async function bootFromPeers(disk, store, swarm) {
}
if (initSource) break
console.log('Kernel not ready on this drive key, trying next...')
log('Kernel not ready on this drive key, trying next...')
} catch (err) {
console.log('Drive error:', err.message)
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)
log('Starting kernel...')
return initSource
}
async function bootLocal(disk, store, swarm) {
/**
* @param {SwarmDisk} disk
* @param {import('corestore').default} store
* @param {import('hyperswarm').default} swarm
* @param {{ silent?: boolean }} [opts]
* @returns {Promise<Uint8Array>}
*/
async function bootLocal(disk, store, swarm, opts = {}) {
const { silent = false } = opts
const log = silent ? () => {} : console.log.bind(console)
const seedPath = defaultLocalSeedCorestorePath(_pkg, import.meta.url)
console.log('Local boot from', seedPath)
log('Local boot from', seedPath)
const seedStore = new Corestore(seedPath)
disk.drive = new Hyperdrive(seedStore)
await disk.drive.ready()
@@ -288,17 +340,30 @@ async function bootLocal(disk, store, swarm) {
if (!initSource) throw new Error('Kernel missing in local seed store')
await disk.initPersonalDrive(store, swarm, Hyperdrive)
await executeKernel(disk, store, swarm, initSource)
return initSource
}
async function main() {
console.log('--- bare-os-booter ---')
const { stdout } = await resolveStdio()
const useSplash = Boolean(stdout && typeof stdout.write === 'function')
const silentBoot = useSplash && stdout.isTTY === true
const store = new Corestore(bootStorePath())
const swarm = new Hyperswarm()
const disk = new SwarmDisk()
const topic = topicKey()
/** @type {string} */
let bootStatus = 'joining swarm'
const stopSplash = useSplash
? startBootSplash(stdout, {
peerCount: () => disk.peers.size,
status: () => bootStatus
})
: () => {}
swarm.on('connection', (socket) => {
const mux = new Protomux(socket)
disk.addPeer(mux, socket)
@@ -306,21 +371,50 @@ async function main() {
swarm.join(topic)
const maxWait = Number(globalThis.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)
const loadTimeoutMs = parseLoadTimeoutMs()
const peerWaitMs = parsePeerWaitMs()
/** Room for full peer wait plus replication/local load without racing too early. */
const totalBootRaceMs = Math.min(peerWaitMs + loadTimeoutMs, 900_000)
try {
if (disk.peers.size > 0) {
await bootFromPeers(disk, store, swarm)
} else {
await bootLocal(disk, store, swarm)
const initSource = await Promise.race([
(async () => {
let waited = 0
bootStatus = 'waiting for peers'
while (disk.peers.size === 0 && waited < peerWaitMs) {
await new Promise((r) => setTimeout(r, 500))
waited += 500
}
if (!silentBoot) {
console.log('Peers:', disk.peers.size)
}
bootStatus =
disk.peers.size > 0 ? 'replicating kernel' : 'loading local seed'
if (disk.peers.size > 0) {
return await bootFromPeers(disk, store, swarm, {
silent: silentBoot
})
}
return await bootLocal(disk, store, swarm, { silent: silentBoot })
})(),
sleepReject(totalBootRaceMs, () => {
stopSplash()
})
])
stopSplash()
await executeKernel(disk, store, swarm, initSource)
} catch (err) {
stopSplash()
if (err && err.message === BOOT_TIMEOUT_MESSAGE) {
console.error('bare-os-booter:', err.message)
if (typeof globalThis.process?.exit === 'function') {
globalThis.process.exitCode = 1
}
return
}
throw err
} finally {
try {
if (disk.personalDrive) await disk.personalDrive.close()
@@ -0,0 +1,79 @@
/** Default boot attempt timeout (ms). */
export const BOOT_TIMEOUT_MS = 60_000
export const BOOT_TIMEOUT_MESSAGE = 'Boot timed out'
const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
/**
* @param {import('stream').Writable | null | undefined} stdout
* @param {{ peerCount?: () => number, status?: () => string }} opts
* @returns {() => void} stop — clears the line and restores cursor visibility
*/
export function startBootSplash(stdout, opts = {}) {
const start = Date.now()
let frame = 0
let stopped = false
function tick() {
if (stopped || !stdout?.write) return
const elapsedMs = Date.now() - start
const totalSec = Math.floor(elapsedMs / 1000)
const m = Math.floor(totalSec / 60)
const s = totalSec % 60
const clock = `${m}:${s.toString().padStart(2, '0')}`
const spin = SPINNER[frame++ % SPINNER.length]
let extra = ''
try {
const n = opts.peerCount?.()
if (typeof n === 'number') extra += ` peers ${n}`
} catch {
/* ignore */
}
try {
const st = opts.status?.()
if (st) extra += ` · ${st}`
} catch {
/* ignore */
}
stdout.write(
`\r\x1b[K\x1b[36m${spin}\x1b[0m \x1b[1mbare-os\x1b[0m booting \x1b[33m${clock}\x1b[0m${extra}`
)
}
if (stdout?.write) stdout.write('\x1b[?25l')
tick()
const id = setInterval(tick, 100)
return function stopBootSplash() {
if (stopped) return
stopped = true
clearInterval(id)
if (stdout?.write) {
stdout.write('\r\x1b[K')
stdout.write('\x1b[?25h')
}
}
}
export function bootTimedOutError() {
return new Error(BOOT_TIMEOUT_MESSAGE)
}
/**
* @param {number} ms
* @param {() => void} [onTimeout]
*/
export function sleepReject(ms, onTimeout) {
return new Promise((_, reject) => {
const t = setTimeout(() => {
try {
onTimeout?.()
} catch {
/* ignore */
}
reject(bootTimedOutError())
}, ms)
if (t.unref) t.unref()
})
}
@@ -205,6 +205,12 @@ export async function createFishReadLine(ctx, opts) {
let prevCursorRows = 0
/** Rows spanned by last painted prompt+input+ghost (>=1); forces full refresh when >1. */
let prevPaintRowCount = 0
/**
* First draw after each readLine(): kernel/console output may leave the cursor on a
* wrapped or partial line; clearEntireLine only wipes one row. One full clear (col 0 +
* clearScreenDown) resyncs — same effect as typing ^C (which prints a newline first).
*/
let firstPaintThisReadLine = false
/** @type {((v: string | null) => void) | null} */
let pendingResolve = null
@@ -298,11 +304,14 @@ export async function createFishReadLine(ctx, opts) {
const curEnd = displayPos(strBefore, termCols)
const paintRowCount = lineEnd.rows + 1
const needFullRefresh =
firstPaintThisReadLine ||
lineEnd.rows > 0 ||
curEnd.rows > 0 ||
prevCursorRows > 0 ||
prevPaintRowCount > 1
if (firstPaintThisReadLine) firstPaintThisReadLine = false
const out = prompt + coloredLine + ghostWrite
function moveUp(n) {
@@ -694,6 +703,7 @@ export async function createFishReadLine(ctx, opts) {
pendingResolve = resolve
prevCursorRows = 0
prevPaintRowCount = 0
firstPaintThisReadLine = true
line = ''
lines = ['']
currentLineIndex = 0
+9 -1
View File
@@ -1,6 +1,14 @@
import b4a from 'b4a'
import c from 'compact-encoding'
import { PROTOCOL_NAME } from 'bare-os-protocol/constants.js'
import { BOOT_TIMEOUT_MS } from './boot-splash.js'
/** Default matches boot race; override with BARE_OS_MBR_READ_TIMEOUT_MS (was hardcoded 10s). */
function mbrReadTimeoutMs() {
const n = Number(globalThis.process?.env?.BARE_OS_MBR_READ_TIMEOUT_MS)
if (Number.isFinite(n) && n > 0) return Math.min(Math.trunc(n), 300_000)
return BOOT_TIMEOUT_MS
}
export class SwarmDisk {
constructor() {
@@ -255,7 +263,7 @@ export class SwarmDisk {
return new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error('MBR read timeout')),
10000
mbrReadTimeoutMs()
)
this.pendingReads.set(index, (data) => {
clearTimeout(timeout)