Fixes for http
This commit is contained in:
@@ -48,8 +48,10 @@ export async function ensureBareOsWwwHolesailTunnel(ctx, env, port) {
|
||||
if (!ctx.b4a || typeof ctx.b4a.from !== 'function') return
|
||||
|
||||
const { path, state } = await bareHolesailManagedReadState(ctx, env)
|
||||
/** Holesail server mode requires an explicit loopback host. */
|
||||
const wantHost = '127.0.0.1'
|
||||
/** @type {Record<string, unknown>} */
|
||||
const want = { server: true, port: p, enabled: true }
|
||||
const want = { server: true, port: p, host: wantHost, enabled: true }
|
||||
const existing = state.connections[id]
|
||||
if (existing && typeof existing === 'object') {
|
||||
const ex = /** @type {Record<string, unknown>} */ (existing)
|
||||
@@ -59,7 +61,9 @@ export async function ensureBareOsWwwHolesailTunnel(ctx, env, port) {
|
||||
ex.enabled === undefined || ex.enabled === null || String(ex.enabled) === ''
|
||||
? true
|
||||
: bareHolesailEnvTruthy(ex.enabled)
|
||||
if (server && curPort === p && enabled) {
|
||||
const rawHost = String(ex.host ?? '').trim()
|
||||
const hostOk = rawHost === wantHost
|
||||
if (server && curPort === p && enabled && hostOk) {
|
||||
if (bareHolesailManagedServiceIsRunning()) {
|
||||
try {
|
||||
await bareHolesailManagedStartOne(ctx, env, id)
|
||||
|
||||
@@ -221,17 +221,27 @@ function wwwLog(ctx, line) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure `~/.www/` exists and contains default `index.html` when missing (idempotent).
|
||||
* Called from identity unlock/register and from the `bare-os-www` initd unit.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
*/
|
||||
async function ensureDefaultIndex(ctx) {
|
||||
export async function ensureBareOsWwwHomeDefaults(ctx) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.resolveLogical !== 'function') return
|
||||
const idx = vfs.resolveLogical('~/.www/index.html')
|
||||
try {
|
||||
await vfs.readFile(idx)
|
||||
return
|
||||
const cur = await vfs.readFile(idx)
|
||||
if (cur != null) {
|
||||
const u8 =
|
||||
cur instanceof Uint8Array
|
||||
? cur
|
||||
: ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from(cur)
|
||||
: null
|
||||
if (u8 && u8.byteLength > 0) return
|
||||
}
|
||||
} catch {
|
||||
/* missing */
|
||||
/* missing or unreadable */
|
||||
}
|
||||
try {
|
||||
await vfs.mkdir(vfs.resolveLogical('~/.www'), { recursive: true })
|
||||
@@ -253,6 +263,13 @@ async function ensureDefaultIndex(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
*/
|
||||
async function ensureDefaultIndex(ctx) {
|
||||
await ensureBareOsWwwHomeDefaults(ctx)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('node:http').IncomingMessage} req
|
||||
* @param {import('node:http').ServerResponse} res
|
||||
@@ -275,13 +292,44 @@ async function handleWwwRequest(req, res, ctx, docrootNorm) {
|
||||
return
|
||||
}
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function') {
|
||||
if (
|
||||
!vfs ||
|
||||
typeof vfs.readFile !== 'function' ||
|
||||
typeof vfs.stat !== 'function'
|
||||
) {
|
||||
res.statusCode = 500
|
||||
res.end('VFS unavailable\n')
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (req.method === 'HEAD') {
|
||||
const st = await vfs.stat(abs)
|
||||
if (!st || st.type === 'directory') {
|
||||
res.statusCode = 404
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.end(
|
||||
'<!DOCTYPE html><html><head><meta charset="utf-8"/><title>404</title></head><body><h1>404</h1></body></html>\n'
|
||||
)
|
||||
return
|
||||
}
|
||||
const len = Number(st.size) || 0
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', mimeForPath(abs))
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
res.setHeader('Content-Length', String(len))
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
|
||||
const buf = await vfs.readFile(abs)
|
||||
if (buf == null) {
|
||||
res.statusCode = 404
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.end(
|
||||
'<!DOCTYPE html><html><head><meta charset="utf-8"/><title>404</title></head><body><h1>404</h1></body></html>\n'
|
||||
)
|
||||
return
|
||||
}
|
||||
const u8 =
|
||||
buf instanceof Uint8Array
|
||||
? buf
|
||||
@@ -291,18 +339,16 @@ async function handleWwwRequest(req, res, ctx, docrootNorm) {
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', mimeForPath(abs))
|
||||
res.setHeader('Cache-Control', 'no-cache')
|
||||
if (req.method === 'HEAD') {
|
||||
res.setHeader('Content-Length', String(u8.byteLength))
|
||||
res.end()
|
||||
return
|
||||
}
|
||||
res.setHeader('Content-Length', String(u8.byteLength))
|
||||
res.end(u8)
|
||||
} catch (e) {
|
||||
const code = /** @type {{ code?: string }} */ (e)?.code
|
||||
if (code === 'ENOENT') {
|
||||
res.statusCode = 404
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
||||
res.end('<!DOCTYPE html><html><head><meta charset="utf-8"/><title>404</title></head><body><h1>404</h1></body></html>\n')
|
||||
res.end(
|
||||
'<!DOCTYPE html><html><head><meta charset="utf-8"/><title>404</title></head><body><h1>404</h1></body></html>\n'
|
||||
)
|
||||
return
|
||||
}
|
||||
res.statusCode = 500
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
SSHD_DEFAULT_AUTHORIZED_KEYS_FILE
|
||||
} from './sshd-config-parse.js'
|
||||
import { ensureBareOsVarLogTree } from './bare-os-var-log.js'
|
||||
import { ensureBareOsWwwHomeDefaults } from './bare-os-www-initd.js'
|
||||
|
||||
const LEGACY_ROOT_MIGRATION_STATE = '/.bare-os/migration/legacy-root-v1.json'
|
||||
|
||||
@@ -373,6 +374,11 @@ export async function applyLoginKeys(ctx, keys) {
|
||||
wipeSecret(ctx)
|
||||
await applyUnlockedEnv(ctx, keys.publicKey, keys.secretKey)
|
||||
await ensureBareDir(ctx)
|
||||
try {
|
||||
await ensureBareOsWwwHomeDefaults(ctx)
|
||||
} catch (e) {
|
||||
ctx.console?.error?.('[bare-os] ensureBareOsWwwHomeDefaults: ' + (e?.message || e))
|
||||
}
|
||||
maybeAppendVaultMultisigContinuityAudit(ctx, 'applyLoginKeys')
|
||||
}
|
||||
|
||||
@@ -451,6 +457,11 @@ export async function registerIdentity(ctx, passphrase) {
|
||||
await applyUnlockedEnv(ctx, publicKey, secretKey)
|
||||
secureZero(secretKey)
|
||||
await ensureBareDir(ctx)
|
||||
try {
|
||||
await ensureBareOsWwwHomeDefaults(ctx)
|
||||
} catch (e) {
|
||||
ctx.console?.error?.('[bare-os] ensureBareOsWwwHomeDefaults: ' + (e?.message || e))
|
||||
}
|
||||
try {
|
||||
await bareOsAppendVaultRotationCheckpoint(ctx, {
|
||||
kind: 'identity_register',
|
||||
@@ -494,6 +505,11 @@ export async function unlockIdentity(ctx, passphrase) {
|
||||
wipeSecret(ctx)
|
||||
await applyUnlockedEnv(ctx, publicKey, secretKey)
|
||||
await ensureBareDir(ctx)
|
||||
try {
|
||||
await ensureBareOsWwwHomeDefaults(ctx)
|
||||
} catch (e) {
|
||||
ctx.console?.error?.('[bare-os] ensureBareOsWwwHomeDefaults: ' + (e?.message || e))
|
||||
}
|
||||
try {
|
||||
await bareOsAppendVaultRotationCheckpoint(ctx, {
|
||||
kind: 'identity_unlock',
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
bareOsWwwInitdEnabled,
|
||||
bareOsWwwResolveStaticPath,
|
||||
BARE_OS_WWW_DEFAULT_INDEX,
|
||||
ensureBareOsWwwHomeDefaults,
|
||||
maybeRegisterBareOsWwwInitd
|
||||
} from './lib/bare-os-www-initd.js'
|
||||
import { findBareServiceDefinition } from './lib/bare-initd.js'
|
||||
@@ -133,6 +134,21 @@ test('bare-os-www serves default index on ephemeral port', async (t) => {
|
||||
})
|
||||
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 })
|
||||
@@ -156,6 +172,7 @@ test('ensureBareOsWwwHolesailTunnel persists managed server entry', async (t) =>
|
||||
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')
|
||||
await store.close()
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
})
|
||||
@@ -164,3 +181,20 @@ 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 })
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user