()
| 446 | } |
| 447 | |
| 448 | async function main(): Promise<void> { |
| 449 | // Install logging + crash handlers first thing so any early throws |
| 450 | // (parsePort, parseMountPoint, resolveFuseBackend) still land in |
| 451 | // LOG_FILE when set. |
| 452 | const teardownLogging = installLogging(process.env.LOG_FILE); |
| 453 | |
| 454 | rejectLegacyFuseEnv(process.env); |
| 455 | |
| 456 | const port = parsePort(process.env.PORT); |
| 457 | const mountPoint = parseMountPoint(process.env.MOUNT_POINT); |
| 458 | // FUSE_MOUNT picks the backend. auto (default) probes /dev/fuse |
| 459 | // or macFUSE and falls back to the userspace shim. fuse / macfuse |
| 460 | // require their respective real backend. shim forces the userspace |
| 461 | // polling shim. none skips the mount entirely; HTTP + /api + /ws |
| 462 | // still come up so tests and tooling can talk to computerd's RPC surface. |
| 463 | const fuseMountMode = parseFuseMountMode(process.env.FUSE_MOUNT); |
| 464 | const backend: FUSEBackend = await resolveFuseBackend(fuseMountMode); |
| 465 | console.log(`[info] FUSE_MOUNT=${fuseMountMode} resolved to backend=${backend.kind}`); |
| 466 | |
| 467 | const upstreamUrl = process.env.UPSTREAM_URL?.trim(); |
| 468 | let upstreamClient: WorkspaceClient | undefined; |
| 469 | if (upstreamUrl !== undefined && upstreamUrl.length > 0) { |
| 470 | // Use the `ws` package's WebSocket (not Node's built-in |
| 471 | // global) so the dial negotiates permessage-deflate against |
| 472 | // the upstream's WebSocketServer. Node 22's built-in |
| 473 | // WebSocket doesn't advertise the deflate extension. |
| 474 | upstreamClient = createWorkspaceClient({ |
| 475 | url: upstreamUrl, |
| 476 | WebSocketImpl: WebSocket as unknown as typeof globalThis.WebSocket, |
| 477 | }); |
| 478 | } |
| 479 | const { vfs, db, stopSync } = await createNodeVirtualFileSystem({ |
| 480 | upstream: upstreamClient?.sync, |
| 481 | }); |
| 482 | const info: ComputerdInfo = { backend, mountPoint, port }; |
| 483 | |
| 484 | let fuse: FuseMount | undefined; |
| 485 | // When running on the userspace shim, capture the typed handle |
| 486 | // so we can wire `flush()` and `reconcileNow()` into the SyncRPC |
| 487 | // afterApply / beforeFetch hooks below. A real FUSE mount serves |
| 488 | // reads straight from the VFS, so it doesn't need either settle. |
| 489 | let shim: ShimMount | undefined; |
| 490 | if (backend.kind !== "none") { |
| 491 | // The VFS stores everything under `mountPoint` so capnweb pulls, |
| 492 | // shim materialisation, and shell `exec` agree on absolute |
| 493 | // paths. Pre-create the mount directory in the VFS so FUSE's |
| 494 | // first getattr on "/" can stat the mount root. |
| 495 | vfs.mkdirSync(mountPoint, { recursive: true }); |
| 496 | await mkdir(mountPoint, { recursive: true }); |
| 497 | |
| 498 | if (backend.kind === "shim") { |
| 499 | shim = await mountShim({ vfs, mountPoint }); |
| 500 | fuse = shim; |
| 501 | } else { |
| 502 | fuse = await mountFuse({ |
| 503 | backend, |
| 504 | mountPoint, |
| 505 | vfs, |
no test coverage detected