Release packaging

August 10, 2026 · View on GitHub

These scripts assemble the application update package that NanoKVM devices pull over the air: nanokvm_<version>.tar.gz plus its latest.json manifest.

ScriptPurpose
build-in-container.shBuilds every riscv64 artifact (kvm_system, libkvm.so, NanoKVM-Server). Runs inside the nanokvm-builder image only.
package.shStages the package tree, creates the tarball, and writes latest.json.
compare-release.shDiffs a freshly built package against the currently published one. Informational.
verify-release-assets.shChecks the three GitHub Release assets before publishing or promotion.
verify-release-tag.shRequires an annotated numeric tag whose commit is on main.

What the updater expects

The format is not arbitrary — it is fixed by the on-device updater in server/service/application/:

  • One safe root directory. The updater scans before extracting and accepts only directories and regular files under nanokvm_<version>/; links, special files, duplicate paths, and path traversal are rejected.
  • name is the file name. version.go builds the download URL as <base>/<name>, where <base> is https://cdn.sipeed.com/nanokvm (or .../preview when /etc/kvm/preview_updates exists).
  • sha512 is base64, not hex. update.go compares against base64(raw sha512 digest). A hex digest will fail verification on device.
  • /kvmapp/version is what the device reports as its installed version, so it must match the version field.

New releases use manifest v2. size remains the exact compressed byte count for older clients, while v2 clients use size_bytes and unpacked_size_bytes for storage preflight checks. Historical v1 size values have inconsistent units, so v2 clients only require v1 size to be non-zero and verify the downloaded SHA-512 instead. Publish v2 metadata before releasing clients that consume it; clients retain v1 compatibility for custom update servers.

Building a release

The frontend needs Node and pnpm on the host; everything riscv64 needs the builder image (see docker/Dockerfile).

make release VERSION=2.4.4

That is equivalent to:

make release-build         # kvm_system + libkvm.so + NanoKVM-Server, in Docker
make web                   # web/dist
make package VERSION=2.4.4 # build/release/{nanokvm_2.4.4.tar.gz,latest.json}

In CI this runs as the NanoKVM Package workflow. Pull requests and manual runs only create Actions artifacts. For a public release, run NanoKVM Create Tag, then run NanoKVM Release and choose prerelease, stable, or promotion. Publishing attaches three assets:

Publishing is one-shot: an existing Release or draft is an error, and only a published prerelease can be promoted.

  • nanokvm_<version>.tar.gz
  • latest.json
  • sha256.txt

The Actions artifact also includes BUILD_INFO.txt with the source commit, workflow run, immutable builder image digest, and both checksum encodings; that provenance file is not attached to the GitHub release. Uploading the tarball and latest.json to the CDN is what actually offers the update to devices in the field, so that step stays manual. sha256.txt is only for users verifying a manual download and does not change the on-device latest.json contract.

Where each file in the package comes from

Package pathSource
versionthe requested version
server/NanoKVM-Serverserver/build.sh (BoringCrypto, RPATH $ORIGIN/dl_lib)
server/dl_lib/whatever the kvm_vision build emits, with any library it does not emit backfilled from the tracked server/dl_lib/
server/web/web/dist/
kvm_system/kvm_systemsupport/sg2002 build kvm_system
system/tool/prebuilt binaries in tools/nanokvm_update_edid/
kvm/default runtime state (resolution, fps, quality)
everything elsetracked kvmapp/

Note that kvm/ ships default runtime state, and install.go replaces /kvmapp wholesale, so an update resets those values on the device. That is long-standing behaviour, not something these scripts introduce.

The two copies of libkvm.so

NanoKVM-Server is cgo-linked against the tracked server/dl_lib/libkvm.so (server/common/kvm_vision.go: -L../dl_lib -lkvm), but the package ships the library the kvm_vision build just produced. Those are two different files, and the tracked one can lag well behind — published releases have shipped this mismatch for a long time.

Because a symbol the binary imports could in principle be absent from the shipped library, and that would only surface as a crash on a real device, build-in-container.sh checks the shipped libkvm.so still exports every symbol NanoKVM-Server actually imports from it, and fails the build otherwise.