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 }} */ (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 }} */ (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 }} */ (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 }} */ (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} */ (ctx.env), empty ) await ensureBareOsWwwHolesailTunnel(ctx, ctx.env, p) const { state } = await bareHolesailManagedReadState( ctx, /** @type {Record} */ (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} */ (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} */ (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('')) 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 }} */ (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 }} */ (svc).stop(ctx) await store.close() rmSync(dir, { recursive: true, force: true }) })