### Quick Start: Configure Route and Start Server Source: https://earendil-works.github.io/gondolin/ingress Configure a route inside the VM to direct traffic to a specific port, then start an HTTP server on that port. Access the server from the host using the provided ingress URL. ```bash # Inside the VM configure a route (inside the VM): echo '/ :8000' > /etc/gondolin/listeners And then start a server: python -m http.server 8000 Now from the host: curl -v http://127.0.0.1:58650/ ``` -------------------------------- ### Routing Table Configuration Examples Source: https://earendil-works.github.io/gondolin/ingress Define URL path prefixes and the corresponding guest loopback ports in `/etc/gondolin/listeners`. Comments start with `#`. ```plaintext # Send everything to the server on port 8000 / :8000 # Route /api/* to port 9000, and strip the /api prefix /api :9000 ``` -------------------------------- ### Build Custom Image with Post-Build Commands Source: https://earendil-works.github.io/gondolin/custom-images Build a custom image using a pre-built example configuration that includes post-build commands to install packages like 'llm' and its plugins via pip. Then, execute a command within the custom image to verify the installation. ```bash gondolin build --config host/examples/llm.json --output ./llm-assets GONDOLIN_GUEST_DIR=./llm-assets gondolin exec -- llm --help ``` -------------------------------- ### Create, Start, and Close a VM Instance Source: https://earendil-works.github.io/gondolin/sdk-vm Use the async factory to create a VM instance, ensuring guest assets are available. The VM is automatically started by default, but explicit start and close operations are shown. Remember to close the VM to prevent the QEMU instance from running indefinitely. ```typescript import { VM } from "@earendil-works/gondolin"; const vm = await VM.create({ // set autoStart: false if you want to configure things before boot // autoStart: false, }); // Optional: explicit start (VM.create defaults to autoStart: true) await vm.start(); // ...use the VM... await vm.close(); ``` -------------------------------- ### Enable HTTP Ingress and Route Requests Source: https://earendil-works.github.io/gondolin/sdk-network Expose HTTP servers running inside a guest VM to the host machine. This example sets up an ingress gateway, routes requests to a guest server on port 8000, and starts the server using `vm.exec()`. ```typescript import { VM } from "@earendil-works/gondolin"; const vm = await VM.create(); const ingress = await vm.enableIngress({ listenHost: "127.0.0.1", listenPort: 0, // 0 picks an ephemeral port }); console.log("Ingress:", ingress.url); // Route all requests to the guest server on port 8000 vm.setIngressRoutes([{ prefix: "/", port: 8000, stripPrefix: true }]); // Start a server inside the guest // NOTE: the guest currently executes one command at a time; a long-running // vm.exec() (like a server) will block additional exec requests. const server = vm.exec(["/bin/sh", "-lc", "python -m http.server 8000"], { buffer: false, stdout: "inherit", stderr: "inherit", }); // Now you can reach the guest service from the host at ingress.url // ... await ingress.close(); await vm.close(); ``` -------------------------------- ### Install and Run Gondolin Globally Source: https://earendil-works.github.io/gondolin/cli Install the Gondolin package globally using npm and then run the gondolin binary to start an interactive bash shell. ```bash npm install -g @earendil-works/gondolin gondolin bash ``` -------------------------------- ### Quick Start: Create and Execute Command on VM Source: https://earendil-works.github.io/gondolin/sdk Demonstrates the basic workflow of creating a VM, executing a command, and closing the VM. The `exec` method runs commands via `/bin/sh -lc` by default. ```typescript import { VM } from "@earendil-works/gondolin"; const vm = await VM.create(); // String form runs via `/bin/sh -lc "..."` const result = await vm.exec("curl -sS -f https://example.com/"); console.log("exitCode:", result.exitCode); console.log("stdout:\n", result.stdout); console.log("stderr:\n", result.stderr); await vm.close(); ``` -------------------------------- ### Start Ingress Gateway to Forward Traffic into VM Source: https://earendil-works.github.io/gondolin/cli Initiates a bash session and starts a host ingress gateway on a specified port to forward traffic into the VM. ```bash gondolin bash --listen 127.0.0.1:3000 ``` -------------------------------- ### Enable Ingress via CLI Source: https://earendil-works.github.io/gondolin/ingress Start an interactive VM shell with ingress enabled. The output shows the host URL for accessing the ingress. Configure routes by editing `/etc/gondolin/listeners` inside the VM. ```bash gondolin bash --listen ``` -------------------------------- ### Install Build Dependencies on macOS Source: https://earendil-works.github.io/gondolin/custom-images Install the necessary build tools for custom image creation on macOS using Homebrew. This includes Zig, lz4, and e2fsprogs. ```bash brew install zig@0.15 lz4 e2fsprogs ``` -------------------------------- ### VM Creation and Lifecycle Source: https://earendil-works.github.io/gondolin/sdk-vm Demonstrates the basic lifecycle of a VM instance: creating it using the async factory, optionally starting it, using it, and finally closing it to release resources. The `VM.create` factory ensures guest assets are available and defaults to auto-starting the VM. ```APIDOC ## VM Lifecycle ### Description This snippet shows how to create, start, use, and close a VM instance using the `VM.create` async factory. ### Method `VM.create()` ### Parameters - `options` (object) - Optional configuration for VM creation. - `autoStart` (boolean) - Defaults to `true`. If `false`, the VM will not start automatically after creation, allowing for pre-boot configuration. - `sessionLabel` (string) - A custom label for the VM session, displayed in tools like `gondolin list`. ### Usage ```javascript import { VM } from "@earendil-works/gondolin"; // Create a VM instance (defaults to autoStart: true) const vm = await VM.create({ sessionLabel: "my-custom-label" }); // Optional: Explicitly start the VM if autoStart was false or if you need to ensure it's running // await vm.start(); // ... use the VM instance here ... // Close the VM instance to release resources and stop the QEMU process await vm.close(); ``` ### Notes - It is crucial to call `vm.close()` to prevent the QEMU instance from continuing to run in the background. ``` -------------------------------- ### Start Interactive Bash Session with Mounted Working Directory Source: https://earendil-works.github.io/gondolin/cli Initiates an interactive bash session within the VM, mounting the current host directory as a workspace. ```bash gondolin bash --mount-hostfs "$PWD:/workspace" ``` -------------------------------- ### Install Build Dependencies on Linux (Debian/Ubuntu) Source: https://earendil-works.github.io/gondolin/custom-images Install required build tools on Debian/Ubuntu systems, including lz4, cpio, and e2fsprogs. Ensure Zig 0.15.2 is installed separately from the official Zig website. ```bash # Install Zig 0.15.2 from https://ziglang.org/download/ sudo apt install lz4 cpio e2fsprogs # OCI rootfs ownership fixups may also need debugfs (Ubuntu/Debian include it in e2fsprogs) ``` -------------------------------- ### Generate Initial Build Configuration Source: https://earendil-works.github.io/gondolin/custom-images Generate a default JSON build configuration file that can be used as a starting point for customizing Gondolin guest images. This file can then be edited and passed to the `gondolin build` command. ```bash gondolin build --init-config > build-config.json ``` -------------------------------- ### Configure ShadowProvider Policy Source: https://earendil-works.github.io/gondolin/vfs Implement a custom shadow policy for `ShadowProvider` to selectively hide paths. This example hides `.env` files and anything under `/secrets/`. ```javascript import { ShadowProvider } from "@earendil-works/gondolin"; const provider = new ShadowProvider(backend, { shouldShadow: ({ path }) => path === "/.env" || path.startsWith("/secrets/"), }); ``` -------------------------------- ### Example Gondolin Build Configuration Source: https://earendil-works.github.io/gondolin/custom-images A comprehensive JSON configuration file for building a custom Gondolin guest image. It specifies architecture, distribution, environment variables, Alpine-specific settings, rootfs options, and post-build commands for customization. ```json { "arch": "aarch64", "distro": "alpine", "env": { "FOO": "bar" }, "alpine": { "version": "3.23.0", "kernelPackage": "linux-virt", "kernelImage": "vmlinuz-virt", "rootfsPackages": [ "linux-virt", "rng-tools", "bash", "ca-certificates", "curl", "nodejs", "npm", "uv", "python3", "openssh" ], "initramfsPackages": [], "krunfwVersion": "v5.2.1" }, "rootfs": { "label": "gondolin-root" }, "postBuild": { "copy": [ { "src": "./dist/my-tool.tar.gz", "dest": "/tmp/my-tool.tar.gz" } ], "commands": [ "pip3 install llm llm-anthropic" ] } } ``` -------------------------------- ### Check and Ensure Guest Assets Programmatically Source: https://earendil-works.github.io/gondolin/sdk-storage Use SDK functions to check for the availability of guest assets, get the asset directory path, and download them if they are missing. This ensures the VM has the necessary components to run. ```typescript import { hasGuestAssets, ensureGuestAssets, getAssetDirectory, } from "@earendil-works/gondolin"; console.log("Assets available:", hasGuestAssets()); console.log("Asset directory:", getAssetDirectory()); // Download if needed const assets = await ensureGuestAssets(); console.log("Kernel:", assets.kernelPath); ``` -------------------------------- ### Prefix Matching Example Source: https://earendil-works.github.io/gondolin/ingress Demonstrates how the longest matching prefix determines the route. Longer prefixes take precedence. ```plaintext / :8000 /api :9000 ``` -------------------------------- ### Build Image for Different Architecture Source: https://earendil-works.github.io/gondolin/custom-images Use the `gondolin build` command with the `--arch` flag to create images for architectures other than the host system. This example shows building for x86_64 on an ARM64 Mac. ```bash # Build for x86_64 on an ARM64 Mac gondolin build --arch x86_64 --config build-config.json --output ./x64-assets ``` -------------------------------- ### Start Bash Session with Restricted HTTP Egress and API Token Source: https://earendil-works.github.io/gondolin/cli Launches a bash session with a mounted working directory, restricted HTTP egress, and a usable API token injected as a host secret. ```bash gondolin bash \ --mount-hostfs "$PWD:/workspace" \ --host-secret GITHUB_TOKEN@api.github.com ``` -------------------------------- ### Enable SSH Access to Guest VM Source: https://earendil-works.github.io/gondolin/ssh Use this snippet to enable SSH access to the guest VM. It configures the guest to start `sshd` and sets up host-local port forwarding. The `access` object provides connection details and a command string. ```typescript import { VM } from "@earendil-works/gondolin"; const vm = await VM.create(); await vm.start(); const access = await vm.enableSsh({ user: "root", // default listenHost: "127.0.0.1", listenPort: 0, // 0 picks an ephemeral port }); console.error("SSH:", access.command); // ... use SSH ... await access.close(); await vm.close(); ``` -------------------------------- ### Create and Resume Disk Checkpoints Source: https://earendil-works.github.io/gondolin/sdk-storage Create disk-only checkpoints of a VM's root filesystem state using the `checkpoint` method. These checkpoints can be resumed independently, allowing multiple VMs to start from the same disk state. Ensure `rootfs.mode` is `cow` for checkpointing. ```typescript import path from "node:path"; import { VM } from "@earendil-works/gondolin"; const base = await VM.create(); // Install packages / write to the root filesystem... await base.exec("apk add git"); await base.exec("echo hello > /etc/my-base-marker"); // Note: must be an absolute path const checkpointPath = path.resolve("./dev-base.qcow2"); const checkpoint = await base.checkpoint(checkpointPath); const task1 = await checkpoint.resume(); const task2 = await checkpoint.resume(); // Both VMs start from the same disk state and diverge independently await task1.close(); await task2.close(); checkpoint.delete(); ``` -------------------------------- ### Create VM with MemoryProvider at Root Source: https://earendil-works.github.io/gondolin/vfs Configure the VM to use an in-memory filesystem as the root. This is useful for isolated, temporary storage. ```javascript import { VM, MemoryProvider } from "@earendil-works/gondolin"; const vm = await VM.create({ vfs: { mounts: { "/": new MemoryProvider(), }, }, }); ``` -------------------------------- ### Build Gondolin Custom Guest Assets Source: https://earendil-works.github.io/gondolin/cli Builds and verifies custom guest assets like kernel, initramfs, and rootfs. Use `--init-config` to print a default configuration, `--config` to specify a configuration file, and `--output` to set the output directory. ```bash gondolin build [options] ``` ```bash # Generate a default config gondolin build --init-config > build-config.json # Build and import to the local image store (no explicit output dir) gondolin build --config build-config.json --tag default:latest # Use the tagged image gondolin bash --image default:latest # Optionally keep an explicit output directory too gondolin build --config build-config.json --output ./my-assets --tag default:latest # Use the custom assets directly GondOLIN_GUEST_DIR=./my-assets gondolin bash # Verify an asset directory gondolin build --verify ./my-assets ``` -------------------------------- ### Hide Node Modules but Allow Installs with tmpfs Source: https://earendil-works.github.io/gondolin/vfs Configure ShadowProvider to hide the host's node_modules directory while allowing the guest to create its own in memory using writeMode: 'tmpfs'. This is useful for ensuring clean installs. ```typescript import path from "node:path"; import { VM, RealFSProvider, ShadowProvider, createShadowPathPredicate, } from "@earendil-works/gondolin"; const repoDir = path.resolve("."); const base = new RealFSProvider(repoDir); // First: deny reads/writes to host secret files const secrets = new ShadowProvider(base, { shouldShadow: createShadowPathPredicate(["/.env", "/.npmrc"]), writeMode: "deny", }); // Then: hide the host node_modules, but let the guest write its own const noHostNodeModules = new ShadowProvider(secrets, { shouldShadow: createShadowPathPredicate(["/node_modules"]), writeMode: "tmpfs", }); const vm = await VM.create({ vfs: { mounts: { "/workspace": noHostNodeModules, }, }, }); await vm.exec("cd /workspace && npm install"); ``` -------------------------------- ### Process Execution with Options Source: https://earendil-works.github.io/gondolin/sdk-vm Demonstrates executing a command with options like `buffer: false` to avoid large output buffers and `signal` for cancellation. ```APIDOC ## `vm.exec()` with Options ### Description Executes a command within the VM. Options can control buffering, streaming, and cancellation. ### Method `vm.exec(command, options)` ### Parameters - **command** (string[]) - The command and its arguments to execute. - **options** (object) - Configuration options for the execution. - **buffer** (boolean) - If `false`, stdout/stderr are not buffered. Defaults to `true`. - **signal** (AbortSignal) - An AbortSignal to cancel the execution. ### Request Example (Cancellation) ```javascript const ac = new AbortController(); setTimeout(() => ac.abort(), 1000); try { const result = await vm.exec(["/bin/sleep", "10"], { signal: ac.signal }); console.log("exitCode:", result.exitCode); } catch (err) { // aborting rejects with "exec aborted" console.error(String(err)); } ``` ### Notes - Setting `buffer: false` is recommended for commands that may produce large amounts of output. - When `buffer: false`, you can still stream output using `stdout: "pipe"` or `stderr: "pipe"`. - Aborting a process currently rejects the local promise; it does not guarantee guest process termination. ``` -------------------------------- ### Configure DNS Options with SDK Source: https://earendil-works.github.io/gondolin/network Use the VM.create method to configure DNS resolution modes, trusted servers, and synthetic DNS settings. Ensure 'trusted' mode has usable IPv4 resolvers. ```typescript import { VM } from "@earendil-works/gondolin"; const vm = await VM.create({ dns: { mode: "synthetic", // "synthetic" | "trusted" | "open" // trustedServers: ["1.1.1.1"], // syntheticIPv4: "192.0.2.1", // syntheticIPv6: "2001:db8::1", // syntheticTtlSeconds: 60, }, }); ``` -------------------------------- ### Run Bash Shell in Gondolin VM Source: https://earendil-works.github.io/gondolin Execute a bash shell within a Gondolin micro-VM using the npx command. This is a quick way to start an interactive session. ```bash npx @earendil-works/gondolin bash ``` -------------------------------- ### Snapshot a running session (CLI) Source: https://earendil-works.github.io/gondolin/snapshots Use this command to create a snapshot of a running VM session. This action stops the session. ```bash # Snapshot a running session (stops the session) gondolin snapshot ``` -------------------------------- ### enableIngress Source: https://earendil-works.github.io/gondolin/sdk-network Installs host-side hook points on the ingress gateway for custom request handling, such as allow/deny decisions, path rewriting, and header manipulation. It supports streaming or buffering of responses. ```APIDOC ## enableIngress() ### Description Installs host-side hook points on the ingress gateway. This is useful for allow/deny decisions based on client IP/path/route, rewriting upstream target paths or headers, adding/removing response headers, and optionally buffering responses for body rewriting. ### Method Signature `enableIngress({ hooks: ... })` ### Hooks Configuration - `hooks.isAllowed(info) -> boolean`: Returns `false` to deny the request (default response: `403 forbidden`). For a custom deny response, throw `new IngressRequestBlockedError(...)`. - `hooks.onRequest(request) -> patch`: Rewrites headers and/or upstream target. Can also enable per-request response buffering via `bufferResponseBody: true`. - `hooks.onResponse(response, request) -> patch`: Rewrites status/headers and optionally replaces the body. ### Response Buffering By default, responses are streamed directly. If buffering is enabled (globally via `enableIngress({ bufferResponseBody: true })` or per-request via `onRequest()`), the full upstream response body is buffered before `onResponse()` runs and provided as `response.body`. ### Header Patch Semantics - Set a header to a `string`/`string[]` to set/overwrite it. - Set a header to `null` to delete it. ### Example ```javascript import { IngressRequestBlockedError, VM } from "@earendil-works/gondolin"; const vm = await VM.create(); await vm.enableIngress({ hooks: { isAllowed: ({ clientIp, path }) => { if (path.startsWith("/admin")) { throw new IngressRequestBlockedError( `admin blocked for ${clientIp}`, 403, "Forbidden", "nope\n", ); } return true; }, onRequest: (req) => ({ // Rewrite /api/* -> /* inside the guest backendTarget: req.backendTarget.startsWith("/api/") ? req.backendTarget.slice(4) : req.backendTarget, headers: { "x-added": "1", "x-remove": null }, // Only buffer responses we plan to inspect/modify bufferResponseBody: req.backendTarget.endsWith(".json"), maxBufferedResponseBodyBytes: 8 * 1024 * 1024, }), onResponse: (res) => ({ headers: { "x-ingress": "1" }, body: res.body ? Buffer.from(res.body.toString("utf8").toUpperCase()) : undefined, }), }, }); ``` ``` -------------------------------- ### Run Specific Command Instead of Bash Shell Source: https://earendil-works.github.io/gondolin/cli Executes a specific command within the VM's environment instead of starting the default bash shell. Sets the working directory. ```bash gondolin bash -- claude --cwd /workspace ``` -------------------------------- ### Initialize HTTP Hooks and Environment with Secrets Source: https://earendil-works.github.io/gondolin/secrets Use `createHttpHooks` to configure allowed hosts and secrets. Pass both `httpHooks` and `env` to `VM.create` to ensure the guest has access to placeholder environment variables. ```typescript import { VM, createHttpHooks } from "@earendil-works/gondolin"; const { httpHooks, env } = createHttpHooks({ allowedHosts: ["api.github.com"], secrets: { GITHUB_TOKEN: { hosts: ["api.github.com"], value: process.env.GITHUB_TOKEN!, }, }, }); const vm = await VM.create({ httpHooks, env }); ``` -------------------------------- ### gondolin build Source: https://earendil-works.github.io/gondolin/cli Build and verify custom guest assets, including kernel, initramfs, and rootfs. Supports configuration files, output directories, and architecture selection. ```APIDOC ## `gondolin build` ### Description Build and verify custom guest assets (kernel + initramfs + rootfs). For a full configuration reference and build requirements, see: Building Custom Images. ### Usage ``` gondolin build [options] ``` ### Options * `--init-config` - Print a default build configuration JSON to stdout. * `--config FILE` - Use a build configuration file. * `--output DIR` - Output directory for built assets (optional). * `--arch aarch64|x86_64` - Target architecture. * `--verify DIR` - Verify an asset directory against its `manifest.json`. * `--tag REF` - Tag the built assets in the local image store. * `--quiet` / `-q` - Reduce output verbosity. ### Examples ``` # Generate a default config gondolin build --init-config > build-config.json # Build and import to the local image store (no explicit output dir) gondolin build --config build-config.json --tag default:latest # Use the tagged image gondolin bash --image default:latest # Optionally keep an explicit output directory too gondolin build --config build-config.json --output ./my-assets --tag default:latest # Use the custom assets directly GONDOLIN_GUEST_DIR=./my-assets gondolin bash # Verify an asset directory gondolin build --verify ./my-assets ``` ``` -------------------------------- ### enableSsh Source: https://earendil-works.github.io/gondolin/sdk-network Starts an SSH server (sshd) inside the guest and exposes it via a host-local TCP forwarder, enabling workflows that prefer SSH tooling like scp, rsync, and SSH port forwards. ```APIDOC ## vm.enableSsh() ### Description Starts an `sshd` inside the guest and exposes it via a host-local TCP forwarder for workflows that prefer SSH tooling (scp/rsync/ssh port forwards). ### Method Signature `const access = await vm.enableSsh();` ### Usage ```javascript const access = await vm.enableSsh(); console.log(access.command); // ready-to-run ssh command // ... await access.close(); ``` ``` -------------------------------- ### List Gondolin VM Sessions Source: https://earendil-works.github.io/gondolin/cli Lists all registered VM sessions. By default, only live sessions are shown. Use `--all` to include non-live entries. ```bash gondolin list ``` ```bash gondolin list [--all] ``` -------------------------------- ### Executing Commands with vm.exec() Source: https://earendil-works.github.io/gondolin/sdk-vm Explains how to execute commands within a VM using the `vm.exec()` method. It covers both string-based commands (run via a login shell) and array-based commands (direct executable execution), along with how to interpret the `ExecResult`. ```APIDOC ## vm.exec() ### Description Executes a command within the VM. This is the primary method for interacting with the guest OS. It returns an `ExecProcess` handle that is both Promise-like and Stream-like. ### Method `vm.exec(command, options?) `vm.exec(argv, options?) ` ### Parameters - `command` (string | string[]) - The command to execute. If a string, it's run via `/bin/sh -lc`. If an array, it's an executable path and its arguments (must be absolute path, does not search `$PATH`). - `options` (object) - Optional configuration for command execution. - `stdout` (string) - How to handle stdout. Can be `'pipe'` for streaming, or omitted/other values for buffering. - `stderr` (string) - How to handle stderr. Can be `'pipe'` for streaming, or omitted/other values for buffering. - `encoding` (string) - The encoding to use for decoding stdout/stderr. Defaults to `'utf-8'`. - `windowBytes` (number) - For streaming modes, the size of the credit window in bytes. Defaults to 256 KiB. ### Return Value Returns an `ExecProcess` object which resolves to an `ExecResult` upon completion. ### ExecResult Structure An `ExecResult` object contains: - `exitCode` (number): The exit code of the process. - `signal` (number, optional): The termination signal if the guest reports one. - `ok` (boolean): Shorthand for `exitCode === 0`. - `stdout` (string): Decoded standard output. - `stderr` (string): Decoded standard error. - `stdoutBuffer` (Buffer): Raw stdout as a Buffer. - `stderrBuffer` (Buffer): Raw stderr as a Buffer. - `json()`: Helper to parse stdout as JSON. - `lines()`: Helper to get stdout as an array of lines. ### Usage Examples **Buffered Usage (most common):** ```javascript const result = await vm.exec("echo hello; echo err >&2; exit 7"); console.log("exitCode:", result.exitCode); // 7 console.log("ok:", result.ok); // false console.log("stdout:\n", result.stdout); // "hello\n" console.log("stderr:\n", result.stderr); // "err\n" ``` **String Command (via shell):** ```javascript const result = await vm.exec("echo $HOME | wc -c"); console.log("exitCode:", result.exitCode); console.log("stdout:\n", result.stdout); console.log("stderr:\n", result.stderr); ``` **Array Command (direct executable):** ```javascript // Note: '/bin/echo' must be an absolute path const result = await vm.exec(["/bin/echo", "hello world"]); console.log(result.stdout); ``` ### Streaming Output To stream output, set `stdout: "pipe"` (and optionally `stderr: "pipe"`). The `ExecProcess` object becomes an `AsyncIterable` for stdout. ```javascript const proc = vm.exec("for i in 1 2 3; do echo $i; sleep 1; done", { stdout: "pipe" }); for await (const chunk of proc) { // default async iteration yields stdout chunks as strings process.stdout.write(chunk); } const result = await proc; // Get the final ExecResult after iteration console.log(result.exitCode); ``` **Streaming both stdout and stderr with labels:** ```javascript for await (const { stream, text } of vm.exec("echo out; echo err >&2", { stdout: "pipe", stderr: "pipe" }).output()) { process.stdout.write(`[${stream}] ${text}`); } ``` **Capturing streamed output:** If you need both streaming and to capture the output, you must do so manually from the piped streams. ```javascript const proc = vm.exec(["/bin/echo", "hello"], { stdout: "pipe" }); let stdout = ""; proc.stdout!.on("data", (b) => (stdout += b.toString("utf-8"))); await proc; // Wait for the process to complete console.log(stdout); // Access the captured stdout ``` ### Notes - Non-zero exit codes do not throw errors; you must check `result.exitCode` or `result.ok`. - When using streaming (`stdout: "pipe"`), the output is not buffered into the final `ExecResult` unless captured manually. ``` -------------------------------- ### Expose Host Directory with RealFSProvider Source: https://earendil-works.github.io/gondolin/vfs Use `RealFSProvider` to expose a host directory to the guest. This allows for persistent storage and sharing of source trees. Note that symlinks escaping the exposed directory are blocked for safety. ```javascript import path from "node:path"; import { VM, RealFSProvider } from "@earendil-works/gondolin"; const repoDir = path.resolve("."); const vm = await VM.create({ vfs: { mounts: { "/workspace": new RealFSProvider(repoDir), }, }, }); ``` -------------------------------- ### Handle Request Body in onRequest Hook Source: https://earendil-works.github.io/gondolin/sdk-network Inspect and potentially block requests based on their body content within the `onRequest` hook. This example reads the request body and returns a `Response` to short-circuit if forbidden content is found. ```typescript onRequest: async (request) => { const bodyText = await request.clone().text(); if (bodyText.includes("forbidden")) { return new Response("blocked", { status: 403 }); } } ``` -------------------------------- ### Resume a shell from snapshot (CLI) Source: https://earendil-works.github.io/gondolin/snapshots Resume a VM session into a shell environment from a snapshot ID. You can specify the snapshot ID directly or provide the path to the .qcow2 file. ```bash # Resume a shell from snapshot id (from default checkpoint cache) gondolin bash --resume # Or resume directly from a qcow2 path gondolin bash --resume /path/to/snapshot.qcow2 ``` -------------------------------- ### Capture Streamed Output Manually Source: https://earendil-works.github.io/gondolin/sdk-vm When streaming output using `stdout: "pipe"`, you can manually capture the data from the `proc.stdout` stream if you need both streaming and a buffered copy. This example demonstrates appending chunks to a string variable. ```typescript const proc = vm.exec(["/bin/echo", "hello"], { stdout: "pipe" }); let stdout = ""; proc.stdout!.on("data", (b) => (stdout += b.toString("utf-8"))); await proc; console.log(stdout); ``` -------------------------------- ### Generate and Build a Custom Image Source: https://earendil-works.github.io/gondolin/custom-images Generate a default build configuration, edit it to customize packages and settings, and then build the custom image. Finally, use the custom image with the `gondolin bash` command. ```bash # Generate a default configuration gondolin build --init-config > build-config.json # Edit the config to add packages, change settings, etc. # Then build: gondolin build --config build-config.json --output ./my-assets # Use your custom image: GONDOLIN_GUEST_DIR=./my-assets gondolin bash ``` -------------------------------- ### Enable SSH Access to Guest VM Source: https://earendil-works.github.io/gondolin/sdk-network Start an SSH daemon (`sshd`) inside the guest and expose it via a host-local TCP forwarder using `vm.enableSsh()`. This allows the use of SSH tooling like `scp` and `rsync` for interacting with the guest. ```typescript const access = await vm.enableSsh(); console.log(access.command); // ready-to-run ssh command // ... await access.close(); ``` -------------------------------- ### Run Gondolin with krun Backend Source: https://earendil-works.github.io/gondolin/cli Executes a Gondolin session using the krun virtual machine manager. ```bash gondolin bash --vmm krun ``` -------------------------------- ### Map Guest Hostname to Local Database Source: https://earendil-works.github.io/gondolin/cli Maps a guest internal hostname to a local development database for seamless integration. ```bash gondolin bash --tcp-map pg.internal=127.0.0.1:5432 ``` -------------------------------- ### Gondolin System Diagram Source: https://earendil-works.github.io/gondolin/architecture Illustrates the interaction between the host machine and the guest Linux VM, highlighting the virtio interfaces and RPC channels. ```text +--------------------------- Host machine (trusted) ---------------------------+ | | | Your Node.js app / CLI | | (policy + secrets + VFS providers) | | | | +------------------------------ VM boundary ------------------------------+ | | | Guest Linux VM (untrusted) | | | | | | | | [virtio-net] eth0 <---- Ethernet frames ----> host network backend | | | | | | | | [virtio-serial] exec RPC <----------------> sandbox server | | | | [virtio-serial] fs RPC <----------------> VFS RPC service | | | | [virtio-serial] ssh fwd <----------------> loopback-only proxy | | | | [virtio-serial] ingress fwd <----------------> ingress gateway | | | +-------------------------------------------------------------------------+ | | | +------------------------------------------------------------------------------+ ``` -------------------------------- ### Attach Process to Terminal Source: https://earendil-works.github.io/gondolin/sdk-vm Attaches a running process's stdin, stdout, and stderr to the host's terminal streams. Requires the process to be started with stdin, pty, and stdout/stderr pipes enabled. Automatically handles raw mode and resize events for TTYs. ```typescript const proc = vm.exec(["/bin/bash", "-i"], { stdin: true, pty: true, stdout: "pipe", stderr: "pipe", }); proc.attach( process.stdin as NodeJS.ReadStream, process.stdout as NodeJS.WriteStream, process.stderr as NodeJS.WriteStream, ); const result = await proc; console.log("exitCode:", result.exitCode); ``` -------------------------------- ### VM Filesystem Operations Source: https://earendil-works.github.io/gondolin/sdk-vm Provides a comprehensive set of file system operations for the VM, including access checks, directory creation, listing contents, getting file stats, renaming, reading (buffered and streamed), writing (buffered and streamed), and deleting files/directories. ```typescript import { constants } from "node:fs"; import { Readable } from "node:stream"; // Check access await vm.fs.access("/etc/os-release", { mode: constants.R_OK }); // Create directories await vm.fs.mkdir("/tmp/workspace/nested", { recursive: true }); // List direct children const entries = await vm.fs.listDir("/tmp/workspace"); console.log(entries); // Stat (same object shape as node:fs Stats / VFS Stats) const st = await vm.fs.stat("/tmp/workspace"); console.log(st.isDirectory(), st.mode, st.size); // Rename / move await vm.fs.rename("/tmp/old-name.txt", "/tmp/new-name.txt"); // Read text const osRelease = await vm.fs.readFile("/etc/os-release", { encoding: "utf-8", }); // Stream-read a large file const stream = await vm.fs.readFileStream("/var/log/messages"); for await (const chunk of stream) { process.stdout.write(chunk); } // Write text (overwrites existing file) await vm.fs.writeFile("/tmp/hello.txt", "hello from host\n"); // Stream-write from a Node readable await vm.fs.writeFile( "/tmp/payload.bin", Readable.from([Buffer.from([0xde, 0xad]), Buffer.from([0xbe, 0xef])]), ); // Delete file await vm.fs.deleteFile("/tmp/hello.txt"); // Delete recursively / ignore missing path await vm.fs.deleteFile("/tmp/some-dir", { recursive: true, force: true }); ``` -------------------------------- ### Configure Ingress Hooks for Request Manipulation Source: https://earendil-works.github.io/gondolin/sdk-network Use `enableIngress` with the `hooks` option to install host-side hook points. Configure `isAllowed`, `onRequest`, and `onResponse` to control access, rewrite requests, and modify responses. Set `bufferResponseBody` to `true` to enable response body buffering for inspection or modification. ```typescript import { IngressRequestBlockedError, VM } from "@earendil-works/gondolin"; const vm = await VM.create(); await vm.enableIngress({ hooks: { isAllowed: ({ clientIp, path }) => { if (path.startsWith("/admin")) { throw new IngressRequestBlockedError( `admin blocked for ${clientIp}`, 403, "Forbidden", "nope\n", ); } return true; }, onRequest: (req) => ({ // Rewrite /api/* -> /* inside the guest backendTarget: req.backendTarget.startsWith("/api/") ? req.backendTarget.slice(4) : req.backendTarget, headers: { "x-added": "1", "x-remove": null }, // Only buffer responses we plan to inspect/modify bufferResponseBody: req.backendTarget.endsWith(".json"), maxBufferedResponseBodyBytes: 8 * 1024 * 1024, }), onResponse: (res) => ({ headers: { "x-ingress": "1" }, body: res.body ? Buffer.from(res.body.toString("utf8").toUpperCase()) : undefined, }), }, }); ``` -------------------------------- ### VM Shell Convenience Wrapper Source: https://earendil-works.github.io/gondolin/sdk-vm Introduces `vm.shell()` as a simplified method for interactive sessions. ```APIDOC ## `vm.shell()` ### Description A convenience wrapper around `vm.exec()` specifically designed for interactive sessions. It automatically enables PTY and stdin, and can optionally attach to the current terminal. ### Method `vm.shell(command, options)` ### Parameters - **command** (string[]) - The command and its arguments to execute. - **options** (object) - Configuration options for the shell session. ``` -------------------------------- ### Configure Gondolin Image Source via Environment Variables Source: https://earendil-works.github.io/gondolin/sdk-storage Set environment variables to control where Gondolin fetches guest images and assets from. GONDOLIN_GUEST_DIR points to local assets, GONDOLIN_DEFAULT_IMAGE specifies the default image, and GONDOLIN_IMAGE_REGISTRY_URL overrides the built-in registry. ```bash # Use explicit local assets export GONDOLIN_GUEST_DIR=/path/to/assets # Change default image selector export GONDOLIN_DEFAULT_IMAGE=alpine-base:1.0 # Override builtin registry URL export GONDOLIN_IMAGE_REGISTRY_URL=https://example.invalid/my-registry.json ``` -------------------------------- ### Implement SSH Exec Policy for Git Repository Access Control Source: https://earendil-works.github.io/gondolin/ssh Utilize the `execPolicy` hook to control SSH `exec` requests. This example restricts Git operations to a specific set of repositories and disables push access. The `getInfoFromSshExecRequest` helper parses the SSH command for Git-specific information. ```typescript import { VM, getInfoFromSshExecRequest } from "@earendil-works/gondolin"; const allowedRepos = new Set(["my-org/repo-a.git", "my-org/repo-b.git"]); const vm = await VM.create({ dns: { mode: "synthetic", syntheticHostMapping: "per-host" }, ssh: { allowedHosts: ["github.com"], agent: process.env.SSH_AUTH_SOCK, execPolicy: (req) => { const git = getInfoFromSshExecRequest(req); if (!git) return { allow: false, message: "non-git ssh denied" }; if (!allowedRepos.has(git.repo)) return { allow: false, message: "repo not allowed" }; if (git.service === "git-receive-pack") return { allow: false, message: "push disabled" }; return { allow: true }; }, }, }); ``` -------------------------------- ### Use Custom Assets Programmatically with VM API Source: https://earendil-works.github.io/gondolin/custom-images Programmatically create a VM instance using the `@earendil-works/gondolin` library, specifying the custom asset directory via `imagePath` in the sandbox configuration. This allows for automated execution and verification of commands within the custom environment. ```javascript import { VM } from "@earendil-works/gondolin"; const vm = await VM.create({ sandbox: { imagePath: "./my-assets", }, }); const result = await vm.exec("rustc --version"); console.log("exitCode:", result.exitCode); console.log("stdout:\n", result.stdout); console.log("stderr:\n", result.stderr); await vm.close(); ``` -------------------------------- ### Mount Host Directory into Guest VM Source: https://earendil-works.github.io/gondolin/cli Mount a directory from the host machine into the guest VM at a specified path. The host path must exist and be a directory. ```bash # Mount a project directory into /workspace gondolin bash --mount-hostfs "$PWD:/workspace" ``` -------------------------------- ### Implement VFS Hooks for Auditing Source: https://earendil-works.github.io/gondolin/vfs Set up 'before' and 'after' hooks on the VFS to log operations and paths accessed by the guest. This is useful for auditing, metrics, and debugging. ```typescript import { VM, MemoryProvider } from "@earendil-works/gondolin"; const vm = await VM.create({ vfs: { mounts: { "/": new MemoryProvider() }, hooks: { before: (ctx) => console.log("vfs before", ctx.op, ctx.path), after: (ctx) => console.log("vfs after", ctx.op, ctx.path), }, }, }); ``` -------------------------------- ### gondolin list Source: https://earendil-works.github.io/gondolin/cli List registered VM sessions. By default, only live sessions are shown. The `--all` flag includes non-live entries. This command also performs a garbage collection pass. ```APIDOC ## `gondolin list` ### Description List registered VM sessions. By default, only live sessions are shown. `--all` includes non-live entries that still remain after cleanup. `gondolin list` runs a best-effort garbage collection pass first, removing entries whose owning PID is gone or whose socket is no longer connectable. ### Usage ``` gondolin list [--all] ``` ### Output Columns * `ID` - session UUID * `PID` - host process id that owns the VM * `AGE` - time since registration * `ALIVE` - whether the session is reachable * `LABEL` - session label (typically the launching command line) ```