Skip to content

Device Mount Pattern

Synthetic devices (containerFS, future factotum, compose) are mounted during namespace construction via buildNamespace(), not ad-hoc in boot paths.

How It Works

buildNamespace() accepts NamespaceBuildOpts.devices which extends the existing DeviceSpec with optional device fields:

interface DeviceSpec {
// Base devices (always available)
dev?: boolean // /dev
proc?: boolean // /proc
srv?: boolean // /srv
user?: string // /dev/user
tty?: 'inherit' | 'none'
// Synthetic devices (opt-in)
container?: boolean | ContainerDeviceOpts // /dev/container + CLI bins
// Future: factotum, compose, etc.
}

When a device field is truthy, buildNamespace() creates the device’s fileserver, mounts it, and registers any associated bins — all within the canonical namespace construction sequence.

Adding a New Device

To add a new synthetic device (e.g., factotum for M4):

1. Define the opts type

container/types.ts
export interface FactotumDeviceOpts {
readonly authProvider?: AuthProvider
}

Add it to DeviceSpec:

export interface DeviceSpec {
// ... existing fields ...
readonly factotum?: boolean | FactotumDeviceOpts
}

2. Wire in buildNamespace()

Add a step after the existing device steps (15b, 15c, etc.):

// Step 15c: [DEVICE] factotum — /dev/factotum + auth bins
let factotumManager: FactotumManager | undefined
if (devices.factotum !== undefined && devices.factotum !== false) {
const opts = typeof devices.factotum === 'object' ? devices.factotum : {}
const { createFactotum } = await import('../auth/factotum')
const fm = createFactotum(opts)
mounts.set('/dev/factotum', fm.server)
for (const [name, fn] of Object.entries(fm.bins)) {
registerExec(name, fn)
}
factotumManager = fm.manager
}

3. Expose on InterpretedNamespace

export interface InterpretedNamespace {
// ... existing fields ...
readonly factotumManager: FactotumManager | undefined
}

4. Consume in RootContainer or boot paths

const ns = await buildNamespace(ctx, caps, {
devices: { factotum: { authProvider: myProvider } },
})
// ns.factotumManager is available for higher-level API

Design Principles

  1. Single construction site. All device wiring happens in buildNamespace(). Boot paths and RootContainer.start() both call it — no inline wiring.

  2. Lazy imports. Device modules are dynamically imported to avoid circular dependencies at module load time.

  3. Opt-in, not default. bootFromImage() does not wire devices unless explicitly requested. RootContainer.start() opts in via its NamespaceBuildOpts.

  4. Type-safe access. Each device exposes its manager on InterpretedNamespace so consumers can interact with it after construction.

  5. Deterministic ordering. Devices are mounted in a fixed sequence (step 15b, 15c, …) before union binds (step 16). Mount order is load-bearing.