Files
kstyagi 3e17e3e72f feat(tools): Enhance guest tools installation and management
- Added new API endpoints for tool catalog and installation management.
- Updated usage documentation to include new tool installation features.
- Implemented asynchronous tool installation with job tracking.
- Introduced integration tests for tool installation in disposable VMs.
- Enhanced dashboard UI to reflect tool installation status and usage instructions.
- Improved error handling and logging for tool installation processes.
- Added support for multiple Python versions and Conda environments.
- Refactored tool management logic to ensure better state handling and job persistence.
2026-09-25 23:52:21 +05:30

39 KiB
Raw Permalink Blame History

Nest

Nest is a local infrastructure workspace with a browser dashboard for your machines, network, and host health. Launch it with nest --serve.

Its uvm component is a small, local Firecracker microVM manager for Ubuntu. UVM creates one Firecracker process per VM, attaches it to a host TAP device, and keeps a private disk and local registry for named VMs. The CLI supports create, start, stop, restart, resize, delete, inspection, SSH, logs, diagnostics, host-shared IPv4 internet access, and enforced private-IPv4 TCP port allowlists.

This directory is a self-contained project. Nest owns the dashboard and workspace server; UVM provides the machine-management CLI and backend services. All code, tests, packaging metadata, launchers, and documentation live here.

For architecture, lifecycle invariants, extension guidance, and contributor workflows, see DEVELOPER.md.

For end-to-end operator scenarios, CLI examples, and HTTP API calls, see USAGE.md.

For the original firewall proposal and backend evaluation, see FIREWALL.md. That proposal is design background, not the current feature contract; these guides describe the implementation in the workspace.

Quick Start

Run commands from this project root:

./nest.py --help
python3 -B -m nest --help
./uvm.py --help
python3 -B -m uvm --help

The pyproject.toml file defines installable nest and uvm console commands. The project is an application, not a reusable Python SDK.

Requirements

  • Ubuntu or another compatible Linux host
  • Python 3.12 or newer
  • Root access for UVM commands that read or mutate the protected VM registry
  • KVM enabled and accessible at /dev/kvm
  • Linux 5.3 or newer with native bridge conntrack and safe pidfd support
  • An x86_64 or ARM64 (aarch64) host, including NVIDIA DGX Spark
  • Working host networking, iproute2, iptables, nftables, conntrack, coreutils, e2fsprogs, util-linux, openssl, and openssh-client
  • An STP-disabled software bridge; no offload path that bypasses bridge hooks

uvm install installs the required host packages. It validates /dev/kvm before downloading VM artifacts. Boot operations additionally check that the native nftables bridge policy is supported; installing packages alone is not a connectivity or isolation test.

Firecracker and the guest kernel are selected automatically for the host architecture. The default rootfs is built locally from Ubuntu 22.04 (Jammy) packages with OpenSSH, sudo, and a complete apt/dpkg database. On NVIDIA DGX Spark, UVM uses ARM64 assets and packages. arm64 and amd64 architecture names are normalized to aarch64 and x86_64, respectively. Custom UVM_KERNEL_URL and UVM_ROOTFS_URL overrides must point to assets compatible with the host architecture.

The default guest kernel is Linux 6.1.155 from Firecracker's published v1.15 CI artifacts, with built-in virtio-rng support. UVM attaches a host-backed entropy device before each guest boot so services such as SSH can initialize their cryptographic random generator. Custom kernels must include CONFIG_HW_RANDOM=y and CONFIG_HW_RANDOM_VIRTIO=y for this device.

Older installs used a Linux 4.14 ARM demo kernel and supplied no entropy device. If SSH stays at Starting OpenBSD Secure Shell server while ping works, see SSH startup and guest entropy for the kernel-refresh and retained-VM recovery steps.

Trusted Installation

Installation requires trusted SHA-256 digests by default. Supply digests from an independently trusted source for the selected architecture before running the privileged installer:

sudo env \
  UVM_FIRECRACKER_SHA256='<sha256-of-firecracker-archive>' \
  UVM_KERNEL_SHA256='<sha256-of-vmlinux>' \
  ./uvm.py install

The Ubuntu build uses debootstrap, authenticated Ubuntu archive signatures, and native packages. It bootstraps the base release first, then uses apt to apply updates/security fixes and install the guest packages. UVM installs the builder prerequisites and records the built ext4 image's SHA-256 in the integrity manifest. Building requires root with mount/PID namespace support and internet access to Ubuntu mirrors.

Use --force to refresh downloads and rebuild the Ubuntu template:

sudo env \
  UVM_FIRECRACKER_SHA256='<sha256-of-firecracker-archive>' \
  UVM_KERNEL_SHA256='<sha256-of-vmlinux>' \
  ./uvm.py install --force

To use a prebuilt ext4 image instead, set both UVM_ROOTFS_URL and its trusted UVM_ROOTFS_SHA256. Leave both unset for the default Ubuntu build.

Upgrade from the Stripped Bionic Demo Image

Run install --force with the updated code and with UVM_ROOTFS_URL and UVM_ROOTFS_SHA256 unset, then create a new VM. The replacement template includes sudo and the package database that the old demo image lacked. Existing VM disks retain their original OS and credentials; installation updates the template used for future VMs. Restart a running API server to load the updated provisioner before creating VMs through the dashboard.

If the build fails, its detailed bootstrap/package output is retained in /var/lib/uvm/images/ubuntu-build.log (mode 0600). Correct the build failure and rerun installation before creating a custom-user VM; a failed build leaves the previous template installed.

For local experimentation only, the old unverified quick-start behavior is available through an explicit opt-out:

sudo env UVM_ALLOW_UNVERIFIED_DOWNLOADS=1 ./uvm.py install

An unverified manifest is not accepted as publisher trust in a later strict run. Reinstall with trusted digests to establish a verified local manifest. To boot VMs from an unverified local-development install, retain UVM_ALLOW_UNVERIFIED_DOWNLOADS=1 on later create, start, restart, and resize commands and on the API server process.

Commands

These are separate operation examples, not a script to run from top to bottom.

# Install after supplying the trusted checksum variables shown above.
# A bare `install` command fails under the default strict integrity policy.

# Create a named VM: 1 CPU, 512 MiB RAM, 30 GiB storage, root/root, and private TCP/22.
# Use a unique non-default password for anything beyond a throwaway VM.
sudo ./uvm.py create --name web

# Choose resources, a private guest IP, and additional service ports.
sudo ./uvm.py create --name app --cpu 1 --ram 1G --storage 50G \
  --guest-ip 10.42.0.10 --ports 80:HTTP,443:HTTPS

# Read a password from one line of stdin, rather than a --password argument.
# USAGE.md includes a non-echoing Bash prompt example.
sudo ./uvm.py create --name worker --password-stdin

# Explicitly allow no inbound service ports, including SSH.
sudo ./uvm.py create --name closed --no-default-ports

# Inspect persisted state and observed process/policy status.
sudo ./uvm.py list
sudo ./uvm.py list --status running --json
sudo ./uvm.py list -q
sudo ./uvm.py inspect web --json
sudo ./uvm.py logs web --tail 50 --follow
sudo ./uvm.py doctor --json

# Resolve VMs by exact ID, name, or guest IP.
sudo ./uvm.py ssh web
sudo ./uvm.py ssh app --port 22 -- uname -a
ssh root@10.42.0.10

# Stop and boot the same private disk, or restart selected VMs.
sudo ./uvm.py stop web --timeout 5
sudo ./uvm.py start web
sudo ./uvm.py restart web app --timeout 10

# Stop, change resources, and boot the same VM with its existing identity/data.
sudo ./uvm.py resize app --cpu 2 --ram 4G --storage 80G
sudo ./uvm.py resize app --ram 2G

# Preview bulk operations before confirming them.
sudo ./uvm.py stop --all --dry-run --json
sudo ./uvm.py stop --all -y
sudo ./uvm.py start --all -y
sudo ./uvm.py restart --all -y
sudo ./uvm.py delete --all --dry-run
sudo ./uvm.py delete web -y
# destroy is an alias for delete, with the same flags and destructive effect.

start, stop, restart, and delete accept one or more VM identifiers or --all, plus --json, --dry-run, and --yes/-y. Deletion and mutating --all selections require confirmation; non-interactive callers must use --yes. --timeout is available on stop/restart/delete (and destroy), not start. It is a finite non-negative SIGTERM wait in seconds, default 5, not a total command deadline or a guest shutdown timeout.

Storage and Resizing

New VMs default to a 30 GiB private disk, which is also the minimum. --storage accepts values such as 30G, 50G, or 1T; bare numbers use GiB. As with RAM, G/GB/GiB are binary units. Capacities round up to whole MiB and are returned as storage_mib. Disks are sparse: the configured capacity does not immediately reserve that much physical host space.

resize VM accepts any combination of --cpu, --ram, and --storage, plus --timeout and --json. It stops the VM, resizes the ext4 disk when needed, updates the resource settings, and boots it. A previously stopped VM also boots at the end. VM ID, IP/MAC, username/password, port rules, guest files, and SSH host keys are retained. The dashboard exposes the same operation under Resize resources; the API uses POST /vms/{identifier}/resize.

Storage can grow or shrink, provided ext4 can fit its existing data into the requested capacity. Disk changes are prepared on a temporary sparse copy and published after verification. Failed resizes retain a pending_resize target for retry; start on the stopped VM completes that target before booting. Older VMs report storage_mib: null until their first resize, which retains their current capacity or grows it to the 30 GiB minimum when storage is omitted.

Stop sends SIGTERM and, if needed, SIGKILL to the Firecracker VMM. It is not a guest-coordinated shutdown. Start boots the retained disk without resetting guest data, SSH host keys, or credentials; it does not restore guest memory. Delete permanently removes the disk and releases the VM's name and IP after cleanup. See USAGE.md for the complete flag and recovery reference.

Names are unique local identifiers, not DNS names. --host-ip remains an alias for --guest-ip; both select the guest's private IPv4 address. JSON output omits passwords, with warnings and host-command traces on stderr.

SSH uses StrictHostKeyChecking=accept-new and a VM-ID-specific host-key alias by default. --user, --key, and --port customize the SSH client; append a remote command after a literal --. --insecure-host-key disables verification for one connection. SSH does not automatically supply the stored password or open a closed port.

Deletion removes matching local SSH known_hosts entries for the guest IP and UVM's VM-ID host-key alias before releasing the IP for reuse. It checks the effective user's files and, under sudo, the invoking user's files, including hashed entries. Unrelated hosts and file ownership/permissions are preserved. For API services or custom SSH cache locations, set UVM_KNOWN_HOSTS_FILES to a colon-separated list of absolute paths on the UVM host. See the SSH section in USAGE.md for configuration and one-time cleanup of entries left by older versions.

Private Ports

sudo ./uvm.py ports list web --json
sudo ./uvm.py ports list --all
sudo ./uvm.py ports open web --ports 80:HTTP --ports 443:HTTPS,8080:TCP
sudo ./uvm.py ports close web --ports 80
sudo ./uvm.py ports set web --ports 22,443:HTTPS --dry-run --json
sudo ./uvm.py ports set web --ports 22,443:HTTPS -y
sudo ./uvm.py ports close web --all -y
sudo ./uvm.py ports set web --none -y
sudo ./uvm.py ports sync web
sudo ./uvm.py ports sync --all --dry-run
sudo ./uvm.py ports sync --all -y

Rules use PORT[:TYPE], for example 22, 80:HTTP, 443:HTTPS, or 8080:TCP, repeated or comma-separated. HTTP and HTTPS are TCP labels, not proxies or TLS configuration. Create adds 22:TCP even when other ports are supplied, unless --no-default-ports is set. Later port edits do not automatically add SSH back. ports close VM --all closes ports on that one VM; only ports list --all and ports sync --all select all VMs.

Port mutations support --dry-run and --json; close/set/sync also support --yes/-y. Set, close-all, and removal of TCP/22 require confirmation (an already empty managed policy can be a no-op). Closing a service blocks subsequent packets on existing service connections in both directions, as well as new connections. This is packet dropping, not a guaranteed TCP RST or immediate application disconnect.

These rules govern access to the guest private IP. They do not start a guest service or provide public inbound connectivity, host-port mappings, DNS publication, reverse proxies, or guest-service TLS. UDP and IPv6 service policies are not yet supported. Guest-initiated IPv4 TCP/UDP egress, its replies, and required ARP/ICMP traffic remain allowed; managed TAPs block IPv6.

Nest Dashboard and Management API

Install the project dependencies before serving the API:

python3 -m pip install .

Launch the Nest workspace server using the environment where the project dependencies were installed (use the full console-script path if needed):

export UVM_API_TOKEN='replace-with-a-long-random-secret'
sudo env UVM_API_TOKEN="$UVM_API_TOKEN" \
  nest --serve --host 127.0.0.1 --port 8000

# Equivalent when running the source checkout directly.
sudo env UVM_API_TOKEN="$UVM_API_TOKEN" \
  ./nest.py --serve --host 127.0.0.1 --port 8000

The default bind address is 127.0.0.1 and the default port is 8000. OpenAPI documentation is available at /docs once the server is running. The root URL redirects to the Nest dashboard at /dashboard; enter the API token there for the current browser session.

Nest serves the dashboard and UVM-backed management endpoints together, using the existing UVM_API_TOKEN and UVM_* host configuration. The standalone UVM API is available through uvm --serve, with its reference at /docs; the / and /dashboard routes belong to the Nest server.

The Nest dashboard includes a fleet overview, searchable machine collection, private network and port-policy view, host diagnostics, and machine provisioning. Open a machine to manage its lifecycle, install developer tools, launch terminal sessions, edit or sync its port rules, copy its SSH command, or read recent logs. Select machines in the collection for bulk operations. Use Ctrl+K / ⌘K (or /) to find a machine or page. Fleet data refreshes every five seconds while the tab is visible; host checks refresh every thirty seconds and can also be run manually. Charts show observed machine states and allocated resources, rather than guest CPU or memory utilization. All frontend assets, including the font, are served locally; no frontend build step is required.

The server exposes these routes:

Method Route Purpose
GET / Redirect to the dashboard (Nest server)
GET /dashboard Serve the Nest workspace dashboard (Nest server)
GET /health Unauthenticated liveness response
POST /exit Gracefully stop the management server
GET /diagnostics Run host readiness checks for the dashboard/system view
POST /install Install or refresh host assets; body: { "force": false }
GET /vms List VM records and observed status
POST /vms Create; supports cpu, ram, storage, guest_ip, username, password, name, ports, no_default_ports, tools
POST /vms/actions Run a fixed snapshot of selected or all VM lifecycle actions
GET /vms/{identifier} Inspect by exact ID, name, or guest IP
POST /vms/{identifier}/start Boot a retained private disk
POST /vms/{identifier}/restart Stop then boot a VM; optional timeout query parameter
POST /vms/{identifier}/resize Stop, resize CPU/RAM/storage, and boot; optional timeout query parameter
POST /vms/{identifier}/stop Stop a VM while retaining its private disk; optional timeout query parameter
DELETE /vms/{identifier} Delete a VM and release its resources; optional timeout query parameter
POST /vms/{vm_id}/ports Set, open, or close private port rules for an exact VM ID
POST /vms/{vm_id}/ports/sync Reapply a managed private port policy for an exact VM ID
POST /vms/{vm_id}/exec Execute a command/script inside a running VM over vsock, with optional root privileges
POST /vms/{vm_id}/agent/install Install/upgrade the execution agent on a stopped VM's private disk
GET /tools Guest tool catalog and supported Python versions
GET /vms/{vm_id}/tools Tool inventory and installation history; refresh=true probes the running guest
POST /vms/{vm_id}/tools/install Queue selected guest tools; HTTP 202 with a job ID
GET /vms/{vm_id}/tools/jobs/{job_id} Per-tool progress, results, and bounded installation output
GET /vms/{identifier}/logs Read a bounded, non-following tail of a VM log
GET /vms/{vm_id}/sessions List this VM's saved sessions (Nest)
POST /vms/{vm_id}/sessions Launch a guest terminal; body: {} or {"rows":24,"cols":80} (Nest)
GET /vms/{vm_id}/sessions/{session_id} Session metadata and status (Nest)
PATCH /vms/{vm_id}/sessions/{session_id} Rename a session; body: {"name":"Deployment"} (Nest)
DELETE /vms/{vm_id}/sessions/{session_id} Close the session and delete its saved history; returns 204 (Nest)
POST /vms/{vm_id}/sessions/{session_id}/close End the shell and retain its history (Nest)
WS /vms/{vm_id}/sessions/{session_id}/terminal Authenticated terminal input, output, resize, and replay (Nest)

API port rules are objects such as {"port": 80, "protocol": "HTTP"}; no_default_ports is a boolean, default false. Create also accepts storage, defaulting to "30G"; resize accepts optional cpu, ram, and storage fields. Port changes use {"mode":"set"|"open"|"close", "ports":[...]}. A legacy VM must be stopped and use set to establish a managed policy. Responses include name, ports, policy_status, and operation, but never passwords. See USAGE.md for request examples and strict schema details. Log reads use an optional tail query parameter from 0 to 1000, default 100, and are capped at the final 256 KiB of the log.

Bulk requests use an action (start, stop, restart, delete, or ports-sync) plus either identifiers or all_vms: true; they return a result for each fixed target and do not prompt for confirmation. Interactive SSH remains CLI-only. VM lifecycle changes are explicit; accepted tool installation jobs are processed in the background.

One-click Guest Tools

Choose tools under New machine → Pick your tools to install them automatically after boot. For an existing VM, open Machines → select a VM → Tools and click Install, or select several tools and click Install selected. A stopped VM offers Start & install. Installation works over vsock with SSH disabled.

The initial catalog targets Ubuntu 22.04 guests on x86_64 and ARM64:

Tool Default
Git, wget, curl, htop Ubuntu packages
Node.js 24 LTS (24.21.0), including npm and npx
Python 3.11, 3.12, and 3.13 with pip and venv; versions can coexist
JDK OpenJDK 21 LTS, including java and javac
PHP 8.5 CLI from the signed Ondřej Surý PHP PPA
Conda Miniforge 26.5.3-0, using conda-forge
micromamba 2.9.0

Node, Python, Miniforge, and micromamba downloads use pinned release checksums. Python uses Astral's standalone distributions, exposed as python3.11, python3.12, and python3.13. The guest's OS Python continues serving the agent. Downloaded tools live under /opt/uvm-tools, with executable links in /usr/local/bin. Conda environments and caches are writable by the configured guest user. Get started shows commands, including shell initialization for Conda/micromamba activation in Nest's non-login terminals.

Jobs run independently of the browser and show progress and captured output per tool. Already available tools are verified and skipped. A failed tool can be retried on the created VM. Guest internet access is required for downloads. Restart the management server to load the feature; older running VMs may need Restart & install to apply the current guest-agent setup.

The most recent ten jobs and cached inventory are stored in the VM's private tools.json file. Queued requests resume when the server starts. An interrupted in-flight installation has an uncertain outcome and may finish inside the guest; use Check installed tools before retrying. See API examples and recovery for details.

MicroVM Terminal Sessions

Open Machines → select a microVM → Sessions, then click Launch session. Each session opens an interactive guest shell in the dashboard, with terminal colors, keyboard input, Ctrl+C, resizing, and persistent shell state such as the working directory. Sessions run as the VM's configured guest user; normal guest sudo commands can request that user's password in the terminal.

The history list belongs to the selected VM. Reopen an active session to continue using the same shell, or select an ended session to view its saved output. Navigation, browser reloads, and signing out leave the shell running. End session closes the shell and its processes. Stopping/restarting the VM or the Nest server ends live sessions; their saved history remains available.

Use Rename in a history row or the terminal toolbar to give any session a custom name. Names are trimmed and accept 1–80 printable characters; they persist across reloads and server restarts. The session keeps its identity and shell. Delete removes a session and its saved terminal output after confirmation, closing its shell first if it is active. Both actions are scoped to the session's microVM.

Nest stores session metadata and the latest 8 MiB of output per session in $UVM_BASE/nest/sessions.sqlite3 (default /var/lib/uvm/nest/sessions.sqlite3). The viewer indicates truncated output. Raw keyboard input is not logged separately; the transcript contains what the guest terminal outputs. History files are private to the host account running Nest. One Nest server owns sessions for a given VM registry at a time, with up to 32 active sessions; the guest agent shares its eight connection slots between terminals and exec calls.

Vsock and the guest agent are configured by default for every microVM. Creation installs and enables the agent on every compatible image, including cached older templates and custom images. Each stopped-disk boot also installs or updates it automatically; an already-current installation is a read-only check. Existing running VMs receive the setup on their next restart. The Sessions → Guest terminal support → Repair / update terminal support control is available for manual repair on a stopped VM. The guest-agent/kernel requirements described below apply to terminals.

Terminal support adds the Python websockets dependency. In your micromamba environment, install the current project and launch with its interpreter:

micromamba activate nest
python -m pip install -e '.[test]'
sudo env UVM_API_TOKEN="$UVM_API_TOKEN" \
  "$CONDA_PREFIX/bin/python" -m nest --serve

Execute Commands Without SSH

Use an exact VM ID from create/list:

curl -X POST \
  -H "X-UVM-Token: $UVM_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"command":"whoami","sudo":true,"timeout_seconds":60}' \
  "http://127.0.0.1:8000/vms/<vm-id>/exec"

The response contains exit_code, stdout, stderr, timed_out, and truncated. Commands and multiline scripts run through /bin/sh inside the guest. sudo: false (the default) uses the VM's configured username; true uses guest root without a password prompt. A VM configured with username root still runs as root when sudo is false.

Execution uses a guest systemd agent over Firecracker vsock. It works with SSH disabled and with an empty TCP allowlist. Every Firecracker boot attaches the vsock device, and creation/start/restart/resize ensure the guest service is installed and enabled before boot. Restart the management server to load this behavior, then restart older VMs once to apply it automatically.

Installation stages and verifies a private disk copy when changes are needed, preserving guest data and credentials. If Python is missing, the installed bootstrap service acquires it inside the guest on boot; this one-time step needs guest internet access. The default Ubuntu template already includes Python. The guest needs Python 3.10+, systemd with cgroup v2, and a vsock-capable Linux 5.14+ kernel; UVM's default Linux 6.1 kernel provides the required drivers.

See execution examples and limits for programmatic calls, local script submission, timeout behavior, and upgrade steps.

API Security

The server controls privileged host operations. UVM_API_TOKEN is required for every API server, including loopback-only deployments. All management routes require it in the X-UVM-Token header. The dashboard shell, liveness route, and FastAPI's informational /docs, /redoc, and /openapi.json endpoints do not require a token; the dashboard stores the entered token only for its current browser session and applies it to management requests.

A non-loopback bind additionally requires TLS certificate and key paths:

export UVM_API_TOKEN='replace-with-a-long-random-secret'
export UVM_API_TLS_CERT='/etc/uvm/api-cert.pem'
export UVM_API_TLS_KEY='/etc/uvm/api-key.pem'
sudo env \
  UVM_API_TOKEN="$UVM_API_TOKEN" \
  UVM_API_TLS_CERT="$UVM_API_TLS_CERT" \
  UVM_API_TLS_KEY="$UVM_API_TLS_KEY" \
  ./nest.py --serve --host 0.0.0.0 --port 8000

Send the token on management requests:

curl \
  -H "X-UVM-Token: $UVM_API_TOKEN" \
  http://127.0.0.1:8000/vms

Stop the API server without stopping its managed VMs:

curl -X POST \
  -H "X-UVM-Token: $UVM_API_TOKEN" \
  http://127.0.0.1:8000/exit

The endpoint returns 202 {"status":"stopping"} and then gracefully terminates the serving Nest or UVM process. It is a protected management endpoint; it does not stop or delete managed VMs.

Do not expose this server directly to an untrusted network. For remote access, keep TLS enabled and consider placing it behind an additional authenticated reverse proxy.

Guest Image Contract

The installed kernel and rootfs are templates. create copies the rootfs to the VM runtime directory before Firecracker opens it read-write, so VMs do not share a mutable root disk.

During creation, UVM updates the private ext4 disk with a password for the requested guest account and enables OpenSSH password authentication. Missing custom usernames are created with an unused regular-user UID, a primary group, a private /home/USERNAME directory, and /bin/sh as their login shell. The defaults are username root and password root. A custom image must provide:

  • sshd
  • /etc/passwd, /etc/shadow, and /etc/ssh/sshd_config
  • /etc/group and /bin/sh when creating a new user
  • A root-owned, setuid /usr/bin/sudo and /etc/sudoers for custom login users
  • systemd and apt for the automatically installed vsock guest agent (Ubuntu 22.04+)

Custom login users receive password-authenticated sudo access. Inside a new VM:

sudo -i                 # Enter the same password used for this user's SSH login.
sudo apt update
sudo apt install htop

su - asks for root's separate password; creating a custom login does not set root's password. Use sudo -i for an administrative shell. The default Ubuntu template retains apt's package metadata and enables main and universe, including updates/security repositories. Guest DNS defaults to 1.1.1.1 and 8.8.8.8 in /etc/resolv.conf.

UVM specializes the Ubuntu guest's /etc/systemd/network/10-uvm.network with its assigned Address= and Gateway= before boot. KeepConfiguration=yes also retains the kernel-created default route while systemd-networkd starts. Older templates used KeepConfiguration=static, which could retain the guest IP but remove the default route, producing ping: connect: Network is unreachable.

The updated CLI repairs an older VM's original, unedited UVM profile on its next stopped-disk boot, including restart. The repair is prepared and checked on a sparse copy before publication; guest-edited profiles and custom images are preserved. New VMs also receive the fix when using a cached older Ubuntu template. See guest route recovery for immediate recovery inside a running guest.

Existing accounts retain their UID, groups, home directory, and shell. A new user reuses a same-named group if present; otherwise UVM creates one with an unused GID. /etc/gshadow is updated when present. The default root account must already exist in the image.

The known public demo key bundled in Firecracker's older Bionic image is removed from each private disk. UVM also replaces the conventional RSA, ECDSA, and Ed25519 SSH host keys in every private disk so cloned VMs have distinct host identities. Never rely on SSH private keys baked into a shared template.

Host Networking

By default, UVM manages:

Network:  10.42.0.0/24
Gateway:  10.42.0.1
Bridge:   uvm0
TAPs:     uvm-<unique suffix>

VMs share the host's IPv4 internet connection by default. When the host has internet access, guests can initiate outbound connections through it. When the host loses connectivity, guest internet access is lost too; the private VM network remains usable. Reconnecting the host restores guest egress.

UVM creates the bridge when absent, validates an existing bridge before using it, enables IPv4 forwarding, and installs subnet-scoped iptables masquerade and forward/return rules. These rules are installed even while the host is offline and follow its current routing table, so switching uplinks does not require restarting VMs. Existing connections may need application retries after an outage or upstream address change. Guest IPs, gateway, bridge name, and guest boot netmask derive from the configured CIDR.

Forwarding exceptions are inserted ahead of existing host-chain rules and allow guest-originated traffic and its replies. Masquerading excludes traffic back onto the UVM bridge, preserving private peer addresses.

To apply updated sharing rules to an existing running VM, use:

sudo ./uvm.py start web

On a running VM, start refreshes its private policy and shared host networking without rebooting it. New VM boots set up the same networking automatically. For an older guest missing its default route, use restart once to apply the offline guest-profile repair, or follow the guest route recovery steps above.

Private VM creation and start require an RFC 1918 subnet (10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16). Retained VMs prevent incompatible gateway, subnet, or bridge changes within the same registry. Private port enforcement uses separate per-VM native nftables bridge tables, not DNAT or host listeners. Policy is installed and read back before an owned TAP is activated and before the guest starts. The software bridge must have STP disabled; each TAP's bridge-slave group_fwd_mask is set to 0x4000 so LLDP cannot bypass the early identity filter. Native bridge conntrack requires Linux >= 5.3; no br_netfilter sysctl setup is required by this policy layer.

Host-to-VM and VM-to-VM traffic crossing these hooks is filtered. An allowed port is not a reachability guarantee, and a private IP alone is not protection against administrator-supplied external routing or NAT. Integration with UFW, firewalld, Docker, or other external firewall managers remains deferred. Do not offload traffic around the software bridge hooks.

There is no autostart service or daemon reconciliation. After externally lost or changed private policy, inspect its status and explicitly run ports sync. Use start to restore shared bridge/NAT/forwarding rules as well. Read-only commands do not repair rules; port sync does not boot stopped VMs.

Use one authoritative registry per bridge/subnet. Independent UVM_BASE directories do not coordinate IP or MAC allocation with each other.

Persistent State

The default data directory is /var/lib/uvm:

/var/lib/uvm/
├── bin/                 # Firecracker and Jailer binaries
├── images/              # Kernel and rootfs templates
├── vms/<vm-id>/         # Per-VM disk, config, log, and API socket
├── integrity.json       # Download/build checksums and verification provenance
└── state.json           # Root-only VM inventory and guest credentials

The default registry is /var/lib/uvm/state.json, schema version 3. Writes are locked and atomic. Lifecycle, port-policy, and installation mutations share an operation lock; bulk operations use a fixed snapshot of VM IDs.

The registry stores guest usernames and passwords in plaintext, so state.json is mode 0600. Treat it as a secret, do not include it in bug reports, and replace the default root password immediately.

Credential provisioning applies only to VMs created by this version. Existing guest accounts, passwords, and SSH host keys are retained on subsequent boots.

Older registries are read and migrated on the next write. A missing/null ports policy remains None/unmanaged, not an empty allowlist or an implicitly enforced default. Migrate a legacy VM explicitly with stop, ports set VM --ports 22,80:HTTP -y (choose the actual required ports), then start. Resume requires a complete, safe private disk, a verified kernel and VMM, and compatible network settings. Removing the original rootfs template does not block start of a retained private disk.

Process identity includes PID, Linux process start time, and host boot ID. A running legacy PID missing the boot ID is adopted only when its saved start time, running binary, exact API-socket command line, and protected paths all match. Missing or ambiguous evidence fails closed: UVM will not guess which process to signal or release a disk while process exit is unconfirmed.

Schema 3 adds disk capacity and durable resize targets. Older registries migrate on the next write; a recorded pending resize is completed before a stopped VM can boot. A boot failure after a successful resize retains the new allocation for a later start.

Configuration

Host and API settings can be overridden through environment variables:

Variable Purpose
UVM_BASE Data directory; defaults to /var/lib/uvm
UVM_NETWORK Guest private IPv4 CIDR; defaults to 10.42.0.0/24
UVM_GATEWAY Bridge/guest gateway; defaults to 10.42.0.1
UVM_BRIDGE Linux bridge name; defaults to uvm0
UVM_KNOWN_HOSTS_FILES Override local SSH cache files cleaned on deletion; colon-separated absolute paths
UVM_KERNEL_URL Override the host-architecture default guest kernel URL
UVM_ROOTFS_URL Optional prebuilt ext4 image URL; unset to build Ubuntu 22.04 locally
UVM_FIRECRACKER_SHA256 Trusted Firecracker release archive SHA-256
UVM_KERNEL_SHA256 Trusted guest kernel SHA-256
UVM_ROOTFS_SHA256 Trusted prebuilt rootfs SHA-256; use with UVM_ROOTFS_URL
UVM_FIRECRACKER_BINARY_SHA256 Optional trusted local Firecracker binary SHA-256
UVM_ALLOW_UNVERIFIED_DOWNLOADS Set to 1 only for unverified local development
UVM_API_TOKEN Required for Nest and standalone UVM servers; sent as X-UVM-Token
UVM_API_TLS_CERT TLS certificate path required for a non-loopback API bind
UVM_API_TLS_KEY TLS private-key path required for a non-loopback API bind

Project Layout

Selected files:

./
├── README.md
├── USAGE.md
├── DEVELOPER.md
├── pyproject.toml
├── nest.py              # Executable Nest workspace launcher
├── nest/                # Nest workspace application
│   ├── cli.py           # nest --serve entry point
│   ├── server.py        # Workspace server, composing the UVM management API
│   └── dashboard/       # Nest document route and local HTML, CSS, JS, and fonts
├── uvm.py               # Executable UVM compatibility launcher
├── uvm/                 # Machine-management implementation
│   ├── cli.py           # argparse, terminal output, SSH execution
│   ├── lifecycle.py     # Lifecycle, bulk operations, recovery, port-policy intent
│   ├── network.py       # IP/MAC/TAP/bridge/NAT management
│   ├── firewall.py      # Owned nftables bridge policies and offline conntrack cleanup
│   ├── routers/         # UVM management API routes
│   └── firecracker/     # Firecracker config, API, and process adapters
└── tests/               # Unit tests and opt-in isolated namespace packet tests

See DEVELOPER.md for the complete module map and ownership rules.

Testing

Run the unit suite from this project root:

python3 -m pip install '.[test]'
python3 -B -m unittest discover -s tests -v

The unit tests do not need root, KVM, Firecracker, or host network changes. They cover validation, state migration and locking, integrity manifests, network/firewall rendering, Firecracker request ordering, process safety, retained-disk resume, bulk operations, lifecycle rollback, dashboard delivery, and API routes. API route tests run when the FastAPI and httpx test dependencies are installed.

When nft and conntrack are installed, the suite also checks their command parsers using deliberately invalid final commands/options that prevent mutations. With e2fsprogs installed, offline disk tests grow and shrink temporary sparse ext4 images and check that data and credential/key fixtures survive; no VM is booted.

Opt-in packet checks run only in disposable isolated user/network namespaces, which may require privileges and the installed network tools:

UVM_FIREWALL_INTEGRATION=1 python3 -B -m unittest discover -s tests/integration -v

The packet test was skipped in this workspace because namespace creation was denied (Operation not permitted). A skip is not packet-path or real-host validation. The harness uses veths in place of TAPs and simulated upstream networks to check NAT, offline startup, reconnects, and uplink changes alongside private port policies. It does not boot a real VM; validate KVM, TAP, SSH, and guest connectivity on an isolated Linux host.

Current Boundaries

  • Fractional CPU requests still round Firecracker vCPUs up and are advisory.
  • No quotas or cgroup CPU, memory, I/O, or process limits are applied.
  • Firecracker is launched directly by this local CLI. Jailer/cgroup hardening is not implemented by the current command set.
  • No VM autostart, background reconciliation, guest application health probing, snapshots, DHCP, public-port publishing, or multi-host scheduling.
  • The API uses one shared token, not users, roles, or tenants. UVM is not a hardened multi-tenant platform.
  • Run real KVM, TAP, and guest-connectivity checks on an isolated Linux host before relying on UVM for workloads.