EarendilEarendilGondolinDocumentation
Source

Quick Start

This page walks through a first CLI session and a first SDK program. It assumes Gondolin is installed (see Installation); every command also works with npx @earendil-works/gondolin in place of gondolin.

A First CLI Session

Start an interactive shell in a fresh VM:

gondolin bash

The first start downloads the default image (alpine-base:latest, about 200 MB); later starts boot in under a second. Exit the shell and the VM is gone: changes to the root filesystem are thrown away when the session ends.

Mount a Project Directory

Make the current directory available in the guest at /workspace:

gondolin bash --mount-hostfs "$PWD:/workspace" --cwd /workspace

Writes inside /workspace go straight to your host directory. Add :ro for a read-only mount (--mount-hostfs "$PWD:/workspace:ro"). The guest sees nothing else of your host filesystem.

Allow a Host and Inject a Secret

By default the guest can reach any public HTTP(S) host (internal and private addresses are blocked). Restrict it to an allowlist and hand it a credential it never actually sees:

export GITHUB_TOKEN=ghp_...

gondolin bash \
  --allow-host api.github.com \
  --host-secret GITHUB_TOKEN@api.github.com

Inside the VM:

echo "$GITHUB_TOKEN"     # a random placeholder, not your token
curl -sS -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/user
curl -sS https://example.com/   # blocked by the allowlist (HTTP 403)

The host replaces the placeholder with the real value in outgoing request headers, and only for requests to api.github.com. See Secrets and Network Access.

Run a Command Non-Interactively

gondolin exec boots a VM, runs one command, prints its output and exits with its exit code:

gondolin exec --mount-hostfs "$PWD:/workspace" -- \
  sh -c 'cd /workspace && ls -la'

The command after -- is executed directly, without a shell; use sh -c for pipes, cd or $VARIABLES that should expand inside the guest.

Reattach and Inspect

While a gondolin bash session runs, other terminals can see and join it:

gondolin list                    # running sessions
gondolin attach <session-id>     # another shell in the same VM

See the CLI Reference for every command and option.

A First SDK Program

Install the package in a project (npm install @earendil-works/gondolin) and save this as main.ts (Node.js runs TypeScript files directly since 22.18):

import { VM, RealFSProvider, createHttpHooks } from "@earendil-works/gondolin";

// allow one host and give the guest a placeholder for the token
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,
  vfs: {
    // the current directory, read-write, at /workspace
    mounts: { "/workspace": new RealFSProvider(process.cwd()) },
  },
});

try {
  // an argv array runs the program directly
  const files = await vm.exec(["ls", "-la", "/workspace"]);
  console.log(files.stdout);

  // a string runs through `/bin/sh -lc`
  const user = await vm.exec(`
    curl -sS -f \
      -H "Accept: application/vnd.github+json" \
      -H "Authorization: Bearer $GITHUB_TOKEN" \
      https://api.github.com/user
  `);
  console.log("exit code:", user.exitCode);
  console.log(user.stdout);
} finally {
  await vm.close();
}
GITHUB_TOKEN=ghp_... node main.ts

VM.create() resolves the image (downloading it on first use) and returns; the VM boots on the first exec() (or an explicit vm.start()). exec() resolves with exitCode, stdout and stderr (and ok for exit code 0) and does not throw on a non-zero exit. Always close() the VM: it stops the guest and releases its resources.

Where Next