// P1 CROSS-MACHINE capstone: one `mirall daemon` per machine, driven through the real
// `mirall` CLI, mirroring a folder stortron → tabertron over the public DHT.
// stortron: serves an owned folder tabertron (here): mirrors it
// Run from tabertron: bare cli/capstone-xmachine.js
const fs = require('bare-fs')
const path = require('bare-path')
const { spawn } = require('bare-subprocess')
const REPO = path.join(__dirname, '..')
const BARE = Bare.argv[0]
const MIRALL = path.join(REPO, 'bin', 'mirall.js')
const ST_DIR = '~/mirall-p0'
const ST = { sock: '/tmp/mcap/st.sock', store: '/tmp/mcap/st-store', dl: '/tmp/mcap/st-dl', share: '/tmp/mcap/st-share' }
const TB = { sock: '/tmp/mcap/tb.sock', store: '/tmp/mcap/tb-store', dl: '/tmp/mcap/tb-dl', mirror: '/tmp/mcap/tb-mirror' }
let failed = false
function ok (cond, msg) { console.log((cond ? ' ok ' : ' FAIL ') + msg); if (!cond) failed = true }
function sleep (ms) { return new Promise((r) => { const t = setTimeout(r, ms); t.unref && t.unref() }) }
function run (cmd, args, { timeout = 90000 } = {}) {
return new Promise((resolve) => {
const child = spawn(cmd, args)
let out = '', err = ''
child.stdout.on('data', (d) => { out += d })
child.stderr.on('data', (d) => { err += d })
const to = setTimeout(() => { try { child.kill() } catch {} }, timeout); to.unref && to.unref()
child.on('exit', (code) => { clearTimeout(to); resolve({ code, out, err }) })
child.on('error', (e) => { clearTimeout(to); resolve({ code: -1, out, err: err + String(e) }) })
})
}
function sshQuote (s) { return "'" + s.replace(/'/g, "'\\''") + "'" }
function storSh (cmdline) { return run('ssh', ['stortron', `bash -lc ${sshQuote('cd ' + ST_DIR + ' && ' + cmdline)}`]) }
function storMirall (argstr) { return storSh('./node_modules/.bin/bare bin/mirall.js ' + argstr) }
function tabMirall (args) { return run(BARE, [MIRALL, ...args]) }
function parse (res, label) {
if (res.code !== 0) throw new Error(label + ' failed (code ' + res.code + '): ' + (res.err.trim() || res.out.trim()).slice(0, 300))
try { return JSON.parse(res.out) } catch { throw new Error(label + ' bad JSON: ' + res.out.slice(0, 200)) }
}
async function waitUntil (name, fn, { ms = 120000, every = 3000 } = {}) {
const start = Date.now()
for (;;) {
try { if (await fn()) return } catch {}
if (Date.now() - start > ms) throw new Error('timeout waiting for: ' + name)
await sleep(every)
}
}
let tbDaemon = null
async function cleanup () {
console.log('--- cleanup ---')
try { await storSh("pkill -f '/tmp/mcap/st.sock' || true") } catch {}
if (tbDaemon) { try { tbDaemon.kill() } catch {} }
}
;(async () => {
try {
// Fresh dirs on both machines (/tmp/mcap is per-machine).
await storSh('rm -rf /tmp/mcap && mkdir -p ' + ST.share)
fs.rmSync('/tmp/mcap', { recursive: true, force: true })
for (const d of [TB.store, TB.dl, TB.mirror]) fs.mkdirSync(d, { recursive: true })
// Boot one daemon per machine.
await storSh(`setsid nohup ./node_modules/.bin/bare bin/mirall.js daemon --storage ${ST.store} --downloads ${ST.dl} --sock ${ST.sock} >/tmp/mcap/daemon.log 2>&1 & echo started`)
tbDaemon = spawn(BARE, [MIRALL, 'daemon', '--storage', TB.store, '--downloads', TB.dl, '--sock', TB.sock], { stdio: 'ignore' })
await waitUntil('tabertron daemon sock', async () => fs.existsSync(TB.sock), { ms: 40000, every: 1000 })
await waitUntil('stortron daemon sock', async () => (await storSh(`test -S ${ST.sock} && echo Y`)).out.includes('Y'), { ms: 40000, every: 2000 })
ok(true, 'one mirall daemon up on each machine')
// --- stortron: become a peer + serve a folder ---
const stPub = parse(await storMirall(`profile set --name Stortron --sock ${ST.sock}`), 'stor profile:set').publicKey
const spaceId = parse(await storMirall(`space create --name Photos --sock ${ST.sock}`), 'stor space:create').spaceId
const inviteRes = await storMirall(`space invite --space ${spaceId} --sock ${ST.sock}`)
if (inviteRes.code !== 0) throw new Error('stor space:invite failed: ' + inviteRes.err)
const invite = inviteRes.out.trim() // the CLI prints the raw invite string (not JSON)
const shareId = parse(await storMirall(`share create --space ${spaceId} --name Photos --sock ${ST.sock}`), 'stor share:create').id
await storSh(`printf 'hello cross-machine mirror' > ${ST.share}/hello.txt && seq 1 5000 > ${ST.share}/data.bin`)
parse(await storMirall(`share mount --space ${spaceId} --share ${shareId} --path ${ST.share} --sock ${ST.sock}`), 'stor share:mount')
ok(true, 'stortron serves an owned folder (hello.txt + data.bin) in space ' + spaceId)
// --- tabertron: join + mirror ---
parse(await tabMirall(['profile', 'set', '--name', 'Tabertron', '--sock', TB.sock]), 'tab profile:set')
parse(await tabMirall(['space', 'join', '--invite', invite, '--sock', TB.sock]), 'tab space:join')
ok(true, 'tabertron joined the space')
// Seeing the peer's share proves the two daemons connected over the DHT + replicated the catalog.
await waitUntil('tabertron sees stortron\'s share', async () => {
const r = await tabMirall(['share', 'list', '--space', spaceId, '--sock', TB.sock])
if (r.code !== 0) return false
try { return JSON.parse(r.out).some((s) => s.id === shareId) } catch { return false }
}, { ms: 150000, every: 3000 })
ok(true, 'tabertron sees the share (cross-machine connection + catalog replicated)')
parse(await tabMirall(['mirror', '--space', spaceId, '--owner', stPub, '--share', shareId, '--path', TB.mirror, '--sock', TB.sock]), 'tab mirror')
await waitUntil('files materialize on tabertron', async () => fs.existsSync(path.join(TB.mirror, 'hello.txt')) && fs.existsSync(path.join(TB.mirror, 'data.bin')), { ms: 150000, every: 3000 })
// --- verify bytes, cross-machine ---
const txt = fs.readFileSync(path.join(TB.mirror, 'hello.txt')).toString()
ok(txt === 'hello cross-machine mirror', 'hello.txt content matches across machines')
const stSha = (await storSh(`sha256sum ${ST.share}/data.bin`)).out.trim().split(/\s+/)[0]
const tbSha = (await run('sha256sum', [path.join(TB.mirror, 'data.bin')])).out.trim().split(/\s+/)[0]
ok(stSha && stSha === tbSha, 'data.bin sha256 matches across machines (' + (stSha || '?').slice(0, 12) + '…)')
console.log(failed
? 'CAPSTONE FAIL'
: 'CAPSTONE PASS: stortron served a folder; tabertron mirrored it over the DHT; bytes match — one mirall daemon per machine, all via the CLI')
} catch (err) {
console.log('CAPSTONE FAIL:', err.message)
failed = true
} finally {
await cleanup()
}
Bare.exit(failed ? 1 : 0)
})()