1075 lines
28 KiB
JavaScript
1075 lines
28 KiB
JavaScript
import {
|
|
test,
|
|
b4a,
|
|
Hyperdrive,
|
|
Corestore,
|
|
mkdirSync,
|
|
rmSync,
|
|
readFile,
|
|
readdir,
|
|
path,
|
|
fileURLToPath,
|
|
PassThrough,
|
|
XTERM_CLEAR_SCROLLBACK_AND_VIEWPORT,
|
|
runKernelFromSource,
|
|
runBinCommand,
|
|
createGitFsFromVfs,
|
|
runGitCli,
|
|
createStreamLineReader,
|
|
sanitizeInteractiveShellLine,
|
|
createVfs,
|
|
BARE_OS_PROC_FILE_TO_ID_HYPERCORE_PACK_HRPC_LIFECYCLE,
|
|
parseHdmsPairArgv,
|
|
hcCrypto,
|
|
idEnc,
|
|
decodeRwShareOffer,
|
|
decodeShareOffer,
|
|
HDMS_AUTOPASS_SHARE_KEY,
|
|
HDMS_AUTOPASS_SHARE_RW_KEY,
|
|
normalizeWriterSecretHex,
|
|
buildBareOsHypercorePackHrpcLifecycleProcJson,
|
|
buildBareOsPearCorestoreHrpcProcJson,
|
|
buildBareOsPearInspectLoggerTlsProcJson,
|
|
buildBareOsBareRuntimeProtoMuxProcJson,
|
|
buildBareOsBootGraphProcJson,
|
|
pathnameExpandShellWord,
|
|
createBareOsIpc,
|
|
BareOsSwarmConnectionManager,
|
|
createBareOsLifecycleStateMachine,
|
|
runTransactionalLifecycleHooks,
|
|
bareOsRegisterCorestoreSuspendResumeHooks,
|
|
bareOsInstallCorestoreSuspendResumeHooks,
|
|
bareOsFindOrphanProcessRows,
|
|
topologicalOrderKernelExtensions,
|
|
resolveKernelExtensionsFull,
|
|
createBareOsProtomuxAliasRegistry,
|
|
bareOsHttpUrlAllowed,
|
|
bareOsParseBareAclText,
|
|
bareOsAclDeniesSubject,
|
|
raceWithAbortAndTimeout,
|
|
tokenize,
|
|
expandWord,
|
|
execShellLine,
|
|
expandArgvAliases,
|
|
defaultShellAliases,
|
|
loadBarerc,
|
|
BARERC_SKELETON,
|
|
splitTokensBySemicolon,
|
|
splitTokensByAndOr,
|
|
bareOsShellAstSnapshot,
|
|
planShellRedirections,
|
|
buildShellExecutionGraph,
|
|
dispatchShellTrapSignal,
|
|
BARE_OS_EXIT_STATUS_ENV,
|
|
syncBareOsExitStatusEnv,
|
|
getBareOsPipelineLimits,
|
|
DEFAULT_PIPELINE_MAX_STAGES,
|
|
tokenizeBareShellLineDetailed,
|
|
decodeBareOsDollarQuote,
|
|
applyBareOsThemeFromEnv,
|
|
bareOsListThemeNames,
|
|
bareParseLsColors,
|
|
bareSerializeLsColors,
|
|
bareLsColorOpenSgrFromMap,
|
|
bareDefaultDircolorsDatabase,
|
|
bareParseDircolorsDatabase,
|
|
BARE_OS_CTX_API_VERSION,
|
|
buildBareOsRuntimeCaps,
|
|
bareOsBareModulesEnabled,
|
|
bareOsBareModuleManifestEmbeddedRef,
|
|
buildBareCtxObjectFromHost,
|
|
buildPearCtxObjectFromHost,
|
|
loadBareModuleManifest,
|
|
maybeMergeBareFromDrive,
|
|
maybeBareOsBuildBinManifest,
|
|
createFishReadLine,
|
|
fuzzyMatch,
|
|
stripAnsi,
|
|
parseHistoryFile,
|
|
formatHistoryFile,
|
|
dedupeConsecutiveHistory,
|
|
searchHistoryEntries,
|
|
shouldPersistShellHistoryCommand,
|
|
scrubShellHistoryEntries,
|
|
SHELL_BUILTINS,
|
|
resolveShellPromptHookSegment,
|
|
parseCompletionContext,
|
|
rankCompletionItems,
|
|
suggestGhostFromHistory,
|
|
suggestGhostFromFs,
|
|
ghostReplaceCurrentWord,
|
|
longestCommonCompletionPrefix,
|
|
completeLine,
|
|
levenshtein,
|
|
fieldMatches,
|
|
dowFieldMatches,
|
|
parseCronLine,
|
|
jobMatchesDate,
|
|
bareInitdReadinessSnapshot,
|
|
bareInitdShutdownActiveUnitsReverse,
|
|
getBareServiceRuntime,
|
|
registerBareInitdDisposer,
|
|
registerKernelShutdownHook,
|
|
runKernelShutdownHooks,
|
|
startBareInitd,
|
|
stopBareInitd,
|
|
getLastBareInitdDagSnapshotJson,
|
|
appendVarLog,
|
|
ensureBareOsVarLogTree,
|
|
KERNEL_CONSOLE_LOG,
|
|
BOOT_LOG,
|
|
bareOsKernelMetricsReset,
|
|
bareOsKernelMetricsSnapshot,
|
|
parseUnitDropInText,
|
|
bareOsHyperswarmOptsFromEnv,
|
|
bareOsBootJoinPeerHexFromEnv,
|
|
bareOsBootInitPrefetchEnabled,
|
|
bareOsBootWarmBinDownloadDiffEnabled,
|
|
bareOsProcessTableSnapshot,
|
|
buildBareOsSyscallsProcJson,
|
|
bareOsParseSendmsgScmRights,
|
|
wantPosixSocketScmRights,
|
|
parseBareOsHrpcAllowlistJson,
|
|
bareOsHrpcAllowlistDeniesRoute,
|
|
buildBareOsProtomuxExtensionsProcJson,
|
|
createBareOsDiskOsBridge,
|
|
buildBareOsReplicationLiveSketch,
|
|
evaluateBareOsPeerAdmission,
|
|
bareOsFormatPeerAdmissionAuditEvent,
|
|
bareOsPeerAdmissionAuditRateAllow,
|
|
parseDelegateAllowSet,
|
|
isDelegateKindAllowed,
|
|
bareOsAuditAppend,
|
|
bareOsAuditVerifyRows,
|
|
bareOsDrainFcntlLockWaiters,
|
|
buildBareOsReplicationOperatorSurfaceProcJson,
|
|
DEFAULT_CURL_USER_AGENT,
|
|
DEFAULT_WGET_USER_AGENT,
|
|
filenameFromContentDisposition,
|
|
encodeUstarHeader,
|
|
runTarCli,
|
|
require,
|
|
__dirname,
|
|
TAR_BLK,
|
|
concatTarParts,
|
|
headerFromInit,
|
|
testCorestoreDir,
|
|
testCtx,
|
|
personalHomeBacking,
|
|
readBuiltBin
|
|
} from './test/_helpers.js'
|
|
|
|
test('kernel_program proc JSON includes program metadata', async (t) => {
|
|
const o = buildBareOsHypercorePackHrpcLifecycleProcJson(
|
|
'kernel_program',
|
|
{ BARE_OS_BOOT_SAFE_MODE: '1' }
|
|
)
|
|
t.is(o.schema, 3)
|
|
t.is(o.program, 'bare-os-kernel-program')
|
|
t.ok(o.operatorSketches && typeof o.operatorSketches === 'object')
|
|
t.ok(o.hooks && o.hooks.bootSafeMode === true)
|
|
})
|
|
|
|
test('giant_phase_program proc id returns same builder as kernel_program', async (t) => {
|
|
const a = buildBareOsHypercorePackHrpcLifecycleProcJson('kernel_program', {})
|
|
const b = buildBareOsHypercorePackHrpcLifecycleProcJson('giant_phase_program', {})
|
|
t.is(a.program, b.program)
|
|
t.is(a.schema, b.schema)
|
|
})
|
|
|
|
test('parseUnitDropInText parses ConditionPathExists and Restart keys', async (t) => {
|
|
const d = parseUnitDropInText(
|
|
'[Unit]\nConditionPathExists=/tmp/x\nRestart=on-failure\nRestartSec=2\n'
|
|
)
|
|
t.is(d.conditionPathExists, '/tmp/x')
|
|
t.is(d.restart, 'on-failure')
|
|
t.is(d.restartSec, 2)
|
|
})
|
|
|
|
test('bareOsIpc.signalProcessGroup pushes bareOsProcessGroupSignal to scoped channels', async (t) => {
|
|
const ipc = createBareOsIpc()
|
|
ipc.create('c1')
|
|
ipc.create('c2')
|
|
ipc.assignProcessGroup('c1', 7)
|
|
ipc.assignProcessGroup('c2', 7)
|
|
const p1 = ipc.takeJson('c1')
|
|
const p2 = ipc.takeJson('c2')
|
|
const out = ipc.signalProcessGroup(7, 'SIGUSR1')
|
|
t.is(out.delivered, 2)
|
|
const j1 = await p1
|
|
const j2 = await p2
|
|
t.is(j1.method, 'bareOsProcessGroupSignal')
|
|
t.is(j1.signal, 'SIGUSR1')
|
|
t.is(j2.pgid, 7)
|
|
const st = ipc.stats()
|
|
t.ok(st.processGroups && st.processGroups.byPgid)
|
|
t.ok(st.processGroups.byPgid['pgid:7'].includes('c1'))
|
|
})
|
|
|
|
test('kernel ext incremental reload runs only new scripts when hot reload env set', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const extDir = ['01-a.json']
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const vfsFiles = new Map()
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
scripts: ['/lib/bare-os/extensions/a.js']
|
|
})
|
|
)
|
|
}
|
|
if (p === '/etc/bare-os/kernel.ext.d/02-b.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'b',
|
|
scripts: ['/lib/bare-os/extensions/b.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') {
|
|
for (const x of extDir) yield x
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_KERNEL_EXT_D_HOT_RELOAD: '1'
|
|
},
|
|
drive,
|
|
b4a,
|
|
vfs: {
|
|
async readFile(p) {
|
|
return vfsFiles.get(p) ?? null
|
|
},
|
|
async writeFile(p, body) {
|
|
vfsFiles.set(
|
|
p,
|
|
body instanceof Uint8Array ? body : b4a.from(String(body))
|
|
)
|
|
}
|
|
},
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(path) {
|
|
ran.push(path)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.alike(ran, ['/lib/bare-os/extensions/a.js'])
|
|
t.ok(typeof ctx.bareOsReloadKernelExtDropinsSafe === 'function')
|
|
extDir.push('02-b.json')
|
|
const out = await ctx.bareOsReloadKernelExtDropinsSafe()
|
|
t.ok(out.ok)
|
|
t.alike(out.ranScripts, ['/lib/bare-os/extensions/b.js'])
|
|
t.alike(ran, [
|
|
'/lib/bare-os/extensions/a.js',
|
|
'/lib/bare-os/extensions/b.js'
|
|
])
|
|
const jr = b4a.toString(vfsFiles.get('/run/bare-os/kernel-ext-reload.ndjson'))
|
|
t.ok(jr.includes('incremental'))
|
|
})
|
|
|
|
test('kernel.ext.d minCtxApiVersion skips drop-in when ctx API too low', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-hi.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'hi',
|
|
minCtxApiVersion: '99.0.0',
|
|
scripts: ['/lib/bare-os/extensions/hi.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') yield '01-hi.json'
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
bareOsCtxApiVersion: '1.0.0',
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(path) {
|
|
ran.push(path)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(ran.length, 0)
|
|
})
|
|
|
|
test('kernel.ext.d conflictsWith strict skips extension scripts', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
conflictsWith: ['b'],
|
|
scripts: ['/lib/bare-os/extensions/a.js']
|
|
})
|
|
)
|
|
}
|
|
if (p === '/etc/bare-os/kernel.ext.d/02-b.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'b',
|
|
scripts: ['/lib/bare-os/extensions/b.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') {
|
|
yield '01-a.json'
|
|
yield '02-b.json'
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_BOOT_POLICY_STRICT: '1'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(p) {
|
|
ran.push(p)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(ran.length, 0)
|
|
})
|
|
|
|
test('kernel.ext.d dependency cycle strict skips extension scripts', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const ran = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
dependsOn: ['b'],
|
|
scripts: ['/lib/bare-os/extensions/a.js']
|
|
})
|
|
)
|
|
}
|
|
if (p === '/etc/bare-os/kernel.ext.d/02-b.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'b',
|
|
dependsOn: ['a'],
|
|
scripts: ['/lib/bare-os/extensions/b.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') {
|
|
yield '01-a.json'
|
|
yield '02-b.json'
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_BOOT_POLICY_STRICT: '1'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript(p) {
|
|
ran.push(p)
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(ran.length, 0)
|
|
})
|
|
|
|
test('stock kernel BARE_OS_BOOT_SAFE_MODE skips kernel.ext.d scripts', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
let extRan = false
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/kernel.ext.d/01-a.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
id: 'a',
|
|
scripts: ['/lib/bare-os/extensions/x.js']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir(d) {
|
|
if (d === '/etc/bare-os/kernel.ext.d') yield '01-a.json'
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_BOOT_SAFE_MODE: '1' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
},
|
|
async bareOsRunImageScript() {
|
|
extRan = true
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(extRan, false)
|
|
})
|
|
|
|
test('boot.policy denySeedRpcMethods merges into kernel env deny list', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/boot.policy.json') {
|
|
return b4a.from(
|
|
JSON.stringify({
|
|
denySeedRpcMethods: ['replication_queue', 'capabilities']
|
|
})
|
|
)
|
|
}
|
|
return null
|
|
},
|
|
async *readdir() {}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: {
|
|
BARE_OS_BOOT_POLICY: '1',
|
|
BARE_OS_BOOT_SKIP_STAGES:
|
|
'profile,rc,rc.d,rc.local,kernel.d,onboot,selftest',
|
|
BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS: 'version,replication_queue'
|
|
},
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {}, warn() {} },
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(
|
|
ctx.env.BARE_OS_BOOT_POLICY_DENY_SEED_RPC_METHODS,
|
|
'version,replication_queue,capabilities'
|
|
)
|
|
})
|
|
|
|
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('stock kernel init.js onboot runs multiple BARE_OS_ONBOOT lines when SKIP_REPL', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get() {
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo one\n#c\n echo two' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.alike(execLines, ['echo one', 'echo two'])
|
|
})
|
|
|
|
test('stock kernel init.js runs rc.local before onboot', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc.local') return b4a.from('echo rc-local')
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo onboot-line' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(execLines[0], 'echo rc-local')
|
|
t.is(execLines[1], 'echo onboot-line')
|
|
})
|
|
|
|
test('stock kernel init.js runs kernel.d after rc.local before onboot', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc.local') return b4a.from('echo rc-local')
|
|
if (p === '/etc/bare-os/kernel.d/05-k.mod') return b4a.from('echo kmod')
|
|
return null
|
|
},
|
|
async *readdir(dir) {
|
|
if (dir === '/etc/bare-os/kernel.d') yield '05-k.mod'
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo onboot-line' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(execLines[0], 'echo rc-local')
|
|
t.is(execLines[1], 'echo kmod')
|
|
t.is(execLines[2], 'echo onboot-line')
|
|
})
|
|
|
|
test('stock kernel init.js BARE_OS_BOOT_TRACE=json emits boot trace JSON on stderr', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const stderr = []
|
|
const drive = {
|
|
async get() {
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: false,
|
|
env: { BARE_OS_BOOT_TRACE: 'json' },
|
|
drive,
|
|
b4a,
|
|
console: {
|
|
log() {},
|
|
error(...a) {
|
|
stderr.push(a.join(' '))
|
|
}
|
|
},
|
|
readLine: async () => null,
|
|
async execLine() {
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
const phases = stderr
|
|
.map((l) => {
|
|
try {
|
|
return JSON.parse(l)
|
|
} catch {
|
|
return null
|
|
}
|
|
})
|
|
.filter(Boolean)
|
|
const bootSteps = phases.filter(
|
|
(o) => o && typeof o.ms === 'number' && o.type !== 'bootOutput'
|
|
)
|
|
t.ok(bootSteps.length >= 3)
|
|
t.ok(bootSteps.some((o) => o.phase === 'rc.local'))
|
|
t.ok(bootSteps.some((o) => o.step === 'rc.local'))
|
|
t.ok(bootSteps.some((o) => o.phase === 'kernel.d'))
|
|
t.ok(bootSteps.some((o) => o.step === 'kernel.d'))
|
|
t.ok(bootSteps.every((o) => o.bootTraceSchemaVersion === 2))
|
|
t.ok(bootSteps.every((o) => typeof o.ms === 'number'))
|
|
t.ok(
|
|
phases.some(
|
|
(o) => o && o.type === 'bootOutput' && o.step === 'banner'
|
|
),
|
|
'bootOutput trace for guest-visible banner'
|
|
)
|
|
})
|
|
|
|
test('stock kernel BARE_OS_RC_D_SKIP skips matching rc.d snippets', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc.d/05-skip.me')
|
|
return b4a.from('echo skipped-should-not-run')
|
|
if (p === '/etc/bare-os/rc.d/10-keep') return b4a.from('echo kept')
|
|
return null
|
|
},
|
|
async *readdir(dir) {
|
|
if (dir === '/etc/bare-os/rc.d') {
|
|
yield '05-skip.me'
|
|
yield '10-keep'
|
|
}
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_ONBOOT: 'echo tail', BARE_OS_RC_D_SKIP: '05-*' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execLines.push(String(line).trim())
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.ok(!execLines.some((l) => l.includes('skipped')))
|
|
t.is(execLines[0], 'echo kept')
|
|
t.is(execLines[1], 'echo tail')
|
|
})
|
|
|
|
test('stock kernel BARE_OS_BOOT_STRICT stops boot after execLine throw', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
const execLines = []
|
|
let exitArg = null
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/bare-os/rc')
|
|
return b4a.from('echo first\n__throw__\necho third')
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_BOOT_STRICT: '1' },
|
|
drive,
|
|
b4a,
|
|
console: { log() {}, error() {} },
|
|
readLine: async () => null,
|
|
requestBooterExit(code) {
|
|
exitArg = code
|
|
},
|
|
async execLine(line) {
|
|
const t0 = String(line).trim()
|
|
if (t0 === '__throw__') throw new Error('boot-fail')
|
|
execLines.push(t0)
|
|
return 'ok'
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.alike(execLines, ['echo first'])
|
|
t.is(exitArg, 1)
|
|
})
|
|
|
|
test('stock kernel BARE_OS_BOOT_DRY_RUN skips execLine and reports bootDryRun in boot proc', async (t) => {
|
|
const src = await readFile(
|
|
path.join(__dirname, '../../kernel/init.js'),
|
|
'utf8'
|
|
)
|
|
/** @type {string[]} */
|
|
const errs = []
|
|
/** @type {string[]} */
|
|
const execCalls = []
|
|
const drive = {
|
|
async get(p) {
|
|
if (p === '/etc/os-release') return b4a.from('ID=test\n')
|
|
if (p === '/etc/motd') return b4a.from('')
|
|
if (p === '/etc/bare-os/rc') return b4a.from('echo dry-rc\n')
|
|
if (p === '/etc/bare-os/onboot') return b4a.from('echo dry-onboot\n')
|
|
return null
|
|
},
|
|
async *readdir() {
|
|
/* empty */
|
|
}
|
|
}
|
|
const ctx = {
|
|
bareOsSkipRepl: true,
|
|
env: { BARE_OS_BOOT_DRY_RUN: '1' },
|
|
drive,
|
|
b4a,
|
|
console: {
|
|
log() {},
|
|
warn() {},
|
|
error: (...a) => errs.push(a.join(' '))
|
|
},
|
|
readLine: async () => null,
|
|
async execLine(line) {
|
|
execCalls.push(String(line || ''))
|
|
return 'should-not-run'
|
|
},
|
|
bareOsPublishBootReady(patch) {
|
|
this._bootReady = patch
|
|
}
|
|
}
|
|
await runKernelFromSource(src, ctx)
|
|
t.is(execCalls.length, 0)
|
|
t.ok(
|
|
errs.some(
|
|
(m) =>
|
|
m.includes('bootDryRunExecLine') ||
|
|
m.includes('bootDryRunOnboot') ||
|
|
m.includes('[boot-dry-run]')
|
|
)
|
|
)
|
|
t.is(ctx._bootReady?.subsystems?.kernel?.programProc?.bootDryRun, true)
|
|
})
|
|
|
|
test('runBinCommand runs /bin helper', async (t) => {
|
|
const dir = testCorestoreDir('bin')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('p'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put(
|
|
'/bin/hello',
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.out.push(argv.join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['hello', 'a', 'b'])
|
|
t.is(out[0], 'hello a b')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs bare hello.js from cwd on personal drive', async (t) => {
|
|
const dir = testCorestoreDir('barejs')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pj'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'hello.js'),
|
|
b4a.from(`
|
|
async function run(ctx, argv) {
|
|
ctx.out.push(argv.join(' '))
|
|
}
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['hello.js', 'x', 'y'])
|
|
t.is(out[0], 'hello.js x y')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand strips shebang from user script', async (t) => {
|
|
const dir = testCorestoreDir('shebang')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('psh'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'x.js'),
|
|
b4a.from(`#!/usr/bin/env bare
|
|
async function run(ctx) { ctx.out.push('ok') }
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./x.js'])
|
|
t.is(out[0], 'ok')
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs top-level user script without run()', async (t) => {
|
|
const dir = testCorestoreDir('norun')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pnr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'plain.js'),
|
|
b4a.from(`ctx.out.push('only-top')`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./plain.js'])
|
|
t.is(out[0], 'only-top')
|
|
t.is(ctx.exitCode, 0)
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand runs top-level then run() when both present', async (t) => {
|
|
const dir = testCorestoreDir('bothrun')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pbr'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'hybrid.js'),
|
|
b4a.from(`ctx.out.push('first')
|
|
async function run(ctx) { ctx.out.push('second') }
|
|
`)
|
|
)
|
|
const out = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.out = out
|
|
await runBinCommand(ctx, ['./hybrid.js'])
|
|
t.alike(out, ['first', 'second'])
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('runBinCommand user script error is caught and logged', async (t) => {
|
|
const dir = testCorestoreDir('throwjs')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('ptj'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'bad.js'),
|
|
b4a.from(`async function run() { test() }`)
|
|
)
|
|
const errs = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: () => {},
|
|
error: (...a) => {
|
|
errs.push(a.join(' '))
|
|
}
|
|
}
|
|
await runBinCommand(ctx, ['./bad.js'])
|
|
t.ok(errs.length > 0)
|
|
t.ok(errs.some((e) => /test|not defined|ReferenceError/i.test(e)))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('ls hides dotfiles unless -a', async (t) => {
|
|
const lsPath = path.join(__dirname, '../../kernel/bin/ls')
|
|
const lsSrc = await readFile(lsPath, 'utf8')
|
|
const dir = testCorestoreDir('lsdot')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
const personal = new Hyperdrive(store.namespace('pls'))
|
|
await drive.ready()
|
|
await personal.ready()
|
|
await drive.put('/bin/ls', b4a.from(lsSrc))
|
|
await personal.put(
|
|
personalHomeBacking('/home/user', 'shown.txt'),
|
|
b4a.from('')
|
|
)
|
|
await personal.put(personalHomeBacking('/home/user', '.hidden'), b4a.from(''))
|
|
const lines = []
|
|
const ctx = testCtx(drive, personal)
|
|
ctx.console = {
|
|
log: (s) => lines.push(String(s)),
|
|
error: (...a) => lines.push(a.join(' '))
|
|
}
|
|
await runBinCommand(ctx, ['ls'])
|
|
const flat = lines.join('\n')
|
|
t.ok(flat.includes('shown.txt'))
|
|
t.ok(!flat.includes('hidden'))
|
|
lines.length = 0
|
|
await runBinCommand(ctx, ['ls', '-a'])
|
|
const flatA = lines.join('\n')
|
|
t.ok(flatA.includes('hidden'))
|
|
await store.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|
|
|
|
test('createStreamLineReader yields lines after newline', async (t) => {
|
|
const stdin = new PassThrough()
|
|
const chunks = []
|
|
const stdout = {
|
|
write(s) {
|
|
chunks.push(String(s))
|
|
}
|
|
}
|
|
const readLine = createStreamLineReader(stdin, stdout)
|
|
const p = readLine('> ')
|
|
stdin.write('help\n')
|
|
t.is(await p, 'help')
|
|
t.ok(chunks.join('').includes('> '))
|
|
stdin.end()
|
|
})
|
|
|
|
test('createStreamLineReader yields lines after carriage return (SSH PTY)', async (t) => {
|
|
const stdin = new PassThrough()
|
|
const stdout = { write() {} }
|
|
const readLine = createStreamLineReader(stdin, stdout)
|
|
const p = readLine('> ')
|
|
stdin.write('pwd\r')
|
|
t.is(await p, 'pwd')
|
|
const p2 = readLine('> ')
|
|
stdin.write('a\r\n')
|
|
t.is(await p2, 'a')
|
|
stdin.end()
|
|
})
|
|
|
|
test('sanitizeInteractiveShellLine applies DEL and strips controls', async (t) => {
|
|
t.is(sanitizeInteractiveShellLine('ls'), 'ls')
|
|
t.is(sanitizeInteractiveShellLine('a\x7Fb'), 'b')
|
|
t.is(sanitizeInteractiveShellLine('ab\x08'), 'a')
|
|
t.is(sanitizeInteractiveShellLine('x\x01y'), 'xy')
|
|
})
|
|
|
|
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 })
|
|
})
|
|
|
|
test('Hyperdrive checkout read-only boot probe', async (t) => {
|
|
const dir = testCorestoreDir('seed-checkout-ro')
|
|
const store = new Corestore(dir)
|
|
const drive = new Hyperdrive(store)
|
|
await drive.ready()
|
|
await drive.put('/boot/init.js', b4a.from('async function start() { return 1 }'))
|
|
const version = drive.version
|
|
const ro = drive.checkout(version, { writable: false })
|
|
const buf = await ro.get('/boot/init.js')
|
|
t.ok(buf)
|
|
t.is(b4a.toString(buf), 'async function start() { return 1 }')
|
|
await t.exception(async () => {
|
|
await ro.put('/boot/init.js', b4a.from('mutate'))
|
|
})
|
|
await ro.close()
|
|
await drive.close()
|
|
rmSync(dir, { recursive: true, force: true })
|
|
})
|