scripts/nvx.py is the supported command-line entry point for building,
running, benchmarking, and packaging NVX. Run it from the repository root with
Python 3.10 or newer:
python3 scripts/nvx.py COMMAND [OPTIONS]
The examples on this page use the POSIX spelling. On Windows, use
python scripts\nvx.py instead. Every command accepts -h or --help,
including nested commands:
python3 scripts/nvx.py --help
python3 scripts/nvx.py run --help
python3 scripts/nvx.py performance gate --helpnvx.py runs nvx-rs, the Rust
implementation of NVX's runtime steps, for the run and
sandbox commands, which it runs entirely, for the artifact lookup
of the CPU-profile guidance of run, and for the egress policies of the
test-microvm scenarios. It runs, in this order:
- the executable that the
NVX_RSenvironment variable names; nvx-rs/target/release/nvx-rs, ornvx-rs.exeon Windows, in the checkout, wherecargo build --releaseputs it, and wheredownloadinstalls thenvx-rsof a release; or else- that executable, after it builds it from the repository root with
cargo build --release --locked --features cli --manifest-path nvx-rs/Cargo.toml --target-dir nvx-rs/target, the Rust release thatrust-toolchain.tomlpins, and Cargo's output on standard error, so that it never mixes with the output of the command.
Every release package ships bin/nvx-rs, or bin/nvx-rs.exe, which
download installs only when the release was built from the checkout's
nvx-rs sources, so that nvx.py then needs no Cargo. A checkout whose
nvx-rs sources differ from those of its nearest release, as those of the
latest dev often do, needs Cargo to build it. Without NVX_RS, a
built or installed executable, or Cargo, such a command fails and names
these choices; rustup installs Cargo. nvx.py rebuilds
nothing on its own once the executable exists, so run the build command again,
or delete the executable, after changing nvx-rs. The managed-lifecycle
scenario of test-microvm also needs Cargo, which runs its
nvx-rs test.
The provenance of the nvx-rs that nvx.py runs,
build/nvx-rs.provenance.json, binds the SHA-256 of its executable to a
digest of the nvx-rs sources that built it, the crate's manifest, lock
file, build script, and src/ tree and rust-toolchain.toml, and to the
Rust release, target, and Cargo features of the build. nvx.py records it
when it builds nvx-rs, download installs a release's, and
record-nvx-rs-provenance records it for a
build made otherwise. package and the
adversarial executor refuse an
nvx-rs that it does not describe as built from the checkout's sources, so a
stale build never ships or runs unnoticed.
nvx-rs reports each error on standard error as error[CODE]: MESSAGE, and
exits with status 1, and each usage error as the usage of its parser and then
PROG: error[CODE]: MESSAGE, such as nvx.py run: error[argument-invalid]: ..., and exits with status 2. Each error is one line: a line break in a value
that its message names, such as a path, prints as \n or \r. CODE is a
stable code, which tells errors apart where their messages may change: callers
match codes, and stable fragments of messages, such as the names of options,
fields, and files, never whole messages. A code keeps its meaning and is never
reused, and a later release may add codes, which callers treat as failures of
the command. The usage errors that nvx.py itself reports, as argparse does,
carry no code, and neither does an interruption, which prints Interrupted
and exits with status 130. The
nvx-rs README gives the ErrorKind of
each code for callers of the nvx library.
| Code | Status | Meaning |
|---|---|---|
argument-unrecognized |
2 | The command line holds an argument that the command does not take, such as an unknown option. |
argument-missing |
2 | A required argument, or the value of an option, is missing. |
argument-invalid |
2 | An argument has an invalid value, such as one that is not among its choices, not a number, not of its form, or not valid Unicode. |
unsupported |
1 | NVX refuses the request: options that it cannot combine, a value that it does not accept, or a policy that it cannot represent or enforce. The message names the option or field. |
unavailable |
1 | A dependency is missing or unusable, other than as a finer code says: install metadata, the hypervisor, a release, a distro layer or scratch image that the request names, or a host that NVX does not support. |
artifact-missing |
1 | An artifact that the command runs, such as OpenVMM, the guest kernel, or an initramfs, does not exist, or no artifact root holds them. |
guest-features-missing |
1 | The guest image lacks control features that the sandbox needs, which the message names. |
sandbox-not-provisioned |
1 | No sandbox is provisioned at --state-dir, which does not exist, is not a plain directory, or holds no configuration. |
sandbox-provisioned |
1 | provision found the sandbox provisioned already, or it was provisioned again while deprovision removed it. |
sandbox-not-running |
1 | exec or stop found the VM of a provisioned sandbox not running. |
sandbox-running |
1 | start or deprovision found the sandbox's VM, or a launch of it, running. |
sandbox-exited |
1 | The sandbox's OpenVMM exited without a stop: the transition that finds it reports it and clears its runtime record, and start launches the sandbox again. |
sandbox-busy |
1 | Another lifecycle transition held the sandbox's lock until --timeout passed. |
runtime-mismatch |
1 | The sandbox's VM runs under another runtime identity than the artifacts', whose differing parts the message names, so only stop, which terminates it, applies. |
vmm-indeterminate |
1 | Whether the sandbox's OpenVMM runs cannot be determined, so its records remain for a later attempt. |
format-unsupported |
1 | A record or report has a format that this build does not read, such as the state of a sandbox that another build provisioned or started. |
foreign-files |
1 | deprovision found files that NVX does not own in the state directory, which the message names, and kept them with the directory. |
outcome-report-exists |
1 | A file exists at the --outcome-report path. |
scratch-claimed |
1 | Another sandbox claims the scratch image. |
exited |
1 | OpenVMM exited, as when a start fails, whose message names the category and status of OpenVMM's outcome report when it has one. |
exec-refused |
1 | The guest agent refused the workload, as for a working directory that it cannot use. |
endpoint-closed |
1 | OpenVMM closed the control endpoint, as it does when the VM shuts down. |
protocol |
1 | The control session failed otherwise, as when the guest breaks the protocol or the session is reset. |
timed-out |
1 | Another deadline passed, such as that of a stop or of a control request. |
io |
1 | A host I/O operation failed, or state that NVX recorded is unreadable or malformed. |
not-found |
1 | Other state that the command names does not exist on the host. |
other |
1 | Any other failure. |
| Command | Description |
|---|---|
init |
Initialize the OpenVMM submodule and its nested submodules. |
build-guest |
Build the Linux kernel and selected guest artifacts. |
check-guest-binaries |
Lint and test the Rust guest executables for musl. |
build-guest-binaries |
Build and digest the pinned Rust executables for Linux guests. |
test-guest-binaries |
Run the musl integration tests against prebuilt Rust guest executables. |
build-kernel |
Build the pinned and patched Linux kernel natively. |
build-initramfs |
Build the selected Alpine, Ubuntu, or Azure Linux initramfs. |
build-distro-layer |
Build a deterministic Ubuntu EROFS distro layer. |
verify-guest-determinism |
Rebuild Ubuntu guest artifacts twice and compare SHA-256 values. |
build-openvmm |
Build the OpenVMM release binary. |
record-openvmm-provenance |
Bind an existing OpenVMM binary to the pinned source revision. |
record-nvx-rs-provenance |
Bind an existing nvx-rs executable to its sources, Rust release, target, and features. |
materialize-kernel-provenance-inputs |
Write kernel provenance inputs from raw run-head blobs. |
setup-cross-os-cache |
Install GNU tar and zstd for GitHub Actions cross-OS caches. |
classify-ci-changes |
Select the CI suites that a list of changed files runs. |
kernel-patch-identity |
Print the digest of the kernel patch paths and contents that keys the kernel cache. |
guest-binary-source-identity |
Print the digest of the Rust guest binary source paths and contents that keys the guest image caches. |
record-guest-artifacts |
Record the cache keys and the SHA-256 of each file of guest artifact components, which a dev push checks before it reuses them. |
reuse-guest-artifacts |
Install guest artifact components that missed the cache from the latest same-repository pull request run for the merged head. |
check-required-ci |
Validate required GitHub Actions job results. |
report-nightly-ci |
Open, update, or close the report of failed nightly CI runs. |
check-static-elf |
Check that executables are static ELF files without an interpreter or an executable stack. |
test-openvmm-unit |
Run the OpenVMM workspace unit and documentation tests. |
build-openvmm-tests |
Build OpenVMM Petri VMM tests without running them. |
openvmm-test-cache-key |
Print the key under which CI caches the OpenVMM VMM tests that build-openvmm-tests builds. |
check-openvmm-tests |
Check that a directory holds the OpenVMM VMM tests that build-openvmm-tests builds from this checkout. |
reuse-openvmm-tests |
Keep OpenVMM VMM tests that a cache restored if build-openvmm-tests builds them from this checkout, and remove them otherwise. |
test-openvmm |
Run OpenVMM Petri VMM tests. |
plan-openvmm-test-shards |
Split the OpenVMM VMM tests into shards that nextest runs in balanced times for CI. |
test-microvm |
Run NVX Linux and device correctness tests through OpenVMM. |
plan-microvm-shards |
Split the default microVM suite into shards of balanced scenario durations for CI. |
doctor |
Qualify this host for the NVX time ABI. |
test-aci-edge-sandboxes |
Run the aci_edge_sandboxes Rust crate lifecycle test on a real hypervisor. |
test-agent-bundling |
Check the aci_edge_sandboxes crate's agent bundle staging without a hypervisor. |
test-adversarial |
Run a brokered Copilot-driven adversarial campaign. |
build |
Build the guest artifacts and OpenVMM. |
download |
Install the selected platform package built from this checkout or a released first-parent ancestor. |
run |
Run an OpenVMM microVM. |
sandbox |
Run or manage workloads over EROFS layers and private ext4 scratch. |
benchmark |
Run the OpenVMM-native benchmark coordinator. |
performance |
Collect, gate, and persist CI performance results, and check benchmark hosts. |
collect-sources |
Materialize verified Linux, Alpine, and Ubuntu release sources. |
collect-alpine-sources |
Collect exact Alpine recipes and upstream sources. |
collect-ubuntu-sources |
Collect exact Ubuntu source packages. |
create-linux-source-archive |
Create a Linux corresponding-source archive. |
package |
Stage a binary distribution. |
archive-release |
Create a deterministic archive from a staged distribution. |
verify |
Verify source and submodule inputs. |
python3 scripts/nvx.py initInitializes and recursively updates the OpenVMM Git submodule.
python3 scripts/nvx.py verifyVerifies the repository's pinned source inputs and submodule state.
python3 scripts/nvx.py setup-cross-os-cacheInstalls the GNU tar and zstd tools used by GitHub Actions cross-OS caches.
python3 scripts/nvx.py kernel-patch-identity [--root PATH]
Prints a digest of each file under kernel/patches/ by relative path, in path
order, together with its content. The guest build appends it to both kernel
cache keys, because hashFiles hashes contents only: a renamed patch changes
the key even when its bytes do not.
python3 scripts/nvx.py guest-binary-source-identity [--root PATH]
Prints the source digest that guest package manifests record for the Rust guest
binaries: a digest of the relative path and content of every file that their
build reads. Those are the crate's manifest, lockfile, and build script, its
Cargo configuration under nvx-rs/.cargo/, every file under nvx-rs/src/
except the host side and the test modules, rust-toolchain.toml, and the
build_constants.py and guest_binaries.py build modules. The host side is
the host and testing modules and the nvx-rs and nvx-fake-openvmm
binaries; the test modules are the tests.rs files, the tests directories,
and the managed agent's loopback harness. GuestRustBuildConstants in
scripts/nvx_tools/build_constants.py defines the set, and
build-guest-binaries fails when Cargo's dep-info
lists a file outside it. The Alpine, Ubuntu, and Azure Linux cache keys end
with the digest and hash no other file of nvx-rs, so a change to the
crate's host side or tests rebuilds no guest image. Because the digest covers
paths, and hashFiles hashes contents only, moving a module into its own
mod.rs changes the keys even when no bytes change.
python3 scripts/nvx.py record-guest-artifacts --components COMPONENTS
--keys JSON [--directory PATH]
Writes the guest artifact record, guest-artifacts.json, into the directory
of the guest artifacts, build/ by default. --components lists, separated
by spaces, some of the kernel, alpine, ubuntu, and azurelinux
components of the CI guest build; the CI debug kernel and the Ubuntu sandbox
scratch template, which every run formats anew, are in no record. The record
holds the exact cache keys of those components' cache entries, from the JSON
object of keys by name that --keys gives, and the SHA-256 of each of their
files. It fails if a key or a file is missing. Each CI job that builds guest
images records them, and uploads the record beside the components' own
artifacts, so that the dev push that merges the pull request can check them
before it reuses them; see Guest artifact reuse.
python3 scripts/nvx.py reuse-guest-artifacts
--repository OWNER/REPOSITORY
--workflow-ref REF
--record-artifact NAME
--components COMPONENTS
--keys JSON
(--commit SHA | --head-sha SHA)
[--dry-run]
[--destination PATH]
When the destination, build/ by default, lacks a file of a component that
--components lists, as after a cache miss, replaces every file of those
components in it with the one from the artifacts of the latest pull_request
run of the workflow that --workflow-ref, a GITHUB_WORKFLOW_REF value,
names, for the pull request head: the second parent of the merge commit
--commit, or --head-sha. The run's head must be in --repository itself,
never a fork.
The command first downloads the run's --record-artifact artifact, which
holds the record that record-guest-artifacts
wrote, and goes on only if the record gives exactly the cache keys of the
components' cache entries in --keys, a JSON object of keys by name. It then
downloads the artifacts in which CI hands over the components' files,
guest-kernel, guest-alpine, guest-ubuntu, guest-ubuntu-distro, or
guest-azurelinux, and installs the files only when no artifact has expired,
each matches the SHA-256 digest that GitHub recorded for it, every file
matches its SHA-256 in the record, and the kernel and initramfs provenance and
the Ubuntu and Azure Linux manifests match the files and this checkout's
sources. A failed installation puts back the files that it replaced. With
--dry-run, it checks the record and the artifacts in the same way, even when
the destination lacks no file, but installs nothing.
It reads the GitHub token from GH_TOKEN or GITHUB_TOKEN and gives up on
the artifacts after two minutes, which a timer enforces on Linux until the
installation starts. It never fails for a reason not to reuse, so the guest
build proceeds: it prints the reason, as a GitHub notice when reuse is not
possible, such as for other keys or an expired artifact, and as a warning for
a fault, such as a failed request or a mismatched file, sets the reused step
output to true or false, and adds the outcome to the job summary; see
Guest artifact reuse.
python3 scripts/nvx.py classify-ci-changes
--event-name {pull_request,push}
--changed-files PATH
[--null]
[--summary PATH]
Reads one changed repository path per line by default, or NUL-delimited paths
with --null, and prints
the change detector's
run-guest-artifacts, run-openvmm-binaries, run-openvmm-unit-tests,
run-openvmm-vmm-tests, run-microvm-tests, and run-benchmarks outputs as
name=true or name=false lines. Documentation selects no suite. Pull
requests and pushes run each test suite only when one of its own inputs
changed. A pull request runs the platform benchmarks only when one of their
inputs changed, but a push runs them for every change but documentation,
because it publishes the development release and records the performance
history. The guest artifacts and the OpenVMM binaries are built exactly when a
job that uses them runs. The summary file receives a Markdown
explanation of each decision. CI runs the full matrix instead when the command
fails or omits an output; see
Change classification.
python3 scripts/nvx.py check-required-ci
--event-name {pull_request,push,schedule,workflow_dispatch}
--same-repository {false,true}
--run-guest-artifacts VALUE
--run-openvmm-binaries VALUE
--run-openvmm-unit-tests VALUE
--run-openvmm-vmm-tests VALUE
--run-microvm-tests VALUE
--run-benchmarks VALUE
Validates the GitHub Actions result values supplied through the
QUALITY_RESULT, CHANGES_RESULT, and job-specific *_RESULT environment
variables. CI supplies the true or false suite flags that
classify-ci-changes reports. The command expects successful results for jobs
enabled by the event, repository, and suite flags, and skipped for jobs that
are not enabled; mismatches are reported as errors and cause a nonzero exit.
As in CI, a job that uses the guest artifacts or the OpenVMM binaries is
enabled only when the flags enable the jobs that build them too.
python3 scripts/nvx.py report-nightly-ci
--repository OWNER/REPOSITORY
--run-id ID
[--run-attempt N]
[--assignee LOGIN]...
[--label NAME]...
[--summary PATH]
[--dry-run]
Reports a completed scheduled run of the CI workflow, by default its latest
attempt, in the repository's nightly failure report: a failing run opens or
updates the report, and a passing run closes it, as
Nightly failure reports describes. The command
prints what it did. It reads a token from GH_TOKEN or GITHUB_TOKEN, which
must be able to read the run's jobs, logs, and check annotations, and to read
and write issues. A new report is assigned to every --assignee and names
them as its triage owners, and carries every --label that exists in the
repository; the command never creates a label. --summary appends the report
to a Markdown file, such as $GITHUB_STEP_SUMMARY. --dry-run prints the
changes that the command would make, with their Markdown, and makes none; it
accepts a run of any event, so that a past failure can preview its report,
and, for a public repository, needs a token only for the job logs.
python3 scripts/nvx.py check-static-elf PATH [PATH ...]
Prints each executable's ELF type, size, and linkage, and fails unless every
one is a 64-bit little-endian ELF executable without a program interpreter
(PT_INTERP) or needed libraries (DT_NEEDED), so that it runs in a guest
without a dynamic loader. A static position-independent executable passes. It
also fails for an executable stack: a PT_GNU_STACK program header with
PF_X. The nvx-rs CI job checks the
guest binaries with it.
python3 scripts/nvx.py check-guest-binaries [--toolchain TOOLCHAIN]
The Linux-only check command adds the x86_64-unknown-linux-musl target to the
Rust toolchain that --toolchain names, by default the exact
rust-toolchain.toml pin. It then runs Clippy with warnings denied on every
target of the guest feature, and the feature's musl tests, whose static test
executables run on the host. It ignores an inherited NVX_GUEST_BINARIES, so
that the integration tests run the binaries that Cargo builds from the
checkout. The nvx-rs CI job runs it with the toolchain that its action
selects, before it builds the binaries.
python3 scripts/nvx.py build-guest-binaries --output DIRECTORY
The Linux-only build command checks the exact rust-toolchain.toml pin and
builds the nine Rust guest executables for x86_64-unknown-linux-musl with one
cargo build of the guest profile, a fresh target directory, Cargo's locked
mode, incremental compilation disabled, inherited Cargo profile overrides
removed, remapped source paths, and no linker build IDs. It then fails unless
the source identity, which
guest-binary-source-identity prints and the
guest image cache keys end with, covers every file that Cargo's dep-info lists
for the executables: each source file that rustc read, including the targets of
include_str! and include_bytes!, and each path that the build script
watches. Files that the build generates in its own target directory are exempt.
It runs the static-ELF check and writes manifest.json with the source and
executable SHA-256 digests.
It updates the output of an earlier build in place, adding nvx-managed-agent,
nvx-ingress, nvx-time, and nvx-time-probe when that build's manifest does
not record them and removing the retired nvx-port-io executable when that
build's manifest records it, and refuses any other existing DIRECTORY.
python3 scripts/nvx.py test-guest-binaries --binaries DIRECTORY
The Linux-only test command validates the nine binaries and build manifest,
sets NVX_GUEST_BINARIES, and runs the musl guest_tools integration test
against that exact directory.
See Setup for host prerequisites.
python3 scripts/nvx.py materialize-kernel-provenance-inputsRecreates the tracked kernel configuration and patch files from the current Git run head. It removes stale worktree patches that are absent from that revision and is used before packaging kernel provenance inputs.
python3 scripts/nvx.py build-guest
[--guest {alpine,ubuntu,azurelinux,all}]
[--native]
[--debug-kernel]
By default, builds the guest kernel and initramfs with Docker. --native
builds the selected artifacts directly on Linux instead. Alpine is the
default. Azure Linux does not support --native and always builds through
Docker. --guest all also builds the Ubuntu EROFS distro layer.
--debug-kernel also builds the CI debug kernel, build/vmlinux-debug; see
Build.
python3 scripts/nvx.py build-kernel [--debug]Fetches, verifies, patches, and builds the pinned kernel directly on Linux.
--debug builds the CI debug variant instead of the production kernel.
python3 scripts/nvx.py build-initramfs [--guest {alpine,ubuntu,azurelinux}]Builds the selected initramfs. Alpine and Ubuntu build directly on Linux; Azure Linux uses Docker. Alpine is the default.
python3 scripts/nvx.py build-distro-layer
--guest ubuntu
[--output PATH]
[--replace]
Builds the immutable Ubuntu EROFS distro layer directly on Linux. The default
output is build/ubuntu-distro.erofs. Existing output or manifest files are
rejected unless --replace is present.
python3 scripts/nvx.py verify-guest-determinism
--guest ubuntu
[--work-dir PATH]
Currently supports only --guest ubuntu. It builds the Ubuntu initramfs and
EROFS layer twice from separate roots and compares every artifact SHA-256. A
mismatch reports the first differing normalized rootfs entry when one exists.
python3 scripts/nvx.py build-openvmm [--skip-restore] [--backend {kvm,mshv,whp}]
Builds the openvmm release binary. Before building, the command runs
cargo xflowey restore-packages; use --skip-restore when those packages are
already restored. Without --backend, Windows builds the native MSVC target
and Linux builds the native GNU target. On Linux, --backend kvm selects GNU
and --backend mshv selects musl; Windows accepts --backend whp. Build-target
selection does not probe /dev/kvm or /dev/mshv, so compilation also works
on build-only hosts and hosts exposing both devices. Unsupported OS/backend
combinations are rejected.
python3 scripts/nvx.py record-openvmm-provenanceRecords the checked-out OpenVMM revision, clean-state flag, and executable
SHA-256 for the existing openvmm/target/release/openvmm[.exe] binary in
build/openvmm.provenance.json. The command takes no options. It requires
the OpenVMM submodule to be checked out/initialized and the release binary to
already exist at openvmm/target/release/openvmm on POSIX or
openvmm/target/release/openvmm.exe on Windows. The command fails if either
prerequisite is missing.
python3 scripts/nvx.py record-nvx-rs-provenance [--target TARGET]
| Option | Default | Description |
|---|---|---|
--target TARGET |
the host target that rustc -vV reports |
Rust target that built nvx-rs. |
Records build/nvx-rs.provenance.json for the nvx-rs that nvx.py runs
without building it, the one that NVX_RS names or else
nvx-rs/target/release/nvx-rs[.exe]; see nvx-rs. The record binds
the executable's SHA-256 to the digest of this checkout's nvx-rs sources,
the Rust release that rust-toolchain.toml pins, the target, and the cli
feature, so record it right after building nvx-rs from this checkout. For
x86_64-unknown-linux-musl, the target of the Linux release packages, the
executable must be static. CI's NVX CLI jobs record the builds that they hand
to the packages, and nvx.py records the builds that it makes itself.
python3 scripts/nvx.py build
[--guest {alpine,ubuntu,azurelinux,all}]
[--native]
[--debug-kernel]
[--skip-restore]
[--backend {kvm,mshv,whp}]
Runs build-guest followed by build-openvmm. The options have the same
meaning as on those individual commands.
See Build for dependencies, outputs, and native build details.
python3 scripts/nvx.py test-openvmm-unitRuns the OpenVMM workspace's unit-test binaries with cargo-nextest's agent
profile and the ci feature. Packages that require specialized test harnesses
are excluded, along with all fuzz crates reported by OpenVMM's xtask.
Afterward, runs the workspace doctests with Cargo. On Windows, it appends
/Brepro to _LINK_, so that MSVC links proc macros reproducibly and sccache
can reuse the crates that use them; see
Compilation cache.
python3 scripts/nvx.py build-openvmm-tests
--backend {kvm,mshv,whp} [--backend {kvm,mshv,whp} ...]
--output PATH
Builds the VMM tests that test-openvmm runs for each
--backend, without running them, into the new or empty directory that
--output names, so that another host can run them without building OpenVMM.
The backends must run on this host's operating system: Linux builds the KVM
and MSHV tests, and Windows the WHP tests. The command runs OpenVMM's
vmm-tests-run in build-only mode on the union of the backends' filters, for
the linux-x64 or windows-x64 target, since NVX supports only x86-64 hosts,
as a release build without optional features whose run script uses the
nextest CI profile, which puts the tests' nextest archive, OpenVMM, the guest
agents, the test firmware and kernels, and a vmm-tests-run-target runner into
the directory. It then reads the runner's options from the run script that the
build writes, and records them in the directory's nvx-openvmm-tests.json
with the OpenVMM revision and whether its checkout was clean, the Rust
release, the target, the vmm-tests-run build options, each backend's filter,
and every file's size, SHA-256, and executable bit. The tests boot NVX's kernel
and initramfs only when they run, so the build needs no guest artifacts. On
Windows, it appends /Brepro to _LINK_, as test-openvmm does.
python3 scripts/nvx.py openvmm-test-cache-key
--backend {kvm,mshv,whp} [--backend {kvm,mshv,whp} ...]
Prints the key under which CI caches the test content that
build-openvmm-tests builds for the backends from this
checkout (see OpenVMM VMM test cache):
openvmm-vmm-tests-vVERSION-TARGET-REVISION-DIGEST. VERSION is the
cache's own version, which changes when what a build makes changes in a way
that nothing else in the key captures. TARGET is the build's target, REVISION
the OpenVMM revision that the checkout's index pins, and DIGEST the SHA-256 of
the fields of the manifest of a clean build of that revision that describe
what built it: the manifest's format, the Rust release, the target, the build
options, and each backend's filter and their union. The backends must run on
this host's operating system, as for build-openvmm-tests, and the command
needs neither the OpenVMM submodule's files nor Cargo. In GitHub Actions, it
also gives its step the key output.
python3 scripts/nvx.py check-openvmm-tests
--backend {kvm,mshv,whp} [--backend {kvm,mshv,whp} ...]
--content PATH
Fails unless the directory that --content names holds exactly the test
content of the backends' cache key: a manifest that records every field that
openvmm-test-cache-key digests, for a clean
OpenVMM checkout, and the runner and options that the content's run script
runs, and exactly the files that the manifest lists, with their sizes,
SHA-256, and executable bits. CI checks each build this way before it caches
it.
python3 scripts/nvx.py reuse-openvmm-tests
--backend {kvm,mshv,whp} [--backend {kvm,mshv,whp} ...]
--content PATH
Keeps the directory that --content names, into which a cache restored test
content, if it passes the checks of
check-openvmm-tests, and otherwise removes it with a
warning that gives the reason, so that a build can take its place. A
directory that doesn't exist, as after a cache miss, needs no removal. The
command fails only when it cannot compute the key or remove the directory,
never because of the content. In GitHub Actions, it gives its step the
reused output, true or false.
python3 scripts/nvx.py test-openvmm
--backend {kvm,mshv,whp}
[--content PATH]
[--shard INDEX/COUNT]
Builds and runs OpenVMM's checkout-owned VMM tests. The Linux-direct microVM
TTRPC test boots NVX's build/vmlinux and build/initramfs.cpio.gz, so build
the guest first; the remaining test artifacts are produced by OpenVMM itself.
On Windows, it appends /Brepro to _LINK_, as test-openvmm-unit does.
--shard INDEX/COUNT, such as 1/2, runs shard INDEX of the COUNT shards of
the backend's tests, as
openvmm_test_shards.json
splits them: the tests of the backend's filter that the shard lists, which the
command runs with the backend's filter and the shard's tests both as its
filter. CI runs each shard of the WHP tests in a job of its own (see
OpenVMM VMM test shards). Only the WHP filter
names each of its tests, so only the WHP tests have shards. The command fails
before it runs a test unless that file puts each test of every sharded filter
in exactly one shard, with its duration, and splits the backend's tests into
COUNT shards.
With --content, the command builds nothing: it runs the backend's tests from
a directory that build-openvmm-tests built for that
backend, so the host needs neither the OpenVMM checkout nor its Rust targets.
It refuses a directory built from another OpenVMM revision than the checkout
pins or for another filter than the backend's, and one that lacks a file of
the build or holds a truncated one, and it restores the executable bits that
an artifact download drops. It still needs the guest artifacts above, and
Petri writes the test results to test_results in the directory, where the
command also writes the test duration report. On
Linux, it runs the tests with the directory's vmm-tests-run-target runner and
the options that its manifest records.
On Windows, the pinned OpenVMM's runner checks for Hyper-V whatever tests it
runs: the account must be able to run Get-VM, and the
AllowFirmwareLoadFromFile and EnableAdditionalComPorts values must be set
under HKLM\Software\Microsoft\Windows NT\CurrentVersion\Virtualization. No WHP
test needs Hyper-V, so the command takes the runner's remaining steps itself.
It downloads the images that the recorded options name into images in the
directory, or into the directory that VMM_TEST_IMAGES names, unless that
already holds an image at the size that OpenVMM declares. It then sets the
environment that Petri reads and runs the backend's filter, or the shard's,
from the directory's nextest archive with cargo-nextest, which must be on
PATH, the directory's nextest configuration, the profile that the options
select, and the directory as the workspace. It refuses the options that only
the runner carries out, such as prep steps, the test IGVM agent, hardware
isolation, and repeated runs, and an image that it cannot download.
A run of a test content directory records how long each test that nextest ran
took in openvmm-test-durations.json in the directory's test_results, as
the run ends, whether its tests passed or not, and CI uploads the
reports of its VMM-test jobs. The command reads the durations from the JUnit
report that OpenVMM's nextest configuration writes for the ci profile, which
the content's runs use, under the workspace that a run remaps to the
directory: target/nextest/ci/junit.xml. It removes both reports of an
earlier run before it runs the tests, so that a run whose nextest writes no
JUnit report leaves no test duration report either. A run that is
interrupted, as when CI cancels its job, stops nextest before it writes the
JUnit report, which it writes only as its run ends, and so usually leaves no
test duration report. A report that it cannot write only warns, so that it
never hides the run's own outcome. The report is a JSON object:
| Field | Value |
|---|---|
schema, schema_version |
nvx-openvmm-test-durations and 1. The version changes only when a reader of the previous version would misread a report, not when a field is added. |
backend |
The --backend |
shard |
The --shard, as {"index": INDEX, "count": COUNT}, or null for a run of all of the backend's tests |
filter |
The nextest filter that the run ran |
logical_processors |
The host's logical processors, one for each of nextest's default test threads, or null if the OS doesn't tell |
github |
In GitHub Actions, the job's repository, workflow, run_id, run_attempt, job, event_name, ref, sha, runner_name, and runner_os, as strings from the GITHUB_ and RUNNER_ variables of those names; otherwise null |
tests |
Each test case of the JUnit report, in its order: its binary and test names, the seconds that it ran, to the millisecond, and its outcome: passed, failed, flaky for a test that passed only when nextest retried it, or skipped |
python3 scripts/nvx.py plan-openvmm-test-shards
[--report PATH]...
[--seconds TEST=SECONDS]...
[--shards BACKEND=COUNT]...
[--test-threads BACKEND=COUNT]...
[--partition PATH]
Plans the shards of the OpenVMM VMM tests, which CI runs in parallel (see
OpenVMM VMM test shards), and writes them to
openvmm_test_shards.json,
or to --partition. The file holds, for each sharded backend, the test
threads with which nextest runs its tests, the median duration of each of the
tests that its filter selects, the CI runs whose reports gave them, and each
shard's tests with the time that nextest takes to run them. Only a backend
whose filter names each of its tests, which only the WHP filter does, can have
shards.
--report reads test duration reports, or every
report in a directory, such as the one that gh run download fills with a
run's openvmm-vmm-test-durations-* artifacts. Each backend's duration of a
test is the median, to a tenth of a second, of the reports' times of the test
when it passed, in runs of a shard or of all of the backend's tests. A test
that no report measures keeps its duration in the file. --seconds sets a
test's duration on every backend instead, such as an estimate of a new test
that no report measures yet; the command fails when a test of a sharded filter
has no duration.
nextest runs a shard's tests in parallel, so a shard does not take the sum of
their durations. The command models how nextest runs them: in name order, each
as soon as it has the test threads that it requires, and never one before an
earlier one. A test requires the threads that OpenVMM's nextest configuration
gives it, 2, or 12 for a heavy test and 32 for a very_heavy one, and
nextest gives a test that requires more than it runs all of its threads, so
that the test runs alone and the tests after it wait for it. A backend's test
threads are those that the file records, which --test-threads changes, one
for each logical processor of its runners; the command notes the reports whose
runners have another number.
Each backend keeps its number of shards unless --shards changes it, and a
new backend needs both --shards and --test-threads. Each test starts in its
shard of the file, and a test that has none goes, longest first, to the shard
that nextest takes the least time to run so far. A shard that is still empty,
as one that --shards adds can be, takes the longest test of the longest
shard that has more than one. The command then moves a test to another shard,
or swaps two tests of different shards, while that shortens the longest shard
by at least a second, comparing the next longest where the longest stay alike,
and takes the best such change each time; no change empties a shard. It prints
each backend's shards with the times that nextest takes to run them and all of
its tests, and the tests that changed shard.
python3 scripts/nvx.py test-microvm
--backend {kvm,mshv,whp}
[--guest {alpine,ubuntu,azurelinux}]
[--scenario SCENARIO]...
[--debug-kernel]
[--shard INDEX/COUNT]
[--processors {1,2,4,8} ...]
[--memory-mib MIB]
[--timeout SECONDS]
[--output-dir PATH]
Runs NVX-owned Linux, SMP, virtio, sandbox, and snapshot correctness scenarios
against the public OpenVMM CLI. Repeat --scenario to select a subset; without
it, every scenario supported by the selected guest runs, except four that run
only when named. smp-lapic repeats smp and also asserts that every CPU
uses the one-shot counting LAPIC. managed-exec-config, sandbox-lifecycle,
and sandbox-proxy-only drive the public sandbox lifecycle commands (see
Run) and also require the guest-artifact build's
Ubuntu layer and scratch template, which packages omit. Alpine remains the
default. Ubuntu and Azure Linux cannot act as sandbox control, so they reject
the Alpine-control-only sandbox-blocks and scratch-snapshot scenarios and
the sandbox-control-dependent snapshot-tiers, managed-exec-config,
sandbox-lifecycle, and sandbox-proxy-only scenarios. Ubuntu also rejects
the Alpine-prompt-specific console-snapshot scenario. --debug-kernel boots
the CI debug kernel, build/vmlinux-debug, whose soft-lockup and hung-task
detectors the guest's time ABI watcher reports; without --scenario, it runs
only the same-host restore scenarios smp, smp-snapshot,
restore-processors, restore-downtime, and snapshot-tiers.
--shard INDEX/COUNT, such as 1/2, runs shard INDEX of the COUNT shards of
the default Alpine suite on the default kernel, as
microvm_shards.json splits it for
the backend; CI runs each shard in a job of its own (see
MicroVM test shards). It takes no --scenario,
--debug-kernel, or other --guest, and fails before it runs a scenario
unless that file puts each default scenario in exactly one shard of every
backend and splits the backend's suite into COUNT shards. The
cpu-profile-fallback scenario cold boots with OpenVMM's host-cpu-unknown
test hook, which makes OpenVMM's default CPU profile selection, auto, fall
back to a host profile on a host that a built-in profile serves, checks the
fallback's warning, captures the guest, and restores it on the snapshot's host
profile. The managed-lifecycle scenario boots a guest whose init hands
the boot to the managed agent, and runs the ignored nvx-rs test
tests/managed_lifecycle.rs with Cargo, which starts OpenVMM with the
scenario's command line and drives the guest's control session; the scenario
then checks OpenVMM's outcome report and the guest console. The scenarios that
drive run or sandbox, or that compile egress policies, run
nvx-rs. The
command requires build/vmlinux (with --debug-kernel, build/vmlinux-debug
and its build/vmlinux-debug.config), the selected initramfs, and
openvmm/target/release/openvmm[.exe].
test-microvm prints each scenario's progress line as the scenario starts,
so that a log's timestamps follow the scenarios. It also records how long each
scenario takes in scenario-durations.jsonl in --output-dir, replacing the
previous run's report, and CI uploads the reports of its microVM
jobs. The report is JSON Lines: one JSON object per line, whose record field
names its kind. The first line, run, describes the run:
| Field | Value |
|---|---|
schema, schema_version |
nvx-microvm-durations and 1. The version changes only when a reader of the previous version would misread a report, not when a field is added. |
started_at |
When the run and its setup started, in UTC, such as 2026-10-21T13:50:00.123Z |
guest, backend |
The --guest and the --backend |
kernel |
debug with --debug-kernel, otherwise default |
processors |
The --processors counts, without repeats |
scenarios |
The selected scenarios |
github |
In GitHub Actions, the job's repository, workflow, run_id, run_attempt, job, event_name, ref, sha, runner_name, and runner_os, as strings from the GITHUB_ and RUNNER_ variables of those names; otherwise null |
Each later line records a span of the run as soon as the span ends, so that a run that fails or is interrupted keeps every span that it completed. The spans follow one another without gaps, so they add up to the run's time:
setup, from the start of the Python process, or from the report's creation where the OS doesn't report that start, until the first scenario starts;- a
scenariofor each scenario execution, until the next one starts.console-exit,smp, andsmp-lapicexecute once per processor count; teardown, which sums up the time ABI evidence and records the run's outcome.
Every span has its started_at, its seconds, rounded to the millisecond, its
outcome, passed, failed, or interrupted, for example by SIGINT, and
an error: for a failure, the exception's type and message, cut to 1,000
characters, and otherwise null. A scenario record also has the
scenario, the guest, backend, and kernel, and its processors: the
--processors counts that the execution covers, such as [4] for smp on 4
vCPUs, the largest count for time-abi-conformance, all of them for
smp-snapshot and restore-processors, or null for a scenario that doesn't
take them. A failed run ends with the failed scenario's record and the
teardown; a run that is killed keeps every record that it completed and has
no teardown.
python3 scripts/nvx.py plan-microvm-shards
[--report PATH]...
[--seconds SCENARIO=SECONDS]...
[--shards BACKEND=COUNT]...
[--partition PATH]
Plans the shards of the default Alpine suite on the default kernel, which CI
runs in parallel (see MicroVM test shards), and
writes them to microvm_shards.json,
or to --partition. The file holds each backend's median duration of every
default scenario, the CI runs whose reports gave them, and each shard's
scenarios with their total duration.
--report reads scenario duration reports, or
every report in a directory, such as the one that gh run download fills with
a run's nvx-microvm-durations-* artifacts. Each backend's duration of a
scenario is the median, to a tenth of a second, of the reports of the default
suite, sharded or not: those of the Alpine guest on the default kernel with
the default processor counts, of runs that ended, passed or not. A report
counts for a scenario only when every execution of the scenario passed, such
as the four of console-exit. A scenario that no report measures keeps its
duration in the file. --seconds sets a scenario's duration on every backend
instead, such as an estimate of a new scenario that no report measures yet;
the command fails when a default scenario has no duration.
Each backend keeps its number of shards unless --shards changes it. Each
scenario starts in its shard of the file, and a scenario that has none goes,
longest first, to the shard that takes the least time so far. A shard that is
still empty, as one that --shards adds can be, takes the longest scenario of
the longest shard that has more than one. The command then moves a scenario
to another shard, or swaps two scenarios of different shards, while that
shortens the longest shard by at least a second, comparing the next longest
where the longest stay alike, and takes the best such change each time. It
prints each backend's shards with their durations, and the scenarios that
changed shard.
python3 scripts/nvx.py doctor
--backend {kvm,mshv,whp}
[--checks ID ...]
[--openvmm PATH]
[--kernel PATH]
[--initrd PATH]
[--cpu-fingerprint PATH | --no-openvmm]
[--ci-schedule]
[--probe-dir PATH]
[--summary PATH]
[--timeout SECONDS]
Qualifies this host for the time ABI.
It runs the selected checks in order, prints one
NVX-DOCTOR: check=<id> status=<pass|fail> detail="..." line per check, and
exits with status 1 if any check fails. A check fails, and never passes, when
it can't read a fact it gates on. CI describes each
check and the subset that CI runs.
| Option | Default | Description |
|---|---|---|
--backend {kvm,mshv,whp} |
required | Select the backend to qualify. |
--checks ID ... |
H1 to H7 |
Run only these checks, from H1 to H7, in order. |
--openvmm PATH |
openvmm/target/release/openvmm[.exe] |
Select the OpenVMM binary for H2, H3, and H6. |
--kernel PATH |
build/vmlinux |
Select the guest kernel for H3 and H6. |
--initrd PATH |
build/initramfs.cpio.gz |
Select the guest initramfs for H3 and H6. |
--cpu-fingerprint PATH |
nvx-cpu-fingerprint-<backend>.json in the probe directory |
Select where H2 writes OpenVMM's CPU fingerprint. |
--no-openvmm |
off | Qualify without an OpenVMM binary. H2 then checks the CPU identity and generation but not the CPU profile, and --checks must exclude H3 and H6, which boot OpenVMM. |
--ci-schedule |
off | Run H4 and H6 on CI's short schedules: 3 rate samples 1 s apart instead of 13 samples 10 s apart, and two warp-probe runs instead of five. |
--probe-dir PATH |
$RUNNER_TOOL_CACHE/nvx-host-time-probe, or build/host-time-probe |
Select the cache directory for the host probe, which the doctor builds with rustc. |
--summary PATH |
none | Append a Markdown summary, for example to $GITHUB_STEP_SUMMARY. |
--timeout SECONDS |
120 |
Set the seconds allowed for each probe or guest. H4 gets its sampling window on top. |
python3 scripts/nvx.py test-aci-edge-sandboxes
--backend {kvm,mshv,whp}
[--output-dir PATH]
[--cargo CARGO]
Runs the ignored openvmm_e2e tests of the aci_edge_sandboxes crate,
whose OpenVMM backend builds on the nvx crate,
against a real hypervisor. The tests start sandboxes with build/vmlinux and
the Alpine initramfs, whose userland the workloads use directly, and run
workloads. They check the workload identity, timeouts, and cancellation, that
guest state lasts until a stop, and that a start terminates the VM of an
interrupted earlier start before deprovisioning. They check that a backend
over the same artifacts reattaches to a running VM, and that one whose
initramfs differs drives it through no control session, and only terminates
it on stop. They check that each workload
starts in its requested working directory, or / without one, and that a
missing, non-directory, or inaccessible working directory fails the launch.
They also map temporary host directories read-only, read-write, and denied,
and check egress defaults and
rules against the network gateway, which needs no Internet access. The command requires
Cargo and openvmm/target/release/openvmm[.exe], and writes the OpenVMM log to
--output-dir (default build/test-results/aci_edge_sandboxes) when the test fails.
The sandboxes keep their state in a temporary directory, which the command deletes
only once every sandbox has been deprovisioned. If the test is interrupted or fails
to stop or deprovision a sandbox, the command keeps the directory and names each
remaining sandbox with the OpenVMM process ID that may still run.
python3 scripts/nvx.py test-agent-bundling
[--cargo CARGO]
[--toolchain TOOLCHAIN]
Checks how the aci_edge_sandboxes crate
stages edge agent bundles with its agent-bundled feature, without a
hypervisor or an agent library that runs. The command writes a synthetic bundle
for this host's platform, whose files hold their own names, stages it through
ACI_EDGE_SANDBOXES_AGENT_BUNDLE_DIR, and requires the crate's bundled-agent
unit tests to find it with the digests that the build compiled in. It then
requires the build to reject copies of the bundle with a tampered file, a
runtime manifest that pins another file as the kernel, another sandbox ABI, and
a link in place of a directory: a symbolic link, or a junction on Windows. It
reads the variable names from the crate's src/agent/bundle_contract.rs and the
sandbox ABI from its data model, and clears the crate's other bundle variables
from the environment of the builds. --cargo selects the Cargo executable and
--toolchain the Rust toolchain that rustup runs it with. CI runs the command
on Linux and Windows.
python3 scripts/nvx.py test-adversarial
--backend {kvm,mshv,whp}
--campaign {workload-isolation,guest-isolation,snapshot-isolation}
[--budget-seconds SECONDS]
[--budget-actions COUNT]
[--budget-ai-credits CREDITS]
[--seed SEED]
[--output-dir PATH]
[--replay ACTIONS.jsonl]
[--model MODEL]
[--host-type {baremetal,virtual-machine}]
[--memory-mib MIB]
[--phase-timeout SECONDS]
[--action-timeout SECONDS]
[--executor-command PATH]
[--no-minimize]
[--minimize-attempts COUNT]
Runs a bounded adaptive campaign in which an already installed and authenticated Copilot CLI selects one deterministic primitive at a time. Copilot has no tools or direct NVX access. The typed broker validates and records every action, while a credential-free executor and independent watchdog own VM operation, canaries, teardown checks, and the clean post-campaign boot.
| Option | Default | Description |
|---|---|---|
--backend |
required | Select KVM or MSHV on Linux, or WHP on Windows. |
--campaign |
required | Select workload, privileged-guest, or snapshot boundary probes. |
--budget-seconds |
900 |
Bound preflight, actions, canary boot, and minimization wall time. |
--budget-actions |
8 |
Bound accepted and executed broker actions. |
--budget-ai-credits |
300 |
Bound charged Copilot usage; the minimum is 60 credits so authentication preflight and at least one action each retain a 30-credit CLI cap. |
--seed |
0 |
Seed deterministic candidate ordering and synthetic canary data. |
--output-dir |
backend-specific path under build/test-results |
Parent for a unique campaign run directory. |
--replay |
none | Replay an actions.jsonl bound to its sibling replay-manifest.json, without invoking Copilot. |
--model |
Copilot auto routing | Select the strategist model. |
--host-type |
NVX_HOST_TYPE or unspecified |
Record whether the executor is bare metal or a virtual machine. |
--memory-mib |
256 |
Set memory for deterministic microVM scenarios. |
--phase-timeout |
60 |
Set each underlying deterministic scenario phase timeout. |
--action-timeout |
600 |
Set the outer limit for one action or canary boot. |
--executor-command |
local child executor | Select one trusted no-argument wrapper for a separate disposable target. |
--no-minimize |
off | Disable fresh-target shorter-prefix replay after an anomaly. |
--minimize-attempts |
3 |
Bound shorter-prefix attempts within the campaign time budget. |
Missing or unauthenticated Copilot CLI is a preflight failure in adaptive
mode. Replay mode has no Copilot prerequisite. Production and CI campaigns
must use a separate disposable executor through --executor-command; local
mode cannot reliably classify a target-host crash. Local target logs are kept
under the short build/adv state root to avoid Windows path-length failures;
the run summary records their absolute location. See
Copilot-driven adversarial testing
for the trust boundary, wrapper contract, artifacts, and CI policy.
python3 scripts/nvx.py download
[--repository OWNER/REPOSITORY]
[--hypervisor {auto,whp,kvm,mshv}]
| Option | Default | Description |
|---|---|---|
--repository OWNER/REPOSITORY |
microsoft/nvx |
GitHub repository from which to download the release. |
--hypervisor {auto,whp,kvm,mshv} |
auto |
Select the release platform. auto chooses WHP on Windows and KVM on Linux. |
download first looks for the selected platform's package in the release that
CI built from the checked-out commit. When that package exists, the packaged
artifacts match the checkout. CI publishes a development release tagged
v<VERSION>-dev.<first 12 characters of the commit> for each dev commit whose
checks pass. When HEAD has no release, as with CI's [skip ci] baseline
commits, commits whose checks failed, and local commits, or its release has no
package for the selected platform, download uses the nearest first-parent
ancestor that has one. Those fallback artifacts match that ancestor, not
HEAD. It checks at most 20 commits, at one GitHub API request each. Update the
checkout to install a newer release.
Installing a release replaces the packaged guest artifacts under build/ and
removes any known guest artifact that the release does not declare, such as the
Azure Linux guest that source-inclusive packages omit. It installs the
packaged OpenVMM at openvmm/target/release/openvmm[.exe] and the packaged
nvx-rs at nvx-rs/target/release/nvx-rs[.exe], both executable, with their
provenance in build/, so that run and sandbox need
no Cargo.
download installs the packaged nvx-rs only when its provenance describes
the build of the checkout's nvx-rs sources that nvx.py runs, with the Rust
release that the checkout pins, the release target, and the cli feature,
because nvx.py runs that executable without rebuilding it. A fallback
release built from other nvx-rs sources, and a release from before packages
shipped nvx-rs, still install everything else: download then removes any
earlier nvx-rs and its provenance and reports why, and nvx.py builds
nvx-rs from the checkout with Cargo when a command needs it; see
nvx-rs. A package whose nvx-rs does not match its own provenance
fails the download before it replaces anything.
Windows release downloads support WHP. Linux release downloads support KVM
and MSHV. download first uses GH_TOKEN or GITHUB_TOKEN when configured.
If GitHub rejects that token with HTTP 401 or 403, NVX reports the failure and
retries without credentials so public releases remain downloadable. Private
repositories require a token with read access to the repository contents that
is authorized for the organization when it enforces single sign-on.
python3 scripts/nvx.py run
[--guest {alpine,ubuntu,azurelinux}]
[--hypervisor {auto,whp,kvm,mshv}]
[--machine {microvm}]
[--memory-mib MIB]
[--memory-capacity-mib MIB]
[--processors {1,2,4,8}]
[--cpu-profile ID]
[--mount GUEST_TARGET,HOST_PATH[,ro|rw]]...
[--mount-deny HOST_PATH]...
[--mount-allow HOST_PATH]...
[--mount-write HOST_PATH]...
[--mount-owner {vmm,caller}]
[--net IPV4/PREFIX]
[--network-profile {portable}]
[--network-egress {allow,deny}]
[--network-ingress {allow,deny}]
[--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]...
[--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]...
[--network-egress-policy-file PATH]
[--host-loopback {allow,deny}]
[--network-proxy IPV4:TCP-PORT]
[--host-loopback-forward PROTOCOL:HOST_PORT:GUEST_PORT]...
[--outcome-report PATH]
[--cmdline TEXT]
[--restore-snapshot PATH]
[--restore-processors {1,2,4,8}]
[--restore-memory-mib MIB]
[--restore-ready-path PATH]
[--dry-run]
| Option | Default | Description |
|---|---|---|
--guest {alpine,ubuntu,azurelinux} |
alpine |
Select Alpine, Ubuntu, or Azure Linux userland with the same NVX kernel. This option is not used for snapshot restore. |
--hypervisor {auto,whp,kvm,mshv} |
auto |
Select the OpenVMM hypervisor. auto chooses WHP on Windows and KVM elsewhere. |
--machine {microvm} |
microvm |
Select the fixed-topology microVM with shared-status edge interrupts. |
--memory-mib MIB |
guest-specific | Set guest memory in MiB. Defaults to 128 for Alpine, 512 for Ubuntu, and 512 for Azure Linux. |
--memory-capacity-mib MIB |
none | Reserve an immutable, 128 MiB-aligned RAM capacity for a fresh microVM snapshot. |
--processors {1,2,4,8} |
1 |
Select the microVM processor count. |
--cpu-profile ID |
auto |
Select the guest's CPU profile: auto for the built-in profile of the host's CPU, which falls back with a warning to a development profile derived from this host on an Intel or AMD CPU that no built-in profile serves, a built-in profile ID, or host for that development profile. A restore uses the snapshot's profile, which auto and, for a host profile, host also name. |
--mount GUEST_TARGET,HOST_PATH[,ro|rw] |
none | Expose a host directory, or a single regular host file without its directory, at the absolute guest target. The mode follows the last comma, so HOST_PATH may contain commas when the mode is given. Repeat to expose more directories or files, each with its own target and mode, as the children of an aggregate on the microVM's one virtio-fs device, which the guest binds at their targets; a shared file always uses the aggregate. Targets and host paths must not overlap, and the shares' kernel command-line tokens must fit its 1024-byte budget. An rw mapping accepts guest-created symbolic links, which the host never follows. Active snapshot restore requires the same mappings in the same order, with the same canonical paths, kinds, targets, modes, and ownership mode; a dormant-slot restore may attach one new directory mapping that the resumed guest mounts explicitly. |
--mount-deny HOST_PATH |
none | Hide one existing file or directory inside a mounted host root; repeat to deny multiple paths. It may name the root itself when --mount-allow paths inside it remain. With several mappings, or a shared file, the path must be absolute; a shared file takes none. |
--mount-allow HOST_PATH |
none | Expose one existing file or directory inside a --mount-deny path again; the hidden directories on the way list only the entries that lead to it. Repeat to allow multiple paths. With several mappings, or a shared file, the path must be absolute; a shared file takes none. See Access policy. |
--mount-write HOST_PATH |
none | Make one existing file or directory one of the only writable parts of an rw mapping; the guest's writes anywhere else in it fail with EROFS. Repeat to declare multiple writable paths. With several mappings, or a shared file, the path must be absolute; a shared file takes none. Active snapshot restore requires the same denied, allowed, and writable paths. |
--mount-owner {vmm,caller} |
vmm |
Select the host identity of the guest's operations on every --mount directory or file. caller performs each one as the guest caller's UID and GID and squashes guest root to the owner of the shared directory or file; it requires a Linux host. See Run. |
--net IPV4/PREFIX |
none | Enable virtio-net with the static guest IPv4 address and prefix. The guest also gets the IPv6 address that embeds it in fd00::/96, such as fd00::a00:2/120 for 10.0.0.2/24. |
--network-profile {portable} |
none | Select the required cross-platform network behavior contract; must be specified with --net. |
--network-egress {allow,deny} |
allow |
Set the default guest egress policy. |
--network-ingress {allow,deny} |
deny |
Set the default host ingress policy. allow publishes every TCP and UDP port on which the guest listens to clients on the host's private networks, and to the host itself only with --host-loopback allow; see Private-network ingress. Snapshot capture and restore reject allow. |
--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]] |
none | Allow matching guest egress; repeat to add rules. CIDR is an IPv4 or IPv6 address or network, and a rule matches only its own family. PROTOCOL is tcp, udp, or icmp and matches every port when PORT is omitted; only tcp and udp take a PORT or an inclusive PORT-PORT range, such as 8000-8010. |
--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]] |
none | Deny matching guest egress; repeat to add rules. Deny rules take precedence. PROTOCOL and PORT select traffic as for --network-egress-allow. |
--network-egress-policy-file PATH |
none | Load bounded IPv4 and IPv6 ranges and rule-local CIDR exclusions from JSON. Requires explicit --network-egress; cannot be mixed with explicit allow/deny rule flags. |
--host-loopback {allow,deny} |
existing mapping | Control access between the guest and host loopback services. Explicit allow is bidirectional: the guest reaches host loopback through its gateway, and every TCP and UDP port on which the guest listens is published on host loopback for the host alone, or with --network-ingress allow admits the host as well; see Host-loopback access. Snapshot capture and restore reject allow. |
--network-proxy IPV4:TCP-PORT |
none | Allow one explicit host TCP proxy endpoint. |
--host-loopback-forward PROTOCOL:HOST_PORT:GUEST_PORT |
none | Publish one TCP or UDP localhost port to the guest; repeat to add forwards. |
--outcome-report PATH |
none | Write a bounded local JSON outcome report. |
--cmdline TEXT |
empty | Append kernel parameters; nvx_* and tsc= tokens are reserved. |
--restore-snapshot PATH |
none | Restore the immutable machine contract and saved state from a snapshot directory. |
--restore-processors {1,2,4,8} |
none | Bring this contiguous processor prefix online before restore readiness. Requires an opt-in microVM snapshot and cannot exceed --processors capacity. |
--restore-memory-mib MIB |
none | Select the 128 MiB-aligned RAM target for an expansion-capable snapshot restore. |
--restore-ready-path PATH |
none | Publish one restore-readiness event to an existing Unix socket or Windows named pipe. |
--dry-run |
off | Print the generated OpenVMM command without running it. |
run runs nvx-rs run (see nvx-rs) with the options given, and
its exit status is the command's: 1 for an error, 2 for a usage error, 130
after Ctrl-C, and otherwise the status that OpenVMM returns. The errors and
usage errors that nvx-rs reports carry their error codes, as
error[CODE]: MESSAGE and nvx.py run: error[CODE]: MESSAGE. nvx-rs checks
the options, prints the OpenVMM command after >> , and unless --dry-run is
given, runs OpenVMM with the standard streams. A cold boot requires the OpenVMM
release binary, build/vmlinux, and the selected build/initramfs*.cpio.gz,
and a restore only OpenVMM. nvx.py parses the options only for its help and
for the usage errors of unknown options, invalid choices of --guest,
--hypervisor, --machine, --mount-owner, and the network options, and
missing values, so it still accepts abbreviated options, which it passes to
nvx-rs in full. nvx-rs checks their values, the integers and processor
counts included, and its usage and messages name nvx.py run; its usage text
wraps at 80 columns whatever the terminal's width. On a command line with two
errors, the one that nvx.py reports wins over a value that nvx-rs would
refuse, even when that value comes first, and so does a request for help.
Ubuntu selection never falls back to Alpine. See Run for host setup,
guest shutdown, networking, and virtio-fs examples.
If no built-in CPU profile serves an Intel or AMD host's CPU, OpenVMM falls
back to a host profile and warns with NVX-CPU-PROFILE-FALLBACK: at every
cold boot, as CPU profiles describes. After a failed cold
boot on such a CPU, run names the CPU, lists the CPUs that the built-in
profiles cover, says that auto falls back on it, and asks for the CPU's
fingerprint through a CPU profile request, with the OpenVMM
command that writes the fingerprint. OpenVMM's
own error, above the guidance, names the cause of the failure, and says so
where the host profile cannot boot. On a CPU of another vendor that no
built-in profile serves, OpenVMM exits with E_PROFILE_HOST_UNKNOWN before it
creates the VM, and run explains that host profiles cannot boot there
either. A host whose hypervisor does not support its built-in profile fails
with E_PROFILE_UNSUPPORTED instead, and on an Intel or AMD CPU OpenVMM's
error itself names --cpu-profile host. nvx.py derives the guidance from its
CPU catalog, and nvx-rs run prints it only after OpenVMM fails a cold boot,
never after a dry run, a restore, an option that nvx-rs refuses, or Ctrl-C.
python3 scripts/nvx.py sandbox
[{run,provision,start,exec,stop,deprovision}]
[--layer ROLE,PATH,EROFS_UUID]...
[--scratch PATH]
[--state-dir PATH]
[--entrypoint PATH]
[--arg VALUE]...
[--hostname NAME]
[--workload-user UID:GID]
[--memory-max BYTES]
[--pids-max COUNT]
[--memory-mib MIB]
[--timeout SECONDS]
[--exec-timeout-ms MILLISECONDS]
[--cwd GUEST_PATH]
[--environment KEY=VALUE]...
[--environment-file PATH]
[--inherit-default-environment]
[--hypervisor {auto,whp,kvm,mshv}]
[--mount GUEST_TARGET,HOST_PATH[,ro|rw]]...
[--mount-deny HOST_PATH]...
[--mount-allow HOST_PATH]...
[--mount-write HOST_PATH]...
[--mount-owner {vmm,caller}]
[--net IPV4/PREFIX]
[--network-profile {portable}]
[--network-egress {allow,deny}]
[--network-ingress {allow,deny}]
[--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]]]...
[--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]]]...
[--network-egress-policy-file PATH]
[--host-loopback {allow,deny}]
[--network-proxy IPV4:TCP-PORT]
[--host-loopback-forward PROTOCOL:HOST_PORT:GUEST_PORT]...
[--outcome-report PATH]
[--cmdline TEXT]
[--dry-run]
Network policy and live-share options configure only the run and provision
launches, which check the network options as one typed policy before anything
launches or is recorded; see Sandbox networking.
sandbox runs nvx-rs sandbox (see nvx-rs) with the operation and
the options given, and its exit status is the command's: 1 for an error, 2 for
a usage error, 130 after Ctrl-C, and otherwise the status that the workload or
OpenVMM returns. The errors and usage errors that nvx-rs reports carry their
error codes, as error[CODE]: MESSAGE and
nvx.py sandbox: error[CODE]: MESSAGE; an exec prints its workload's
standard output and error before any error of its own, which starts a new
line. nvx.py parses the
options only for its help and for the
usage errors of unknown options, invalid choices, and missing values, so it
still accepts abbreviated options, which it passes to nvx-rs in full.
nvx-rs checks their values, and its usage and messages name
nvx.py sandbox; its usage text wraps at 80 columns whatever the terminal's
width. As for run, an error that nvx.py reports, or a request for help,
wins over a value that nvx-rs would refuse, even when that value comes first.
| Option | Default | Description |
|---|---|---|
{run,provision,start,exec,stop,deprovision} |
run |
Select a one-shot run or a managed lifecycle operation. |
--layer ROLE,PATH,EROFS_UUID |
required for run and provision |
Attach the distro EROFS layer. Only distro is accepted. |
--scratch PATH |
required for run and provision |
Attach a preformatted ext4 scratch image as the writable overlay. |
--state-dir PATH |
required for managed operations | Select persistent sandbox state. One-shot run rejects this option. |
--entrypoint PATH |
/bin/sh |
Select an absolute workload entrypoint without whitespace. |
--arg VALUE |
none | Append one whitespace-free entrypoint argument. Repeat to pass multiple arguments. |
--hostname NAME |
nvx-sandbox |
Set the workload UTS hostname. |
--workload-user UID:GID |
65534:65534 |
Select the fixed non-root workload identity for run or provision. |
--memory-max BYTES |
none | Set the workload cgroup memory limit. |
--pids-max COUNT |
none | Set the workload cgroup process limit. |
--memory-mib MIB |
256 |
Set guest memory in MiB. |
--timeout SECONDS |
60 |
Set the timeout for each managed control request and its response in start, exec, and stop, and the longest time that provision, start, stop, and deprovision wait for another lifecycle transition of the sandbox. It must be finite and greater than 0. |
--exec-timeout-ms MILLISECONDS |
0 |
Set the managed exec guest workload timeout in the unsigned 32-bit range 0..4294967295; zero disables the workload deadline. This is separate from the finite host --timeout control deadline. |
--cwd GUEST_PATH |
/ |
Set an absolute working directory inside the workload root for managed exec. It is resolved after the workload identity and root are applied; missing, inaccessible, or non-directory paths fail the workload launch. Default and layered environments point PWD at this directory; an exact replacement environment controls PWD itself. |
--environment KEY=VALUE |
omitted | Set the exact managed exec environment. Repeat for multiple entries. Empty values, spaces, additional equals signs, and UTF-8 are preserved. Inline values are visible in the invoking host process arguments; use --environment-file for sensitive values. |
--environment-file PATH |
omitted | Read the exact managed exec environment from a UTF-8 JSON array of KEY=VALUE strings, limited to 1 MiB of input. An empty array requests an empty environment. This option is mutually exclusive with --environment; omitting both preserves guest defaults. |
--inherit-default-environment |
off | Layer the managed exec environment from --environment or --environment-file over the guest default environment instead of replacing it; an entry replaces the default variable of the same name. Without either option the workload already receives the defaults, so the flag has no effect. |
--hypervisor {auto,whp,kvm,mshv} |
auto |
Select the host hypervisor. |
--mount GUEST_TARGET,HOST_PATH[,ro|rw] |
none | Live-share a host directory, or a single regular host file without its directory, at the absolute target inside the container rootfs for run or provision; defaults to ro. The mode follows the last comma, so HOST_PATH may contain commas when the mode is given. Repeat to attach more shares, each with its own target and mode, for example a read-write workspace and a read-only tool cache, as the children of an aggregate that the guest binds at their targets; a shared file always uses the aggregate, and so does every share of a managed sandbox. Targets and host paths must not overlap. A one-shot run describes its shares in kernel command-line tokens that count toward the sandbox's 1024-byte budget; a managed sandbox announces only their number and sends their mapping table once its guest agent is ready, for at most 256 shares. An rw share accepts guest-created symbolic links, which the host never follows. /, /etc, /run, and /run/nvx, and the /etc/machine-id, /proc, /sys, /dev, /.nvx-agent, and /run/nvx/time trees, are reserved. |
--mount-deny HOST_PATH |
none | Hide one existing file or directory inside a --mount host directory; relative paths are resolved inside it. It may name the directory itself when --mount-allow paths inside it remain. With several shares, it applies to the --mount before it, which must share a directory. Repeat to deny multiple paths. |
--mount-allow HOST_PATH |
none | Expose one existing file or directory inside a --mount-deny path of the same share again, for example a readable mcp-payloads directory inside hidden logs. The hidden directories on the way list only the entries that lead to it, and the workload cannot modify them. Relative paths are resolved inside the share; with several shares, it applies to the --mount before it. Repeat to allow multiple paths. See Access policy. |
--mount-write HOST_PATH |
none | Make one existing file or directory one of the only writable parts of an rw share; the workload's writes anywhere else in the share fail with EROFS. Relative paths are resolved inside the share; with several shares, it applies to the --mount before it, which must be rw. Repeat to declare multiple writable paths. provision persists the denied, allowed, and writable paths. |
--mount-owner {vmm,caller} |
vmm |
Select the host identity of the shares' file operations for run or provision. vmm performs them as OpenVMM; caller performs them as the workload's UID and GID, squashes guest root to the owner of each shared host directory or file, and fails them with EPERM when OpenVMM cannot assume that identity. caller requires a Linux host and is persisted by provision. See File ownership. |
--net IPV4/PREFIX |
none | Enable virtio-net with a static guest address. The guest also gets the IPv6 address that embeds it in fd00::/96. The other network options need it. A policy that lets nothing through, --network-egress deny without allow rules, with ingress denied and --host-loopback deny, attaches no network device. |
--network-profile {portable} |
none | Select the required cross-platform network behavior contract; must be specified with --net. |
--network-egress {allow,deny} |
allow |
Set the default guest egress policy for run or provision. |
--network-ingress {allow,deny} |
deny |
Set the host ingress policy for run or provision. allow publishes every TCP and UDP port on which the workload listens to clients on the host's private networks, and to the host itself only with --host-loopback allow; see Private-network ingress. |
--network-egress-allow CIDR[:PROTOCOL[:PORT[-PORT]]] |
none | Allow matching guest egress; repeat to add rules. Requires explicit --network-egress. CIDR is an IPv4 or IPv6 address or network, and a rule matches only its own family. PROTOCOL is tcp, udp, or icmp and matches every port when PORT is omitted; only tcp and udp take a PORT or an inclusive PORT-PORT range, such as 8000-8010. |
--network-egress-deny CIDR[:PROTOCOL[:PORT[-PORT]]] |
none | Deny matching guest egress; repeat to add rules. Requires explicit --network-egress; deny rules take precedence. PROTOCOL and PORT select traffic as for --network-egress-allow. |
--network-egress-policy-file PATH |
none | Load bounded IPv4 and IPv6 ranges and rule-local CIDR exclusions for run or provision. Managed provision persists the compiled rules in its network policy, not this path. Requires explicit --network-egress; cannot be mixed with explicit allow/deny rule flags. |
--host-loopback {allow,deny} |
guest to host | Control access between the workload and host loopback services for run or provision. Without the option, the workload reaches host loopback through its gateway, subject to the egress policy, and no port is published; deny closes both directions. Explicit allow is bidirectional: the workload reaches host loopback through its gateway, and every TCP and UDP port on which the workload listens is published on host loopback for the host alone, or with --network-ingress allow admits the host as well; see Host-loopback access. |
--network-proxy IPV4:TCP-PORT |
none | Make the network proxy-only: the workload reaches the HTTP or HTTPS proxy that listens on host 127.0.0.1:TCP-PORT at IPV4:TCP-PORT, where IPV4 must be the guest gateway, and nothing else. TCP-PORT can't be 53, where OpenVMM answers DNS at the gateway. Requires --network-egress deny and --host-loopback deny, and refuses egress rules, --network-ingress allow, and forwards. NVX gives the workload no proxy settings; see Proxy-only networking. |
--host-loopback-forward PROTOCOL:HOST_PORT:GUEST_PORT |
none | Publish one TCP or UDP localhost port to the guest; repeat to add forwards. Requires --host-loopback allow. A UDP forward also requires egress allowed by default and no deny rule that covers the guest network, which carries its replies; OpenVMM lets the replies on a forwarded TCP connection through whatever the egress policy. |
--outcome-report PATH |
none | Write a bounded local JSON outcome report for one-shot run or managed exec. |
--cmdline TEXT |
empty | Append non-sandbox kernel parameters; nvx_*, virtfs_*, and tsc= tokens are reserved. |
--dry-run |
off | For one-shot run only, print the generated OpenVMM microVM command without running it. Managed operations reject this option. |
See Run for artifact preparation, the security boundary, and current snapshot/configuration limitations.
Every microVM presents a CPU profile to its guest: the complete CPUID surface of one CPU generation, which OpenVMM pins and every backend shares (see the CPU profile design). By default, OpenVMM selects the built-in profile of the host's CPU generation:
| Profile | Generation | CPUs (display family/model) |
|---|---|---|
intel.skylake-sp.v1 |
skylake-sp |
Intel Xeon Scalable, first generation (6/85, steppings 0 to 4) |
intel.icelake-sp.v1 |
icelake-sp |
Intel Xeon Scalable, third generation (6/106) |
intel.emeraldrapids.v1 |
emeraldrapids |
Intel Xeon Scalable, fifth generation (6/207) |
intel.alderlake.v1 |
alderlake |
Intel Core, twelfth generation (6/151 and 6/154) |
amd.milan.v1 |
milan |
AMD EPYC, third generation (25/1, Milan-X included) |
amd.genoa.v1 |
genoa |
AMD EPYC, fourth generation (25/17, Genoa-X included) |
amd.turin.v1 |
turin |
AMD EPYC, fifth generation (26/2) |
Any other CPU, including Cascade Lake, Sapphire Rapids, Granite Rapids, Tiger
Lake, Raptor Lake, and AMD's Ryzen CPUs, has no built-in profile: on an Intel
or AMD CPU, --cpu-profile auto falls back to a host profile, described
below, and on another vendor's CPU, it fails with E_PROFILE_HOST_UNKNOWN.
A host of a listed generation can still fail with E_PROFILE_UNSUPPORTED if
its SKU or hypervisor lacks a feature of the profile, and on an Intel or AMD
CPU OpenVMM's error then names --cpu-profile host. intel.alderlake.v1
derives from one Core i9-12900H on WHP, amd.milan.v1 from one EPYC 7763
Azure VM on WHP, and amd.genoa.v1 and amd.turin.v1 from KVM on one
GitHub-hosted Actions runner each, an Azure VM with an EPYC 9V74 or 9V45.
GitHub's runners with an EPYC 7763, 9V74, or 9V45 pass their profiles' full
test-microvm suite on KVM. Alder Lake hosts on KVM and MSHV, Milan, Genoa,
and Turin hosts on MSHV, Genoa and Turin hosts on WHP, bare-metal AMD hosts,
and SKUs without the profiles' features are unverified. The AMD profiles leave
out what KVM cannot present, BTC_NO and PSFD without a SPEC_CTRL control,
and the enumerations of Intel's speculation controls that KVM adds on AMD
hosts. They present none of AMD's speculation controls, IBRS, STIBP, and
SSBD, because the Azure VMs' hypervisors withhold them; amd.milan.v1's
Linux guests therefore use retpolines and report SSB, SRSO, and TSA as
vulnerable on every host. amd.genoa.v1 pins the TSA immunities that Azure
presents on Genoa, which a bare-metal Genoa host's KVM does not report.
On an Intel or AMD development host that no built-in profile serves,
--cpu-profile auto falls back to a host profile, intel.host.v1 or
amd.host.v1, which run --cpu-profile host also selects, for example where
the hypervisor does not support the built-in profile. OpenVMM fingerprints the
hypervisor on this host and applies the built-in profiles' derivation policy
to it, so the guest sees the same kind of filtered CPU surface, and verifies
it as it verifies a built-in profile: a cold boot still fails with
E_PROFILE_UNSUPPORTED if the hypervisor lacks a CPU feature that the time
ABI requires. Where auto fell back, the error also says so; with an explicit
--cpu-profile host, it reports the failure alone. A host profile is for
development only:
- It is not pinned: a microcode, firmware, hypervisor, or OS update can change it.
- Each cold boot fingerprints the hypervisor first, which #394 measured at 2.6 to 5.5 ms on WHP, 11 to 23 ms on KVM, and 52 ms on MSHV. The cost varies with the host, and the fallback's warning reports what each cold boot's fingerprint took.
- A snapshot records its host profile, and restores only on a host with the same CPU model and stepping whose hypervisor supports the profile.
doctornever qualifies it, so CI hosts need a built-in profile, andbenchmarkrefuses a CPU that no built-in profile serves.
Every cold boot that falls back logs one OpenVMM warning, at the default log
level, that starts with NVX-CPU-PROFILE-FALLBACK: and names the CPU, the
host profile with its digest, and these limits, with what that cold boot's
fingerprint took. It also asks you to help add
a built-in profile for the CPU: run
openvmm --hypervisor <backend> --cpu-fingerprint fingerprint.json, which
reports E_PROFILE_HOST_UNKNOWN on such a CPU but still writes the
fingerprint, and attach fingerprint.json to a
CPU profile request,
whose link in the warning prefills the CPU. The fingerprint holds the CPU's
identity and microcode, the OS kind and build, the hypervisor's interface, and
the CPUID, XSAVE, MSR, and time surface that the backend supports; it holds no
hostname, user name, or serial number. Fingerprints of the generation from
other backends help too, because a profile is the intersection of its
backends' fingerprints and released profiles are immutable. An explicit
--cpu-profile host warns too, with the same request where no built-in
profile serves the CPU.
#408 tracks built-in profiles
for more Intel CPUs, such as Tiger Lake and Meteor Lake, and
#409 for more AMD CPUs, and
maintainers link each CPU profile request to one of them; host profiles serve
no CPU of another vendor than Intel and AMD. A profile derives from
fingerprints of its generation's hosts on every backend that it serves; see
vmm_core/cpu_profile in the OpenVMM submodule.
python3 scripts/nvx.py benchmark [OPTIONS]
| Option | Default | Description |
|---|---|---|
--suite {boot,snapshot,restore,e2e,phase2,snapshot-profile,all,cold-start,device-io,device-restore-profile,network-snapshot,performance,shell-snapshot,shell-snapshot-restore,snapshot-restore-memory,snapshot-restore-vcpu,virtfs} |
boot |
Select an acceptance, diagnostic, or workload suite. |
--backend {whp,kvm,mshv,both} |
both on Windows; kvm elsewhere |
Select the hypervisor backend. |
--platform NAME |
inferred OS/backend | Record the host-typed performance series. |
--openvmm-dir PATH |
openvmm/ |
Select the OpenVMM repository. |
--nvx-dir PATH |
repository root | Select the NVX repository containing guest artifacts. |
--warmups N |
5 for device-io; 1 for device-restore-profile; 3 otherwise |
Set the number of excluded warmup attempts; zero is allowed. |
--runs N |
30 for device-io; 5 for device-restore-profile; 11 otherwise |
Set the number of retained attempts. |
--memory-mib MIB |
128 |
Set guest memory for the general suites. |
--processors {1,2,4,8} |
1 |
Run every cold, capture, restore, and workload launch with this microVM count. |
--virtfs-runs N |
3 |
Set the number of virtio-fs workload samples. |
--virtfs-memory-mib MIB |
512 |
Set guest memory for the virtio-fs workload. |
--payload-mib MIB |
64 |
Set the virtio-fs sequential I/O payload size. |
--shell-memories MIB [MIB ...] |
128 256 512 (128 256 512 1024 for snapshot-profile) |
Set the guest memory sizes for shell snapshot measurements. |
--network-memory-mib MIB |
256 |
Set guest memory for the network snapshot workload. |
--restore-devices {console,net,virtiofs} [...] |
all three devices | Select devices for the device-restore-profile suite. |
--restore-modes {active,deferred} [...] |
both modes | Select activation modes for the device-restore-profile suite. |
--device-io-duration-seconds SECONDS |
10 |
Set each storage-operation or UDP round-trip measurement window. |
--device-io-size-mib MIB |
512 |
Set the virtio-blk and virtio-fs backing-object size. |
--device-io-port PORT |
5201 |
Set the same-host UDP echo port. |
--net IPV4/PREFIX |
none | Enable virtio-net with a static guest address. |
--network-profile {portable} |
none | Required with --net; selects the portable KVM/MSHV/WHP contract. |
--cpus CPUSET |
one logical CPU per physical core | Set process affinity in taskset syntax. |
--host-cpu-reserve N |
2 |
Require this many affinity CPUs beyond the guest vCPU count for VMM/device work. |
--timeout SECONDS |
10 |
Set the time allowed for each boot marker. |
--teardown-mode {guest-exit,host-terminate,host-sigterm} |
guest-exit |
Select how to stop a measured VM; host-sigterm is a deprecated alias. |
--skip-build |
off | Reuse existing release binaries. |
--snapshot-profile |
off | Retain OpenVMM lifecycle phase samples and host counters, and on Windows sample storage, CPU, writeback, and Defender telemetry during each capture; implied by the snapshot-profile suite. |
--cache-state {warm,cold,both} |
both |
Select artifact cache states for the snapshot-profile suite. |
--output PATH |
none | Write the benchmark result as JSON. |
--output-dir PATH |
none | Write canonical workload logs to a directory. |
--scratch-dir PATH |
system temporary directory | Select an existing directory for temporary snapshots, guest RAM backing, and workload files. |
--keep-kvm-stage |
off | Keep temporary staged KVM benchmark binaries. |
Measured counts must be at least 1; warmups may be zero, and timeouts must be greater than zero.
Every suite but phase2, which boots no microVM, refuses a host before it
builds or measures anything if no built-in CPU profile of the
--openvmm-dir checkout serves its CPU, or if it cannot identify the CPU or
read that checkout's profiles and so cannot confirm that one does.
On such an Intel or AMD CPU, OpenVMM's default CPU profile selection, auto,
falls back to a host profile, whose every cold boot fingerprints the
hypervisor first and whose results no baseline shares, and the refusal names
the CPU and links a CPU profile request. On another vendor's CPU, every cold
boot fails with E_PROFILE_HOST_UNKNOWN, as it does on a CPU that profiles of
more than one of the checkout's generations cover, which benchmark refuses
too: auto rejects such a catalog without falling back.
The e2e suite uses the general memory size and measures cold start, snapshot
generation, snapshot restore, guest-exit teardown, and peak RSS against the
shell-ready markers. CI uses the default 128 MiB baseline.
JSON results and workload metadata record the benchmark host's provenance under
host, without credentials or Azure resource identifiers.
See Benchmark for suite semantics, platform support, metric
definitions, and complete examples.
performance processes benchmark outputs for CI. It requires one nested
command.
python3 scripts/nvx.py performance collect
--platform PLATFORM
--commit COMMIT
--input-dir PATH
--output-dir PATH
[--require-network]
[--require-shell-snapshot]
[--require-shared-suite]
[--require-shell-snapshot-restore-512]
[--lifecycle-input PATH]
[--lifecycle-attempt-log PATH]
[--summary PATH]
Parses canonical benchmark logs into p50 CSV files. Each row records --commit
as a full commit ID, resolving a revision such as HEAD in the NVX checkout.
The --require-* flags reject incomplete inputs for their respective workload
sets.
--require-shell-snapshot-restore-512 accepts only the canonical 512 MiB
restore metric from a 2-, 4-, or 8-vCPU run.
--lifecycle-input validates and merges a 128 MiB, guest-exit e2e JSON
result, producing the 29-metric microVM CI result. A directory whose metadata
selects device-io is collected as five additional ABI-2, one-vCPU ops/s
metrics; CI merges them into a 34-metric one-vCPU result.
--lifecycle-attempt-log reads the attempt log that validate-openvmm
records. The --lifecycle-input file must be a byte-identical copy of exactly
one valid logged attempt, or collection fails before it writes a CSV; the
command names that attempt.
--summary writes the p50 table plus lifecycle min/max/sample-count and RSS
diagnostics, the benchmark host provenance, and any snapshot-generation storage
telemetry. With --lifecycle-attempt-log, it also lists every attempt, marks
the published one, and reports the telemetry of rejected attempts.
When the inputs record the benchmark host, the command also writes it to
--output-dir as <platform>-host.json, the format of an
expected benchmark host. It fails if
only some inputs record a host, if the inputs, or a host file already in
--output-dir, record another host, and if --output-dir would mix results
that record a host with results that record none.
python3 scripts/nvx.py performance validate-openvmm
--platform PLATFORM
--input PATH
[--attempt-log PATH]
Validates a complete 128 MiB, guest-exit OpenVMM e2e result without
writing a CSV. Snapshot-generation instability exits with status 75 so
callers can remeasure the temporary host condition selectively; other
malformed or incomplete inputs exit with status 2. --attempt-log appends
the input's path, SHA-256 digest, outcome (valid, unstable, or
invalid), exit status, and error to a JSON attempt log, creating it on the
first attempt.
python3 scripts/nvx.py performance collect-openvmm
--platform PLATFORM
--commit COMMIT
--input PATH
--output-dir PATH
[--summary PATH]
Converts a complete 128 MiB, guest-exit OpenVMM e2e result into an
eight-metric lifecycle p50 CSV. Like performance collect, it records --commit
as a full commit ID and writes the recorded benchmark host to --output-dir as
<platform>-host.json.
python3 scripts/nvx.py performance gate
--baseline-dir PATH
--target-dir PATH
[--window N]
[--minimum-history N]
[--threshold PERCENT]
[--absolute-tolerance-ms MILLISECONDS]
[--history-reset-dir PATH]
[--summary PATH]
Checks target p50 values for regressions against rolling baseline histories.
--window defaults to 10, --minimum-history to 10, --threshold to
40, and --absolute-tolerance-ms to 5. The gate uses the median of the
available window and treats metrics with insufficient history as warmups.
--summary writes a Markdown summary.
--history-reset-dir names tracked candidate histories; metrics removed from
an existing history file restart baseline warmup. When a candidate expected
host, <platform>-host.json in --history-reset-dir, differs from the one in
--baseline-dir in a required property, the gate fails unless the candidate
history files keep no row of that series, of any metric.
python3 scripts/nvx.py performance persist
--source-dir PATH
--history-dir PATH
[--exclude-metric NAME]...
[--require-host]
Appends current p50 values to branch history. Repeat --exclude-metric to omit
more than one metric. When both directories contain a platform's host file,
<platform>-host.json, the command refuses to append that platform's results
if their recorded host differs from the history's in a required property, and
warns about advisory differences. It also refuses results without a host file
for a history that has one. --require-host requires both files for every
platform. When --history-dir lies in a git checkout, even before the
directory exists, the command refuses results whose commit is not a full
commit ID or not an ancestor of HEAD, and skips results measured before a
later commit removed rows of their metric from the history file, as a reset
does; see CI collection.
python3 scripts/nvx.py performance check-host
--platform PLATFORM
--history-dir PATH
[--scratch-dir PATH]
[--output PATH]
[--summary PATH]
Compares this host with the expected host of the PLATFORM series,
<platform>-host.json in --history-dir, as CI does before it measures the
series; see Expected benchmark hosts.
The command exits with status 1 when a required property, such as the CPU, the
VM size, the OS build, or a benchmark disk or volume, differs, and with status
2 when the expected host is missing or malformed. An advisory difference, such
as the microcode revision or a Windows cumulative update, only warns. The
command prints every compared property, and reports the required and the
advisory differences as one GitHub Actions error and one warning annotation.
--scratch-dir names the benchmark scratch directory whose volume the
benchmarks will use; it defaults to the system temporary directory. --output
writes this host in the host-file format, ready to replace the expected host,
and --summary appends a Markdown comparison that includes it.
python3 scripts/nvx.py collect-sourcesMaterializes the verified Linux, Alpine, and Ubuntu source artifacts needed for a source-inclusive release. Azure Linux corresponding source is not collected, so source-inclusive packages omit the Azure Linux guest.
python3 scripts/nvx.py collect-alpine-sources MANIFEST [MANIFEST ...]
[--output PATH]
[--cache PATH]
[--skip-upstream]
| Option | Default | Description |
|---|---|---|
MANIFEST |
required | One or more Alpine package manifests to collect. |
--output PATH |
build/sources/alpine |
Select the output directory. |
--cache PATH |
.cache/aports |
Select the aports cache directory. |
--skip-upstream |
off | Collect exact aports recipes without running abuild fetch. |
Each run replaces the generated recipes, upstream, manifest.json, and
SHA256SUMS entries. The collector rejects an output path that is a symlink or
a Windows junction, and an output directory that contains anything else.
Release checksum manifests reject symlinks, so a recipe symlink is stored as a
regular copy of its target. Links that leave the recipe, hard links, and
special files are rejected.
python3 scripts/nvx.py collect-ubuntu-sources MANIFEST [MANIFEST ...]
[--output PATH]
[--cache PATH]
| Option | Default | Description |
|---|---|---|
MANIFEST |
required | One or more Ubuntu package or EROFS manifests to collect. |
--output PATH |
build/sources/ubuntu |
Select the output directory. |
--cache PATH |
.cache/ubuntu-source-indexes |
Select the downloaded source-index cache. |
The collector requires gpgv, deduplicates source package name/version pairs,
authenticates live or historical Ubuntu source indexes through signed
InRelease metadata and the pinned Ubuntu archive keyring, validates each
.dsc, and downloads every referenced source member.
python3 scripts/nvx.py create-linux-source-archive
--config PATH
--output PATH
Creates the deterministic Linux corresponding-source archive from the pinned
kernel inputs and the generated kernel configuration. --config must name an
existing generated kernel configuration, and --output names the archive to
create.
python3 scripts/nvx.py package
[--version VERSION]
[--destination PATH]
(--include-source | --binary-only)
[--force]
| Option | Description |
|---|---|
--version VERSION |
Override the packaged version. |
--destination PATH |
Override the staging destination. |
--include-source |
Include the corresponding source artifacts in the package and omit the Azure Linux guest artifacts. |
--binary-only |
Stage binaries only; publish corresponding source separately. |
--force |
Replace an existing staging destination. |
Exactly one of --include-source and --binary-only is required. Every
package ships nvx-rs with its provenance, which must describe
nvx-rs/target/release/nvx-rs[.exe] as built from the checkout's sources
for the release target of the host's operating system:
x86_64-unknown-linux-musl, a static executable, on Linux, and
x86_64-pc-windows-msvc on Windows; see
record-nvx-rs-provenance. See
Package and source delivery for release procedures and
source-publication requirements.
python3 scripts/nvx.py archive-release
--source PATH
--destination PATH
Validates the staged distribution against its SHA256SUMS, snapshots the
accepted inventory, and creates a deterministic archive at the destination.
The destination must be outside the source directory and end in .tar.gz or
.zip.