# NanoKVM Server This is the backend server implementation for NanoKVM. For detailed documentation, please visit our [Wiki](https://wiki.sipeed.com/nanokvm). ## Structure ```shell server ├── common // Common utility components ├── config // Server configuration ├── dl_lib // Shared object libraries ├── include // Header files for shared objects ├── logger // Logging system ├── middleware // Server middleware components ├── proto // API request/response definitions ├── router // API route handlers ├── service // Core service implementations ├── utils // Utility functions └── main.go ``` ## Configuration The configuration file path is `/etc/kvm/server.yaml`. ```yaml # Network Settings proto: http # Access protocol. Can be changed to `https` only when certificates are configured. Default is `http` host: "" # The listening address for the HTTP/HTTPS service. If left empty, all network interfaces will be bound port: http: 80 # The listening port for the HTTP service. Default is `80` https: 443 # The listening port for the HTTPS service (effective when HTTPS is enabled). Default is `443` cert: crt: server.crt # The path to the public key certificate for HTTPS key: server.key # The path to the private key file for HTTPS # Logging Configuration logger: level: info # Global log output level. Evaluated options from highest to lowest detail: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`. Default is `info` file: stdout # Log output destination. `stdout` outputs to the standard console. A file path directs log output to that file. Default is `stdout` # Authentication & Security authentication: enable # Whether to enable identity verification for HTTP API and Web endpoints. Options are `enable` or `disable`. Default is `enable`. Highly recommended to leave this enabled for internet-facing devices! jwt: secretKey: "" # The secret key used to sign and verify JWT Tokens. If left empty, a random key will be generated automatically on startup refreshTokenDuration: 2678400 # The token refresh duration threshold in seconds before forcing a re-login. Default is `2678400` (~31 days) revokeTokensOnLogout: true # Whether logout invalidates all sessions belonging to that user. Other users are never logged out. Setting this to false only clears the browser cookie and is not recommended. Default is `true` security: loginLockoutDuration: 0, # The duration (in seconds) to ban an IP from attempting to log in again after reaching the failure limit. If set to `0` or left empty, brute-force protection is disabled. Default is `0` loginMaxFailures: 5, # The maximum number of continuous failed login attempts allowed per IP before triggering protection. Default is `5` # WebRTC Traversal Settings stun: stun.l.google.com:19302 # The default STUN server address used for NAT hole-punching to establish P2P streams turn: turnAddr: example_addr # The relay (TURN) server address (format `ip:port`) used as a fallback when P2P connection fails. Leave empty to disable TURN relay turnUser: example_user # The username required for authorization to the TURN server turnCred: example_cred # The credential/password required for authorization to the TURN server ``` ## Web Users NanoKVM uses two device-wide roles: - `admin`: everything below, plus user management. - `user`: only what the account is granted. A grant names an action on one party, and the catalog the server serves to the account editor carries its group, its risk, and the grants it implies: | Group | Grants | | --- | --- | | console (this session) | `console.view`, `console.control` | | target machine | `power`, `medium.attach` | | media library (this device) | `library.read`, `library.write` | | this device | `terminal`, `device`, `network`, `update` | | services | `mcp`, `tailscale`, `ai` | Implied grants are declared once, in the catalog, and the server resolves them before answering any request: `console.control` implies `console.view`, both `medium.attach` and `library.write` imply `library.read`, and every grant whose controls live in the console menu — `power`, `device`, `network`, `update`, `mcp`, `tailscale` and `ai` — implies `console.view` too, because no page outside the console renders them. Consequences worth knowing: - An account can hold no console access at all. The web app then lands on a page that offers the sections the account may use (the media library, the terminal) instead of a console it cannot open. `library.read` is all that media access needs, because the other two media grants imply it, and only `terminal` and the media grants can stand on their own pages in the first place. - The media grants split along the trust boundary. `medium.attach` mounts or ejects a medium on the target machine and writes into the shared partition the target reads; `library.write` changes what the library keeps (uploads, deletes, the files inside an image); `library.read` lists and downloads. The file endpoints serve an image and the shared partition through one path, so the route admits either grant and each request is checked against the medium type it names. Every `.iso` and `.img` on the partition is a library entry, so uploading one through the shared partition, or deleting a file or folder that holds one, also needs `library.write`. - The emulated USB devices are neither media nor network: the virtual disk and the RNDIS adapter hand the target machine the raw data partition and a network interface, so both follow `device`. A medium can only be attached while a `device` holder has the virtual disk switched on, and the storage page says so when it is off. Four grants reach further than their names suggest and amount to full device control. The catalog has two risk levels, and `warn` is reserved for them: the account editor marks those four and names them before the grants are saved: - `terminal` is a root shell (the same one SSH gives) and can run saved and autostart scripts. - `mcp` hands out a device-wide API key that drives the keyboard, mouse and screenshots from outside the web session. The key is shared, so a copy keeps working after an account loses this grant. Whenever a change takes the grant away (revoked, demoted, disabled or deleted), the account editor warns and offers to regenerate the key, checked by default; regenerating disconnects every MCP client until it is given the new key, and an administrator who keeps the key accepts that a copy stays valid. The API takes the choice as `rotateMcpKey` (a body field on update, a query parameter on delete) and never rotates without it. - `ai` drives the device through the local loopback interface, which the other grants do not restrict. - `update` installs firmware and application packages. An offline package is checked only against the checksum its uploader supplies, so installing one is running code as root. `device` carries no such mark, although it changes what the device does for everyone and cannot be narrowed per account. `device` turns SSH on or off (root login, with the device owner's password), reboots the device, hands the raw data partition or a network interface to the target machine through the emulated USB devices, and edits the OLED, HDMI, mDNS, memory, swap and HID settings. The editor describes it that way. Two grants also change settings that affect everyone watching: `console.view` sets the video mode, resolution, frame rate and quality, and resets the HDMI capture; `power` manages the Wake-on-LAN device list. Both are described that way in the editor. Administrators manage accounts from **Settings > Account**. Account data remains in `/etc/kvm/pwd`; the server migrates the legacy single-account JSON format in place and writes the multi-user format atomically with mode `0600`. Keeping the same path preserves the physical BOOT-button password reset behavior. Users must confirm their current password when changing it themselves; administrators can reset non-owner users from the account manager. Only the device owner can change the device owner's password, because that password is also synchronized to the Linux root account. All authenticated sessions are backed by the current account state: every request is authorized against the account file, so a revoked grant stops working immediately without re-issuing a login. Disabling, deleting, or resetting the password of a user invalidates that user's HTTP and real-time connections without affecting other users. Changing the role or the grants of an account keeps its login valid: the account is told to refresh, and only the long-lived connections whose cached state depends on what changed are dropped (console, video stream, terminal, AI), so those clients reconnect under the new grants. An account that loses `console.view` is sent to the landing page instead of a console it can no longer open. Multiple users may watch and control the KVM concurrently; input uses the existing cooperative HID coordinator, so simultaneous input can interleave. A user without `console.control` keeps the video and the menu for its other grants, but the console rejects keyboard and mouse reports and the web UI marks the session as view only. Video mode, quality, resolution, MJPEG frame-detection controls, and resetting the HDMI capture require only `console.view` and stay shared KVM operations; when several users adjust them concurrently, the latest change applies device-wide. Enabling or disabling the HDMI capture and its idle timeout are device settings. `console.control` is input only: keyboard, mouse, paste, and the input region that maps pointer motion onto the target screen. The account file stores an explicit grant set: a record written before fine-grained permissions keeps exactly what the user role used to grant, and the split media grants are translated on load, so the store never holds a missing set that would fall back to different behavior later. Grants this build does not know are preserved and ignored, so a downgrade cannot lock the device out. Creating a regular account without naming a grant set keeps that same meaning, so a script that only knows about roles gets the account the user role used to create; the account editor seeds a new regular account with `console.view` only, and an explicitly empty set really is empty. ## Compile & Deploy Note: The manual steps below require a Linux x86-64 host with Go 1.25 or newer; they are not compatible with ARM, Windows or macOS. With Docker you can skip them entirely and use the containerized flow instead — the root [Makefile](../Makefile) (`make shell`) or the dev container (see "Development" in the root [README](../README.md)) — which works on any host OS; run `server/build.sh` inside the container for a release-equivalent build. 1. Install the Toolchain 1. Download the toolchain from the following link: [Download Link](https://sophon-file.sophon.cn/sophon-prod-s3/drive/23/03/07/16/host-tools.tar.gz). 2. Extract the file and add the `host-tools/gcc/riscv64-linux-musl-x86_64/bin` directory to your PATH environment variable. 3. Run `riscv64-unknown-linux-musl-gcc -v`. If there is version information in the output, the installation is successful. 2. Compile the Project 1. Run `cd server` from the project root directory. 2. Run `go mod tidy` to install Go dependencies. 3. (Optional) If you compiled `libkvm.so` yourself, you need to modify its RPATH by `patchelf --add-rpath \$ORIGIN ./dl_lib/libkvm.so`. 4. Run `CGO_ENABLED=1 GOOS=linux GOARCH=riscv64 CC=riscv64-unknown-linux-musl-gcc CGO_CFLAGS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d" go build` to compile the project. 5. After compilation, an executable file named `NanoKVM-Server` will be generated. 3. Modify RPATH 1. Run `sudo apt install patchelf` or `pip install patchelf` to install patchelf. 2. Run `patchelf --version`. Ensure the version is 0.14 or higher`. 3. Run `patchelf --add-rpath \$ORIGIN/dl_lib NanoKVM-Server` to modify the RPATH of the executable file. 4. Deploy the Application 1. File uploads requires SSH. Please enable it in the Web Settings: `Settings > SSH`; 2. Replace the original file in the NanoKVM `/kvmapp/server/` directory with the newly compiled `NanoKVM-Server`. 3. Restart the service on NanoKVM by executing `/etc/init.d/S95nanokvm restart`. ## Manually Update > File uploads requires SSH. Please enable it in the Web Settings: `Settings > SSH`; 1. Download the latest application from [GitHub](https://github.com/sipeed/NanoKVM/releases); 2. Unzip the downloaded file and rename the unzipped folder to `kvmapp`; 3. Back up the existing `/kvmapp` directory on your NanoKVM, then replace it with the new `kvmapp` folder; 4. Run `/etc/init.d/S95nanokvm restart` on your NanoKVM to restart the service.