Files
bare-operating-system/packages/bare-os-booter/test.bare-os-www.js
T
2026-04-22 20:40:39 -04:00

423 lines
13 KiB
JavaScript

import test from 'brittle'
import b4a from 'b4a'
import http from 'node:http'
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 { createVfs } from './lib/vfs.js'
import { createBareOsIpc } from './lib/bare-os-ipc.js'
import {
bareOsWwwInitdEnabled,
bareOsWwwResolveStaticPath,
BARE_OS_WWW_DEFAULT_INDEX,
ensureBareOsWwwHomeDefaults,
maybeRegisterBareOsWwwInitd,
maybeRestartBareOsWwwAfterIdentity
} from './lib/bare-os-www-initd.js'
import { findBareServiceDefinition } from './lib/bare-initd.js'
import {
bareHolesailManagedPersistentSeedLooksValid,
bareHolesailManagedReadState,
bareHolesailManagedWriteState
} from './lib/bare-holesail-managed.js'
import {
bareOsWwwHolesailConnectionId,
ensureBareOsWwwHolesailTunnel
} from './lib/bare-os-www-holesail.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
}
function testCtx(drive, personal, env = {}) {
const shellEnv = {
HOME: '/home/user',
PATH: '/bin',
USER: 'user',
UID: '1000',
GID: '1000',
PWD: '/home/user',
BARE_OS_EXIT_STATUS: '0',
BARE_OS_SSH_LISTEN_PORT: '0',
...env
}
const bareOsIpc = createBareOsIpc()
const vfs = createVfs(drive, personal, shellEnv, null, { bareOsIpc })
return {
drive,
personalDrive: personal,
vfs,
bareOsIpc,
env: shellEnv,
console,
b4a
}
}
test('bareOsWwwInitdEnabled respects BARE_OS_WWW_INITD', (t) => {
t.ok(bareOsWwwInitdEnabled({}))
t.absent(bareOsWwwInitdEnabled({ BARE_OS_WWW_INITD: '0' }))
t.absent(bareOsWwwInitdEnabled({ BARE_OS_WWW_INITD: 'false' }))
})
test('bareOsWwwResolveStaticPath', (t) => {
const d = '/home/user/.www'
t.is(bareOsWwwResolveStaticPath(d, '/'), `${d}/index.html`)
t.is(bareOsWwwResolveStaticPath(d, '/index.html'), `${d}/index.html`)
t.is(bareOsWwwResolveStaticPath(d, '/a/b.css'), `${d}/a/b.css`)
t.is(bareOsWwwResolveStaticPath(d, '/docs/'), `${d}/docs/index.html`)
t.absent(bareOsWwwResolveStaticPath(d, '/../x'))
})
test('vfs routes HOME/.www to HDMS www mount when present', async (t) => {
const dir = testCorestoreDir('vfswww')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pvwww'))
const wwwDrive = new Hyperdrive(store.namespace('wwwalias'))
await sys.ready()
await personal.ready()
await wwwDrive.ready()
await wwwDrive.put('/hello.txt', b4a.from('from-www-drive'))
const shellEnv = {
HOME: '/home/user',
USER: 'user',
PATH: '/bin',
PWD: '/home/user'
}
const mntRef = {
getMounts: () =>
new Map([['www', { drive: wwwDrive, writable: true }]])
}
const vfs = createVfs(sys, personal, shellEnv, mntRef, {
bareOsIpc: createBareOsIpc()
})
const r = vfs.route('/home/user/.www/hello.txt')
t.is(r.drive, wwwDrive)
t.is(r.path, '/hello.txt')
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('bare-os-www serves default index on ephemeral port', async (t) => {
const dir = testCorestoreDir('wwwsrv')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pwww'))
await sys.ready()
await personal.ready()
const port = 19000 + Math.floor(Math.random() * 1000)
const ctx = testCtx(sys, personal, {
BARE_OS_WWW_PORT: String(port),
BARE_OS_WWW_HOLESAIL: '0'
})
maybeRegisterBareOsWwwInitd(ctx.env)
const svc = findBareServiceDefinition('bare-os-www')
t.ok(svc && typeof svc.start === 'function')
await /** @type {{ start: (ctx: unknown) => Promise<void> }} */ (svc).start(ctx)
const body = await new Promise((resolve, reject) => {
http.get(`http://127.0.0.1:${port}/`, (res) => {
t.is(res.statusCode, 200)
const chunks = []
res.on('data', (c) => chunks.push(c))
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
res.on('error', reject)
}).on('error', reject)
})
t.ok(String(body).includes('Bare OS'))
t.ok(String(body).includes('~/.www'))
const headLen = await new Promise((resolve, reject) => {
const r = http.request(
{ hostname: '127.0.0.1', port, path: '/', method: 'HEAD' },
(res) => {
t.is(res.statusCode, 200)
const cl = res.headers['content-length']
t.ok(cl && Number(cl) > 100, 'HEAD Content-Length reflects body size')
res.resume()
resolve(Number(cl))
}
)
r.on('error', reject)
r.end()
})
t.ok(headLen > 100)
await /** @type {{ stop: (ctx: unknown) => Promise<void> }} */ (svc).stop(ctx)
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('bare-os-www redirects /dir → /dir/ so relative asset URLs resolve', async (t) => {
const dir = testCorestoreDir('wwwsubdir')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('psub'))
await sys.ready()
await personal.ready()
const port = 18000 + Math.floor(Math.random() * 500)
const ctx = testCtx(sys, personal, {
BARE_OS_WWW_PORT: String(port),
BARE_OS_WWW_HOLESAIL: '0'
})
await ctx.vfs.mkdir(ctx.vfs.resolveLogical('~/.www/test'), { recursive: true })
await ctx.vfs.writeFile(
ctx.vfs.resolveLogical('~/.www/test/index.html'),
b4a.from('SUBDIR_INDEX_BODY', 'utf8')
)
maybeRegisterBareOsWwwInitd(ctx.env)
const svc = findBareServiceDefinition('bare-os-www')
t.ok(svc && typeof svc.start === 'function')
await /** @type {{ start: (ctx: unknown) => Promise<void> }} */ (svc).start(ctx)
await new Promise((resolve, reject) => {
http.get(`http://127.0.0.1:${port}/test`, (res) => {
t.is(res.statusCode, 308)
t.is(res.headers.location, '/test/')
res.resume()
res.on('end', resolve)
res.on('error', reject)
}).on('error', reject)
})
await new Promise((resolve, reject) => {
http.get(`http://127.0.0.1:${port}/test?q=1`, (res) => {
t.is(res.statusCode, 308)
t.is(res.headers.location, '/test/?q=1')
res.resume()
res.on('end', resolve)
res.on('error', reject)
}).on('error', reject)
})
const bodySlash = await new Promise((resolve, reject) => {
http.get(`http://127.0.0.1:${port}/test/`, (res) => {
t.is(res.statusCode, 200)
const chunks = []
res.on('data', (c) => chunks.push(c))
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
res.on('error', reject)
}).on('error', reject)
})
t.is(String(bodySlash), 'SUBDIR_INDEX_BODY')
await /** @type {{ stop: (ctx: unknown) => Promise<void> }} */ (svc).stop(ctx)
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('ensureBareOsWwwHolesailTunnel persists managed server entry', async (t) => {
const dir = testCorestoreDir('wwwhs')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pwwhs'))
await sys.ready()
await personal.ready()
const ctx = testCtx(sys, personal, {
USER: 'guest',
HOME: '/home/guest',
UID: '65534',
GID: '65534',
PWD: '/home/guest'
})
const p = 18188
const id = bareOsWwwHolesailConnectionId(p)
const empty = { version: 1, connections: {} }
await bareHolesailManagedWriteState(
ctx,
/** @type {Record<string, string | undefined>} */ (ctx.env),
empty
)
await ensureBareOsWwwHolesailTunnel(ctx, ctx.env, p)
const { state } = await bareHolesailManagedReadState(
ctx,
/** @type {Record<string, string | undefined>} */ (ctx.env)
)
const row = state.connections[id]
t.ok(row && /** @type {{ server?: boolean }} */ (row).server === true)
t.is(/** @type {{ port?: number }} */ (row).port, p)
t.is(/** @type {{ host?: string }} */ (row).host, '127.0.0.1')
const seedStr = /** @type {{ seed?: string }} */ (row).seed
t.ok(seedStr && bareHolesailManagedPersistentSeedLooksValid(seedStr))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('ensureBareOsWwwHolesailTunnel preserves existing hs key when normalizing row', async (t) => {
const dir = testCorestoreDir('wwwhskey')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pwwhsk'))
await sys.ready()
await personal.ready()
const ctx = testCtx(sys, personal, {
USER: 'guest',
HOME: '/home/guest',
UID: '65534',
GID: '65534',
PWD: '/home/guest'
})
const p = 18189
const id = bareOsWwwHolesailConnectionId(p)
const keepKey = 'hs://0000keepthiskey000000000000000000000000000000000000000000'
await bareHolesailManagedWriteState(
ctx,
/** @type {Record<string, string | undefined>} */ (ctx.env),
{
version: 1,
connections: {
[id]: {
server: true,
port: 19999,
host: '0.0.0.0',
enabled: true,
key: keepKey
}
}
}
)
await ensureBareOsWwwHolesailTunnel(ctx, ctx.env, p)
const { state } = await bareHolesailManagedReadState(
ctx,
/** @type {Record<string, string | undefined>} */ (ctx.env)
)
const row = /** @type {{ port?: number, host?: string, key?: string }} */ (
state.connections[id]
)
t.is(row.port, p)
t.is(row.host, '127.0.0.1')
t.is(row.key, keepKey)
const migratedSeed = /** @type {{ seed?: string }} */ (row).seed
t.ok(migratedSeed && bareHolesailManagedPersistentSeedLooksValid(migratedSeed))
t.is(
migratedSeed,
'keepthiskey000000000000000000000000000000000000000000'.toLowerCase()
)
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('BARE_OS_WWW_DEFAULT_INDEX is non-trivial HTML', (t) => {
t.ok(BARE_OS_WWW_DEFAULT_INDEX.includes('<!DOCTYPE html>'))
t.ok(BARE_OS_WWW_DEFAULT_INDEX.includes('Bare OS'))
})
test('ensureBareOsWwwHomeDefaults creates ~/.www/index.html', async (t) => {
const dir = testCorestoreDir('wwwdefaults')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('pdef'))
await sys.ready()
await personal.ready()
const ctx = testCtx(sys, personal)
await ensureBareOsWwwHomeDefaults(ctx)
const idx = ctx.vfs.resolveLogical('~/.www/index.html')
const buf = await ctx.vfs.readFile(idx)
t.ok(buf && buf.byteLength > 50)
t.ok(b4a.toString(buf, 'utf8').includes('Bare OS'))
await store.close()
rmSync(dir, { recursive: true, force: true })
})
test('maybeRestartBareOsWwwAfterIdentity rebinds HTTP server to new HOME', async (t) => {
const dir = testCorestoreDir('wwwrebind')
const store = new Corestore(dir)
const sys = new Hyperdrive(store)
const personal = new Hyperdrive(store.namespace('prebind'))
await sys.ready()
await personal.ready()
const port = 19500 + Math.floor(Math.random() * 800)
const shellEnv = {
HOME: '/home/alice',
PATH: '/bin',
USER: 'alice',
UID: '1000',
GID: '1000',
PWD: '/home/alice',
BARE_OS_EXIT_STATUS: '0',
BARE_OS_SSH_LISTEN_PORT: '0',
BARE_OS_WWW_PORT: String(port),
BARE_OS_WWW_HOLESAIL: '0'
}
const bareOsIpc = createBareOsIpc()
const vfs = createVfs(sys, personal, shellEnv, null, { bareOsIpc })
const ctx = {
drive: sys,
personalDrive: personal,
vfs,
bareOsIpc,
env: shellEnv,
console,
b4a
}
await vfs.mkdir(vfs.resolveLogical('~/.www'), { recursive: true })
await vfs.writeFile(
vfs.resolveLogical('~/.www/index.html'),
b4a.from('ALICE', 'utf8')
)
shellEnv.HOME = '/home/bob'
shellEnv.USER = 'bob'
shellEnv.PWD = '/home/bob'
await vfs.mkdir(vfs.resolveLogical('~/.www'), { recursive: true })
await vfs.writeFile(
vfs.resolveLogical('~/.www/index.html'),
b4a.from('BOB', 'utf8')
)
shellEnv.HOME = '/home/alice'
shellEnv.USER = 'alice'
shellEnv.PWD = '/home/alice'
maybeRegisterBareOsWwwInitd(ctx.env)
const svc = findBareServiceDefinition('bare-os-www')
t.ok(svc && typeof svc.start === 'function')
await /** @type {{ start: (c: unknown) => Promise<void> }} */ (svc).start(ctx)
const fetchText = () =>
new Promise((resolve, reject) => {
http.get(`http://127.0.0.1:${port}/`, (res) => {
const chunks = []
res.on('data', (c) => chunks.push(c))
res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))
res.on('error', reject)
}).on('error', reject)
})
const fetchTextRetry = async (want, ms = 3000) => {
const deadline = Date.now() + ms
let last = /** @type {unknown} */ (null)
while (Date.now() < deadline) {
try {
const txt = await fetchText()
if (txt === want) return txt
last = txt
} catch {
/* port may still be rebound after restart */
}
await new Promise((r) => setTimeout(r, 25))
}
throw new Error(
`expected body ${JSON.stringify(want)}, last ${JSON.stringify(last)}`
)
}
t.is(await fetchText(), 'ALICE')
shellEnv.HOME = '/home/bob'
shellEnv.USER = 'bob'
shellEnv.PWD = '/home/bob'
await maybeRestartBareOsWwwAfterIdentity(ctx)
t.is(await fetchTextRetry('BOB'), 'BOB')
await /** @type {{ stop: (c: unknown) => Promise<void> }} */ (svc).stop(ctx)
await store.close()
rmSync(dir, { recursive: true, force: true })
})