Nanokvm
Operate a Sipeed NanoKVM from swamp: a health verdict, screenshots, GPIO power/reset and virtual media, with the device's health pushed to your swamp-managed OpenTelemetry collector. WHAT IT DOES
A NanoKVM is an out-of-band IP-KVM running Sipeed's own server, not PiKVM's
kvmd: a small JSON API over a self-signed HTTPS certificate. Two firmware
generations disagree on login — older builds take the password in plaintext
and return a bearer token, newer builds take the web UI's AES-encrypted
password and set a session cookie — and this model speaks both transparently.
health returns a pass/warn/fail verdict with findings — firmware behind, no
HDMI signal, virtual media left mounted, HID misconfigured — and records an
unreachable device as a failure rather than throwing, so a scheduled check
always leaves evidence. emit forwards that verdict to the swamp-managed
OTel system (an OTLP log plus gauges), a no-op that says why when no
collector is configured.
INSTALL
swamp extension pull @svendowideit/nanokvmDEPENDENCIES
@svendowideit/curl (pulled automatically) — the HTTP primitive this model
drives for the login, every read and the OTLP push. Optional:
@svendowideit/otel-settings, whose contract supplies the emit endpoint;
without it emit records that it has nowhere to send and does nothing.
RUN
# Store the login password once (never in the model definition), then
# check the device. Vault-first: the model reads vaultName/passwordRef.
swamp vault create @svendowideit/systemd-creds nanokvm
swamp vault put nanokvm NANOKVM_PASSWORD
swamp model @svendowideit/nanokvm method run setup nanokvm \
--input host=192.0.2.10
swamp model @svendowideit/nanokvm method run health nanokvm
# Capture a frame from the video stream, and press the power button —
# power and msd change state, so they are dry runs until apply=true.
swamp model @svendowideit/nanokvm method run screenshot nanokvm
swamp model @svendowideit/nanokvm method run power nanokvm \
--input action=power --input apply=true
# Push the latest health to the swamp-managed OTel collector. A no-op
# that records the reason when no @svendowideit/otel-settings contract or
# endpoint is configured.
swamp model @svendowideit/nanokvm method run emit nanokvm
# List every NanoKVM swamp looks after (a repo scan, no network) and emit
# an A-record list ready for a DNS reconciler.
swamp model @svendowideit/nanokvm method run devices nanokvm \
--input zone=fi.gyCONFIGURE
Global arguments (set at creation with --global-arg key=value; only host has no default): host NanoKVM address, no scheme (e.g. 192.0.2.10) port HTTPS port (default 443) username web UI user (default admin) vaultName vault holding the password (default empty) passwordRef vault key for the password (default NANOKVM_PASSWORD) envRef env var fallback for the password (default NANOKVM_PASSWORD) password literal password fallback (sensitive; avoid) passwordEncoding how the login sends the password: auto|plain|aes (default auto) curlModel @svendowideit/curl instance name (default nanokvm-curl) otelModel otel-settings model for the emit endpoint (default otel) otelEndpoint OTLP/HTTP base URL overriding the contract (default empty) otelMesh contract mesh to use (default empty = defaultMesh) otelVaultName vault holding the gateway bearer token (default empty) otelTokenRef vault/env key for the token (default OTEL_EXPORTER_OTLP_TOKEN) insecure skip TLS verification (default true) timeoutMs per-request timeout (default 15000) maxClockSkewSeconds tolerated slowness before a warning (default 120)
Method arguments: power takes action (power|reset), durationMs, apply;
msd takes action (status|mount|umount), file, cdrom, apply; screenshot
takes timeoutMs and label; emit takes health, endpoint and mesh; devices
takes zone (the DNS suffix for each device's name). setup takes the
connection overrides it can persist. See the README for the full tables.
SETUP
setup is the entry point for a new device. It persists what it is given —
even when the configuration is incomplete — to the model's config
resource, reads that resource back on the next run and reuses it, and
reports which capabilities are enabled: canLogin (a host and a password)
and canEmit (a collector contract or an explicit endpoint). Supplying
only the host stores the rest and leaves canLogin false; add the vault
later and re-run. health refuses to run until a host is configured and
reports a missing password as a fail verdict with the reason.
UPGRADE
This extension installs nothing on the host, so an upgrade has nothing to rebuild or reconcile. Refresh the pulled model and its bundled files with one idempotent command:
# Pull the newest published model and its bundled files.
swamp extension update Model definitions record the version they were created with; the bundled
upgrades entry migrates them on the next method run. No swamp data is
touched.
WHAT IT INSTALLS
Nothing on the host — no services, cron triggers, daemons or webhooks. It runs the curl binary through @svendowideit/curl, talks to the NanoKVM and (optionally) the OTel collector over the network, and writes swamp resources and a JPEG file.
Global Arguments
| Argument | Type | Description |
|---|---|---|
| name? | string | Model instance name (supplied by swamp). |
| port? | number | HTTPS port of the NanoKVM web server. |
| password? | string | Login password (last resort; prefer vaultName). Recorded as a secret. |
| passwordEncoding? | enum | How the login password is sent. Older NanoKVM firmware expects it in |
| insecure? | boolean | Skip TLS certificate verification. NanoKVM's factory certificate is |
| timeoutMs? | number | Per-request timeout, in milliseconds. |
| maxClockSkewSeconds? | number | Clock difference tolerated before a `clock_skew` finding. |
| Argument | Type | Description |
|---|---|---|
| host? | string | NanoKVM address (persisted for reuse). |
| port? | number | HTTPS port (persisted for reuse). |
| username? | string | Web UI user (persisted). |
| vaultName? | string | Password vault name (persisted). |
| passwordRef? | string | Password vault key (persisted). |
| envRef? | string | Password environment variable (persisted). |
| curlModel? | string | curl model instance name (persisted). |
| otelModel? | string | otel-settings model name (persisted). |
| Argument | Type | Description |
|---|---|---|
| action | enum | `power` presses the power button; `reset` presses the reset button. |
| durationMs? | number | How long to hold the contacts, in ms. 0 uses the device's own default |
| apply? | boolean | Actually press the button. When false (the default) the method is a dry |
| Argument | Type | Description |
|---|---|---|
| action | enum | `status` reads the mounted image; `mount` serves `file` to the host; |
| file? | string | Image path as the NanoKVM sees it (e.g. /data/ubuntu.iso). Required for |
| cdrom? | boolean | Present the image as a CD-ROM instead of a removable disk. |
| apply? | boolean | Actually mount or unmount. `status` is always read-only; mount/umount are |
| Argument | Type | Description |
|---|---|---|
| timeoutMs? | number | How long to read the MJPEG stream before trimming the first frame. |
| label? | string | Name for the stored JPEG frame. Empty uses `screenshot`. |
| Argument | Type | Description |
|---|---|---|
| health? | unknown | A health record to emit. When omitted, the model's latest `health` |
| endpoint? | string | OTLP/HTTP base URL overriding both the contract and the global argument. |
| mesh? | string | Contract mesh to use for this emit. |
| Argument | Type | Description |
|---|---|---|
| zone? | string | DNS zone appended to each device name to form its fqdn (e.g. `fi.gy` |
| Argument | Type | Description |
|---|---|---|
| action | enum | `status` reads the certificate the device currently serves; `install` |
| name? | string | The certificate's DNS name (e.g. nanokvm.fi.gy). Required for `install` |
| certDir? | string | Caddy's certificate store. `install` looks for |
| crt? | string | Explicit path to the certificate PEM, overriding the certDir lookup. |
| key? | string | Explicit path to the private-key PEM, overriding the certDir lookup. |
| waitMs? | number | How long `install` waits for the certificate file to appear (Caddy issues |
| restart? | boolean | Restart the NanoKVM web server after writing the files, so it serves the |
Resources
Fleet health report over a workflow run of @svendowideit/nanokvm devices:
- Has README or module doc2/2earned
- README has a code example1/1earned
- README is substantive1/1earned
- Most symbols documented1/1earned
- No slow types (deprecated)1/1earned
- Dependencies pass trust audit2/2earned
- Has description1/1earned
- Platform support declared (or universal)2/2earned
- License2/2earned
- Verified public repository2/2earned
Spawns a subprocess with Deno.Command().
- models/nanokvm.ts:1525 — reading the certificate the device serves needs a TLS handshake openssl s_client provides; the arguments are fixed and validated (host/port/name), never raw caller input.
- models/nanokvm.ts:1544 — reading a certificate serial needs openssl x509; the argument is a path this method resolved, never raw caller text.