Compare commits

...

1 Commits

Author SHA1 Message Date
Nikhil Sonti
1f504702ec feat(build-tools): add cache:sync:dev for local tarball seeding
Seeds ~/.browseros-dev/cache/vm/ from ./dist/ without touching R2, so
devs can test the server against a freshly-built tarball before anything
is published to cdn.browseros.com. Hardcodes arm64 since all devs are on
Apple Silicon; refuses to run unless NODE_ENV=development; idempotent
(skips copy on sha256 match).

Also fixes the R2_BUCKET default in .env.sample from browseros-artifacts
to browseros to match the actual bucket.
2026-04-23 10:30:04 -07:00
4 changed files with 104 additions and 2 deletions

View File

@@ -2,7 +2,7 @@
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=browseros-artifacts
R2_BUCKET=browseros
# Public CDN base - used by cache:sync to GET manifest and artifacts
R2_PUBLIC_BASE_URL=https://cdn.browseros.com

View File

@@ -67,4 +67,13 @@ bun run --filter @browseros/build-tools emit-manifest -- --slice agents:openclaw
NODE_ENV=development bun run --filter @browseros/build-tools cache:sync
```
Development cache files land under `~/.browseros-dev/cache/vm/images/`. Production-mode cache files land under `~/.browseros/cache/vm/images/`.
Pulls the published manifest and tarballs from R2 (`cdn.browseros.com/vm/`). Development cache files land under `~/.browseros-dev/cache/vm/images/`. Production-mode cache files land under `~/.browseros/cache/vm/images/`.
## Seed the dev cache from a local build
```bash
bun run --filter @browseros/build-tools build:tarball -- --agent openclaw --arch arm64
NODE_ENV=development bun run --filter @browseros/build-tools cache:sync:dev
```
`cache:sync:dev` hardcodes `arm64` (all devs are on Apple Silicon), skips R2 entirely, and writes an arm64-only manifest + tarball into `~/.browseros-dev/cache/vm/` from `./dist/`. It refuses to run unless `NODE_ENV=development`. Use this when you want to test the server against a local tarball without publishing.

View File

@@ -10,6 +10,7 @@
"upload": "bun run scripts/upload-to-r2.ts",
"download": "bun run scripts/download-from-r2.ts",
"cache:sync": "bun run scripts/cache-sync.ts",
"cache:sync:dev": "bun run scripts/cache-sync-dev.ts",
"smoke:tarball": "bun run scripts/smoke-tarball.ts",
"test": "bun test",
"typecheck": "tsc --noEmit"

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bun
import { copyFile, mkdir, readFile, stat, writeFile } from 'node:fs/promises'
import { homedir } from 'node:os'
import path from 'node:path'
import { PATHS } from '@browseros/shared/constants/paths'
import type { Arch } from './common/arch'
import {
type AgentEntry,
type AgentManifest,
type Bundle,
tarballKey,
} from './common/manifest'
import { sha256File, verifySha256 } from './common/sha256'
const ARM64: Arch = 'arm64'
if (process.env.NODE_ENV !== 'development') {
throw new Error(
'cache:sync:dev refuses to run without NODE_ENV=development — it writes to ~/.browseros-dev/cache/vm/',
)
}
const pkgRoot = path.resolve(import.meta.dir, '..')
const distDir = path.join(pkgRoot, 'dist')
const bundle = JSON.parse(
await readFile(path.join(pkgRoot, 'bundle.json'), 'utf8'),
) as Bundle
const cacheRoot = path.join(
homedir(),
PATHS.DEV_BROWSEROS_DIR_NAME,
PATHS.CACHE_DIR_NAME,
)
const imagesDir = path.join(cacheRoot, 'vm', 'images')
const manifestPath = path.join(cacheRoot, 'vm', 'manifest.json')
await mkdir(imagesDir, { recursive: true })
const agents: Record<string, AgentEntry> = {}
for (const agent of bundle.agents) {
const key = tarballKey(agent.name, agent.version, ARM64)
const srcTarball = path.join(distDir, 'images', path.basename(key))
await assertExists(srcTarball)
const sha256 = await sha256File(srcTarball)
const sizeBytes = (await stat(srcTarball)).size
const destTarball = path.join(cacheRoot, key)
if (await matchesExisting(destTarball, sha256)) {
console.log(`cache hit: ${key}`)
} else {
await mkdir(path.dirname(destTarball), { recursive: true })
await copyFile(srcTarball, destTarball)
await verifySha256(destTarball, sha256)
console.log(`seeded ${key}`)
}
agents[agent.name] = {
image: agent.image,
version: agent.version,
tarballs: { arm64: { key, sha256, sizeBytes } } as AgentEntry['tarballs'],
}
}
const manifest: AgentManifest = {
schemaVersion: 2,
updatedAt: new Date().toISOString(),
agents,
}
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
console.log(`manifest written to ${manifestPath}`)
async function assertExists(filePath: string): Promise<void> {
try {
await stat(filePath)
} catch {
throw new Error(
`missing ${filePath} — run: bun run build:tarball -- --agent <name> --arch arm64`,
)
}
}
async function matchesExisting(
filePath: string,
expectedSha: string,
): Promise<boolean> {
try {
await stat(filePath)
} catch {
return false
}
return (await sha256File(filePath)) === expectedSha
}