import test from 'brittle'
import { mkdtempSync, writeFileSync, symlinkSync, mkdirSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import http from '#node:http'
import b4a from 'b4a'
import crypto from 'hypercore-crypto'
import { startPearGit } from '../index.js'
// #66 (store/bootstrap-app design): the bootstrap artifacts are distributed as
// a content-addressed "bootstrap" tree in the pgh store and OTA-synced to every
// forge instance; `<storage>/bootstrap/` is a SYMLINK to that materialized tree.
// The store + OTA distribution is already-proven machinery (pgh app
// install/update). The genuinely-new glue is the serve side: a symlinked store
// tree is served byte-identical by the static handler, transparently following
// the symlink, traversal-safe.
//
// PASS = GET /bootstrap/<file> through the symlink returns the tree's bytes
// byte-identical; unknown → 404; `..` escape → 403.
function tmpStorage () { return mkdtempSync(join(tmpdir(), 'pgv3-bsrv-')) }
function tmpSock () { return join(mkdtempSync(join(tmpdir(), 'pgv3-bsrv-sock-')), 'svc.sock') }
function fetchBytes (url) {
return new Promise((resolve, reject) => {
const u = new URL(url)
const req = http.request({ method: 'GET', hostname: u.hostname, port: u.port, path: u.pathname + u.search }, (res) => {
const parts = []
res.on('data', c => parts.push(c))
res.on('end', () => resolve({ status: res.statusCode, body: b4a.concat(parts) }))
res.on('error', reject)
})
req.on('error', reject)
req.end()
})
}
test('static handler serves a store bootstrap-tree via the <storage>/bootstrap symlink', async (t) => {
// A materialized store "bootstrap" tree (what OTA syncs down): install.sh +
// tarball + .sha256, real files (the store hardlinks content here).
const treeDir = mkdtempSync(join(tmpdir(), 'pgv3-bs-tree-'))
const installSh = '#!/bin/sh\necho "pgh installer ' + b4a.toString(crypto.randomBytes(8), 'hex') + '"\n'
const tarball = crypto.randomBytes(2 * 1024 * 1024)
writeFileSync(join(treeDir, 'install.sh'), installSh)
writeFileSync(join(treeDir, 'pgh-bootstrap-bare-linux-x64.tar.gz'), tarball)
writeFileSync(join(treeDir, 'pgh-bootstrap-bare-linux-x64.tar.gz.sha256'),
crypto.data(tarball).toString('hex') + ' pgh-bootstrap-bare-linux-x64.tar.gz\n')
// <storage>/bootstrap is a SYMLINK to the store tree (the link the OTA hook
// maintains). We pass it as the daemon's bootstrapDir.
const storage = tmpStorage()
mkdirSync(storage, { recursive: true })
const bootstrapLink = join(storage, 'bootstrap')
symlinkSync(treeDir, bootstrapLink)
const node = await startPearGit({
storage,
uds: tmpSock(),
http: { port: 0 },
swarm: false,
autoImportSSH: false,
bootstrapDir: bootstrapLink
})
t.teardown(async () => { try { await node.close() } catch {} })
const port = node.svc.http.server.address().port
const base = 'http://127.0.0.1:' + port + '/bootstrap/'
const sh = await fetchBytes(base + 'install.sh')
t.is(sh.status, 200, 'install.sh served 200 through the symlink')
t.ok(sh.body.equals(b4a.from(installSh)), 'install.sh bytes byte-identical')
const tar = await fetchBytes(base + 'pgh-bootstrap-bare-linux-x64.tar.gz')
t.is(tar.status, 200, 'tarball served 200')
t.ok(tar.body.equals(tarball), 'tarball bytes byte-identical (large file through symlink)')
const sha = await fetchBytes(base + 'pgh-bootstrap-bare-linux-x64.tar.gz.sha256')
t.is(sha.status, 200, '.sha256 served 200')
const miss = await fetchBytes(base + 'does-not-exist.tar.gz')
t.is(miss.status, 404, 'unknown artifact → 404')
// Path traversal must be refused even though the dir is a symlink.
const esc = await fetchBytes('http://127.0.0.1:' + port + '/bootstrap/../../etc/hostname')
t.ok(esc.status === 403 || esc.status === 404, 'traversal escape refused (403/404), not served')
})