OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / bootstrap-serve.test.js

87 lines · javascript ref:
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')
})