A personal App Store catalog for the Sipeed NanoKVM Go and NanoKVM Go+
touchscreens. It uses the same format as the official
sipeed/NanoKVM-Go-Apps main
branch, so the device's built-in Store can install directly from it.
-
Open the NanoKVM web UI, then go to Settings → Apps → Store → Servers.
-
Click Add server, give it a name, and enter this URL:
https://github.com/JoshuaAFerguson/NanoKVM-Go-Apps -
Switch the Store to that server, pick an App, and click Install.
-
On the device touchscreen, open Apps and tap the App. To exit, swipe inward from the screen edge and confirm.
The device downloads the default branch as
https://codeload.github.com/JoshuaAFerguson/NanoKVM-Go-Apps/zip/HEAD and
treats every top-level directory as an App. That means:
- the repo must stay public, and
mainis the catalog; - directories starting with
_(or.) are ignored, so tooling lives there; - everything on
mainships to the device on every Store browse, so keep it lean.
App install scripts run as root on the device. Only add servers you trust.
| App | Description |
|---|---|
macro-pad |
Touch buttons for key combos, BIOS/boot keys and text on the target. Profiles for Windows, macOS, Linux, mobile, plus your own. Needs the MCP key. |
kvm-status |
Dashboard: target video/USB, Wi-Fi, IPs, CPU, temperature, memory. Detects and fixes a stuck video service. |
wol-panel |
Wake-on-LAN: tap a machine to wake it, with live online/offline status. |
homelab-glance |
Home Assistant, Uptime Kuma, Proxmox, Prometheus, Kubernetes and Docker status with drill-down. |
screen-watcher |
Background alerts (ntfy, Discord, Pushover, Home Assistant) when the target loses video, blue-screens, goes black or freezes. |
vnc-bridge |
VNC server for the target (Tight JPEG passthrough, real USB keyboard/mouse). Works with TigerVNC, Remmina, noVNC, Guacamole. |
text-alert |
Go+ only. Notifies when a phrase (e.g. "Build succeeded", "Kernel panic") appears on the target's screen, via on-device OCR. |
media-power |
Mount ISO images on the virtual drive and press the target's power button (Finger Robot) from the touchscreen. Needs the web login. |
pixel-pet |
A Tamagotchi-style pixel pet that grows into one of three forms, never dies, and notices what the KVM is doing. |
ai-monitor |
AI API spend, Claude Code session states via hooks (alerts when an agent needs you), AI control of this KVM with a kill switch, and local LLM servers. |
hello-nanokvm |
Smoke test: confirms this Store installs and runs Apps. |
.
├── hello-nanokvm/ # one directory per App (the catalog)
│ ├── app.json
│ └── main.py
├── _shared/ # code shared by Apps, copied into each App that uses it
│ ├── kvm_mcp.py # client for the device's MCP server (keyboard, mouse, screenshot)
│ ├── ui.py # layout, palette, buttons, headers
│ ├── netinfo.py # interface IP/netmask via ioctl
│ ├── localtls.py # pinned TLS for the device's own HTTPS server
│ ├── font.py # 8x8 font with lowercase + punctuation, fast renderer
│ ├── applog.py # log.txt in the App dir (launcher discards stdout/stderr)
│ ├── appdata.py # persistent data in /var/lib/nanokvm-apps (survives updates)
│ ├── notify.py # ntfy / Discord / Pushover / Home Assistant notifications
│ ├── jpegdc.py # DC-only JPEG decoder: block-average colours, pure Python
│ ├── vinsnap.py # snapshot client for the KVM's video service
│ ├── alerter.py # alert log, pause and retry queue for background services
│ ├── servicekit.py # helpers for Apps with a background systemd service
│ └── httpjson.py # bounded JSON/text HTTP client
├── _template/ # starter App copied by _tools/new_app.py
├── _tools/
│ ├── new_app.py # scaffold a new App with a matching app_id and shared modules
│ ├── sync_shared.py # copy _shared/ changes into Apps (--check in CI)
│ ├── preview_app.py # render an App's screen locally to a PPM image
│ ├── validate_catalog.py # check every App against the launcher's rules
│ ├── package_app.py # build an upload ZIP for Settings → Apps → Upload ZIP
│ ├── fetch_sdk.sh # pull appbase SDK into _sdk/ for IDE type hints
│ └── tests/
└── .github/workflows/ # CI runs the validator on every push and PR
./_tools/fetch_sdk.sh # once: SDK + guide into _sdk/ (git-ignored)
python3 _tools/new_app.py weather-panel "Weather"
# edit weather-panel/main.py and app.json
python3 _tools/preview_app.py weather-panel /tmp/w.ppm && open /tmp/w.ppm # local screenshot
python3 -m unittest discover -s _tools/tests
python3 _tools/validate_catalog.py && python3 _tools/sync_shared.py --check
python3 _tools/package_app.py weather-panel # optional: dist/weather-panel.zipTest quickly without the Store:
scp -r weather-panel root@<device-ip>:/kvmcomm/apps/ # list refreshes in ~10s- Directory name: lowercase kebab-case, e.g.
weather-panel. app.jsonandmain.pyare both required.- Required fields:
app_id,name,creator,create_time(YYYY-MM-DD),version. app_idis reverse-domain with ≥ 3 parts, and its last part equals the directory name with-→_. This repo usesio.github.joshuaaferguson.<app_name>.- Optional:
desc,category,icon,env(a config form shown at install, read viactx.env), andpre_script/post_script(run as root at install). - Don't bundle
appbase.py. The device provides the shared SDK. - Never commit secrets. Declare them in
envwith"secret": true.
The Store installs one App directory at a time, so Apps can't import from each other.
Shared modules live in _shared/ and are copied into each App that uses them.
Edit only _shared/, then run python3 _tools/sync_shared.py. CI fails if a copy is stale.
Start using one with python3 _tools/sync_shared.py add <app> <module>.
The upstream guide is out of date here. On current firmware both models launch Apps at
rotate=90 on a 284×240 landscape canvas, and all of it is visible (no hidden
strip). The SDK font is uppercase-only with a few symbols; _shared/font.py adds
lowercase and punctuation. At about 200 ppi, scale-1 text is roughly 1 mm tall, so use
scale 2 for anything people need to read. Drawing text is slow, so redraw only when
something changes. The host reserves the left and right 40 px for its exit swipe.
Memory: the Go has about 35 MB free. Keep Apps under about 20 MB RSS and avoid heavy
imports. Apps run as root; the launcher discards stdout/stderr, so use applog.
SDK reference: base branch guide.