Repository navigation
nvx-rs: download releases, and fetch one for a checkout that has none - #934
Conversation
The quick start needed Python only to install a development release
with nvx.py download. nvx-rs download now does it. It walks up to 20
first-parent commits of the checkout, reads each commit's VERSION, and
looks for the development release v{VERSION}-dev.{commit[..12]} that
holds the package of the host's platform. It checks the package's
SHA256SUMS, then installs OpenVMM, the guest artifacts that
SOURCE-MANIFEST.json declares, and their provenance as one transaction:
it stages each file beside its destination, sets aside each file that it
replaces, and puts them all back if any step fails.
So that the crate takes no new dependency, the command drives the
host's git, curl and tar, which the bundled feature's build script
already uses; on Windows, it runs those of System32. It hands curl its
configuration, and so the token of GH_TOKEN or GITHUB_TOKEN, on stdin.
It follows redirects itself, sends the token to the API's origin only,
and retries without the token when GitHub refuses it.
nvx-rs run, sandbox run and sandbox start download the release when a
Git checkout misses an artifact that they need and holds none of the
files that a release installs. When it holds some of them, which may be
local builds, they install nothing and warn instead, so that no release
mixes with built artifacts; a download that sees such a file appear
while it runs cancels, puts everything back and warns too. A lock in
build/ makes concurrent commands download once. NVX_AUTO_DOWNLOAD=0
turns this off, and the microVM test workflow sets it so that CI runs
only the artifacts that it built.
nvx.py download goes, with its release code and tests, and the
README's quick start is now: clone, build nvx-rs, and run.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved installation races can overwrite locally built artifacts or expose incomplete artifacts to concurrent launches.
2 open issues
What changed in this PR
Moves release downloading from Python into nvx-rs, enabling a clone–build–run quick start without Python and automatic artifact preparation for empty checkouts.
Changes:
- Adds release discovery, authenticated downloads, archive verification, and staged installation with rollback.
- Integrates automatic downloads into VM and sandbox launches, adds tests, and disables automatic downloads in microVM CI.
- Removes the Python download command and updates documentation, diagnostics, and contributor guidance.
| File | Description |
|---|---|
| tests/runtime-adapter/src/runtime.rs | Updates the download command in module documentation. |
| tests/runtime-adapter/README.md | Updates artifact preparation instructions. |
| scripts/publish_development_release.py | Clarifies packaged executable roles in a docstring. |
| scripts/nvx.py | Removes the Python download command and options. |
| scripts/nvx_tools/release.py | Removes Python release download and installation logic. |
| scripts/nvx_tools/nvx_rs.py | Updates executable discovery documentation and missing-Cargo guidance. |
| README.md | Replaces the quick start with Cargo build and automatic download. |
| nvx-rs/tests/cli/usage.rs | Tests download argument errors and help. |
| nvx-rs/tests/cli/harness.rs | Clears download-related environment variables. |
| nvx-rs/tests/cli/download.rs | Adds network-free download CLI scenarios. |
| nvx-rs/tests/cli.rs | Registers download scenarios and updates expected messages. |
| nvx-rs/src/host/mod.rs | Documents the CLI’s download capability. |
| nvx-rs/src/host/cli/sandbox/mod.rs | Prepares missing artifacts before sandbox runs. |
| nvx-rs/src/host/cli/sandbox/lifecycle.rs | Prepares artifacts before managed sandbox starts. |
| nvx-rs/src/host/cli/run.rs | Integrates automatic downloads into VM runs. |
| nvx-rs/src/host/cli/mod.rs | Registers the download command and environment help. |
| nvx-rs/src/host/cli/download/tests.rs | Tests release selection, download eligibility, staging, and locking. |
| nvx-rs/src/host/cli/download/install/tests.rs | Tests installation layouts, rollback, and conflicts. |
| nvx-rs/src/host/cli/download/install.rs | Implements staged artifact installation and rollback. |
| nvx-rs/src/host/cli/download/github/tests.rs | Tests HTTP requests, credentials, redirects, and errors. |
| nvx-rs/src/host/cli/download/github.rs | Implements GitHub release access through curl. |
| nvx-rs/src/host/cli/download/git.rs | Reads checkout history and per-commit versions. |
| nvx-rs/src/host/cli/download/archive/tests.rs | Tests archive paths, extraction, and checksums. |
| nvx-rs/src/host/cli/download/archive.rs | Validates and extracts release packages. |
| nvx-rs/src/host/cli/download.rs | Coordinates explicit and automatic release downloads. |
| nvx-rs/src/host/artifacts/tests.rs | Updates the expected missing-provenance guidance. |
| nvx-rs/src/host/artifacts/mod.rs | Exposes the build-directory constant and updates documentation. |
| nvx-rs/src/host/artifacts/identity.rs | Points recovery guidance to nvx-rs download. |
| nvx-rs/src/host/artifacts/dir.rs | Updates artifact layout and provenance documentation. |
| nvx-rs/README.md | Documents download behavior and checkout preparation. |
| nvx-rs/Cargo.toml | Updates the CLI feature comment; dependencies are unchanged. |
| doc/usage.md | Moves download reference documentation to the Rust CLI. |
| doc/setup.md | Revises build and download prerequisites. |
| doc/run.md | Updates first-run preparation and provenance guidance. |
| doc/project-structure.md | Updates CLI and release-module descriptions. |
| doc/guests/ubuntu-guest.md | Names the Rust download command. |
| doc/guests/azurelinux-guest.md | Names the Rust download command. |
| doc/distribution.md | Documents installation without replacing nvx-rs. |
| doc/ci.md | Documents CI download controls and package roles. |
| doc/adversarial-testing.md | Removes obsolete executable-installation guidance. |
| .github/workflows/run-nvx-microvm-tests.yml | Disables automatic release downloads during microVM tests. |
| .github/skills/nvx-run/SKILL.md | Updates build and artifact preparation guidance. |
| .github/skills/nvx-host-connect/SKILL.md | Updates platform-specific preparation commands. |
| .github/skills/nvx-debug/SKILL.md | Updates missing-artifact guidance. |
| .github/copilot-instructions.md | Explains automatic downloads and source-change validation. |
| .github/actions/package-release/action.yml | Updates a packaging comment without changing action behavior. |
Review effort: Balanced
Review found two races in the automatic download. A command that runs the artifacts of a checkout decided whether to download without the download lock, so it could read the checkout in the middle of another download's installation. It then warned of a kernel that the download had yet to install, and failed, or ran artifacts whose provenance the download had yet to install, which a managed start reads. The command now decides under the lock, taken shared: commands that decide do not wait for each other, while a download, which takes the lock exclusively, waits for them, and they for it. A command creates no lock file in a checkout into which nothing downloaded: it decides without one there, and decides again under the lock if the file appeared meanwhile, as a download creates it before it installs anything. A root that is no Git checkout takes no lock, as before. A wait is reported once it outlasts a retry interval, which a decision takes far less than, so that a download does not mistake a command that decides for another download. An installation of Mode::Fresh checked that each destination was free and then renamed the package's file there, so a file that a local build published in between was replaced, with nothing to restore. It now links the file into place, which fails where a file exists: such a file cancels the installation as one that appeared earlier does. The staged names of the linked files go once every file is in place. Regression tests pause a download between installation steps while a command waits for it, and publish a kernel between the check and the installation; each fails without its fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
92f3c0f to
2663845
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Artifact locking and rollback races can mix runtime files or delete concurrent build outputs, and incomplete validation allows invalid commands to trigger installation.
3 open issues
2 resolved since last review
Review effort: Balanced
…' files The second review found three more gaps. A command released the download lock as soon as it decided, before it read the artifacts and their provenance. An explicit download could still replace them during those reads, failing the command on a missing artifact, or having sandbox start record a runtime identity that did not match the artifacts that it launched. download::ensure now returns a Hold, which keeps the lock until the command has launched OpenVMM: run and sandbox run drop it once OpenVMM has started, and sandbox start once its VM is ready, by which time OpenVMM has read the artifacts. A command therefore creates the lock file, and build/, where it may, as a download does, instead of deciding without the lock in a checkout that has no lock file. Where it may not, as in a read-only checkout, it decides without the lock. With NVX_AUTO_DOWNLOAD at 0 it takes no lock, so CI's commands, which run the artifacts that CI built, create nothing. A download that waits reports that it waits for another nvx-rs command, since that may be a command that launches. The rollback of a fresh installation removed every file that it had put in place, including one that a local build had since rewritten in place, as shutil.copy2 does, or replaced, before a later file of the build cancelled the installation. It now removes only the files that still hold the package's content, by the digest that the package's SHA256SUMS lists, which plan now reads. run downloaded before it checked its share and network options, so a command with options that it refuses downloaded a release, or failed offline with the download's error. It now checks those options first and downloads only if they pass. It still reports a missing artifact before them, as the validation tests pin. A scenario test has sandbox start keep a download out until its VM, whose readiness the fake OpenVMM delays, is ready. Other tests pin the rollback around a rewritten and a replaced kernel, a run that downloads nothing for options that it refuses, and a package whose SHA256SUMS does not list a file that it installs. The tests of the start, the rollback, and the run fail without their fixes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ties The third review found two more gaps. sandbox exec, stop, and deprovision read the runtime identity that the artifacts' provenance names, to find the sandbox's VM, without the download lock. A download that replaced the provenance meanwhile, even one of the same release, could fail a stop on missing provenance instead of shutting the VM down. These operations now hold the lock shared while they find the VM, as download::hold takes it: in a Git checkout that may download, after any download that holds the lock, and without downloading anything. A start already holds the lock from its own decision at that point. sandbox run reached the download before it checked its command line, so a run with nvx_sandbox=0, or with a command line over the budget, downloaded a release or failed on the network. It now renders the shares and the kernel command line of its plan first, and downloads only if they pass. It still reports a missing artifact before them. One scenario test has a stop wait for a download that holds the lock. Another has sandbox run, like run, download nothing for options that it refuses. Both fail without their fixes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The fourth review found three more gaps. A replacing installation moved whatever stood at the path of a release file aside, directories included, and its cleanup then deleted such a directory with everything in it. A directory there now fails the installation, which restores the checkout, and the cleanup no longer removes a directory that is not empty. sandbox exec, stop, and deprovision took the download lock before they knew whether the sandbox had recorded a launch, the only case that needs the artifacts. A deprovision of a sandbox that never started therefore failed where nvx-rs could find no artifact root, which it did not before. These operations now take the lock only to read the runtime identity of a recorded launch. sandbox run read its distro layer, which may be the release's own ubuntu-distro.erofs, before it took the lock. A download that replaced the layer meanwhile failed the run at the check of the layer's binding. A one-shot run now takes the shared lock before it reads the layer, and decides whether to download under that lock with download::ensure_under, which drops it before taking the lock exclusively to download. A failure to take the lock is reported where the run decides, after its options are checked, as before. Each fix has a test that fails without it: a replacement over a directory, a deprovision from an nvx-rs outside any root, and a run that waits for a download that replaces the packaged layer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ision The fifth review found two more gaps. run built its share and network arguments before the download, so that it would download nothing for options that it refuses, and then launched with those arguments. But a share's kind depends on what its host path holds, so a release file that the download installed was taken for a directory and shared as one. run now only checks those options before the download, and builds their arguments where it always did, once the artifacts are in place. A command whose options it refuses also reads the artifacts under the download lock, without downloading. sandbox provision read its distro layer, which may be the release's, and the layer's manifest without the lock, so a download that replaced them meanwhile could fail a valid provision. A provision now holds the lock while it reads the layer and records the sandbox, wherever it can take the lock: it runs no artifact, so it still needs no artifact root. One scenario test has a provision wait for a download that replaces the packaged layer, then starts the sandbox on the layer that it recorded. Another provisions and deprovisions a sandbox from an nvx-rs outside any root. Each fails without its fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The sixth review found a gap in sandbox provision. It holds the download lock where it can take it, and goes on without the lock otherwise. It also went on when Ctrl-C ended its wait for a download that held the lock, so it created the sandbox while it exited as interrupted. A provision now ends as interrupted at once. So does a one-shot run, which used to read the layer before it reported the interruption. A Unix test interrupts a provision that waits for a download, and checks that it exits with status 130 and records no sandbox. The test fails without the fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-platform installation rollback, credential handling, and lifecycle synchronization warrant human review, with real-boot validation still deferred to CI.
No open issues 🎉
1 resolved since last review
Review effort: Balanced


Folds
nvx.py downloadinto thenvx-rsCLI, so that the quick start needs no Python: clone the repository, buildnvx-rswith Cargo, and run it. Whennvx-rs run,sandbox run, orsandbox startfinds no artifacts in a checkout, it first downloads the release that CI built from the checkout.What changed
nvx-rs download(download.rs) installs the development release of the checkout, asnvx.py downloaddid. It walks up to 20 first-parent commits, reads each commit'sVERSION, and takes the first releasev<VERSION>-dev.<commit[..12]>that publishes the package of the host's platform (--hypervisor,--repository). It checks the package's member names andSHA256SUMS, then installs OpenVMM intoopenvmm/target/release/, and the guest artifacts thatSOURCE-MANIFEST.jsondeclares, with their provenance, intobuild/: all of them or none. Each file is staged beside its destination, the files it replaces are set aside, and a failure puts them back. It never touchesnvx-rsitself.git,curl, andtar, as thebundledfeature's build script already does; on Windows, thecurl.exeandtar.exeof System32. The curl configuration, and soGH_TOKENorGITHUB_TOKEN, goes through stdin, never the command line.nvx-rsfollows redirects itself and sends the token to the API's origin only; a token that GitHub refuses falls back to anonymous access.run,sandbox run, andsandbox startdownload the release when an artifact that they need is missing, the root is a Git checkout with itsSOURCE-MANIFEST.json, and none of the files that a release installs exist. When some exist, which may be local builds, they install nothing and warn instead, so a release never mixes with built artifacts. A lock inbuild/makes concurrent commands download once. Every command also waits for a download's installation before it checks the checkout, and keeps downloads out until it has launched OpenVMM. A download that sees one of those files appear, even while it puts its files in place, rolls back and warns.NVX_AUTO_DOWNLOAD=0turns it off;run-nvx-microvm-tests.ymlsets it, so that CI only ever runs the artifacts that it built.nvx.py download, its release code inrelease.py, and its tests go.doc/usage.mdandnvx-rs/README.mddocumentnvx-rs downloadand the automatic download, and the docs, skills, and messages that pointed tonvx.py downloadnow point tonvx-rs download.sandbox runtakes it before it reads its distro layer, andsandbox startkeeps it until its VM is ready. So a command never sees part of an installation, and no download replaces the files it reads.sandbox provisionshares the lock while it reads its layer and records the sandbox.sandbox exec,stop, anddeprovisionshare it while they read the runtime identity of a recorded launch. None of these downloads anything, or needs an artifact root that it did not need before.NVX_AUTO_DOWNLOAD=0, commands take no lock.fs::hard_link, so a file that a local build publishes at the last moment cancels the installation instead of being replaced.SHA256SUMSdigest.runandsandbox rundownload nothing for options that they refuse.runbuilds its share arguments once the artifacts are in place.Notes for reviewers
>> COMMANDline ofrunstays alone on standard output.run --restore-snapshot) needs only OpenVMM, and an OpenVMM thatNVX_OPENVMMnames is the caller's to provide.downloadrefuses the root of an extracted release package; it only installs into checkouts.unavailable, and anNVX_AUTO_DOWNLOADother than empty,0, or1isunsupported..git, and the download scenarios use a.gitthatgitrejects. The unit tests run the realtar, and the realcurlagainst loopback servers, to check that the token does not follow a cross-origin redirect.Validation
dev: thecheck-nvx-rssteps (rustfmt, Clippy with-D warningsfor every feature set, the tests, rustdoc with-D warnings, the MSRV check with Rust 1.89, theaarch64-apple-darwincheck, and the runtime adapter's configurations). In the full run, the CLI scenariolifecycle::refusals_of_lifecycle_operationsonce missed its 0.5 s window for a fake OpenVMM that exits at start, and passed on its rerun; that window opens after the launch, which this change does not touch.dev:check-quality's Ruff, Pyright for Linux and Windows, and link check, andvalidate-nvx's unit tests (1,345),test_adversarial.py,test_hosts.py, andnvx.pyhelp commands. The only failures are fivetest_setup_azure.pytests that run the Linux setup scripts withbash, which is WSL's on this host; this change touches neither.-D warningsfor all targets, and the CLI download and interrupt scenarios, at the head.run --dry-runin a fresh checkout downloaded and installed the release of itsHEAD(142 MiB), with matching provenance digests; a partial checkout got the warning;downloadreplaced a partial checkout; and two concurrentruns downloaded once. In a Linux clone with a local commit,runfell back to the parent's release and installed OpenVMM with mode 0755, andNVX_AUTO_DOWNLOAD=0disabled the download.