Open johncoles opened 1 month ago
Please refer to these files for now. A more detailed document will be provided later.
I took some time reverse-engineering API calls and crafting a bash-curl-way of interacting with the API. Pretty surely also easy to adapt in other programming languages.
Note: Some API paths seems to mismatch between "reality" (NanoKVM with latest 2.0.9 as of today) and today's available code on GitHub: For example, /storage/image/mounted
fails, while /storage/images/mounted
works. I believe the code is newer than most recent publicly available firmware.
It does not include all available API calls (such as application
which doesn't work as of today or scripts
) as I could/want not test them. But they do offer API endpoints. With below, it should be possible to build something together yourself.
I thought it might be useful for others, so here we go:
Note:
jq
for urlencode and prettify JSON output. It was easier than using just curl or purely bash.nanokvm-sipeed-2024
and must be identical on frontend and backend (see encrypt.go for backend, and encrypt.ts for frontend)# Login to NanoKVM and receive token
# Adjust variables below
HOSTNAME="192.168.0.1"
USERNAME="admin"
PASSWORD="admin"
# Usually hardcoded and no need to change
PWSECKEY="nanokvm-sipeed-2024"
# Encrypt password via AES-256-CBC before sending to API
PASSENC=$(echo -n "$PASSWORD" | openssl enc -aes-256-cbc -base64 -salt -md md5 -pass pass:$PWSECKEY 2>/dev/null)
# Build JSON string for login, urlencode values as backend expects
AUTHJSON=$(jq -crnM --arg u "$USERNAME" --arg p "$PASSENC" '{"username":$u|@uri,"password":$p|@uri}')
# Send request via curl to NanoKVM
REQUEST=$(curl -s -X POST http://$HOSTNAME/api/auth/login -H 'Content-Type: application/json' --data-raw "$AUTHJSON")
# Parse token via jq from above request
TOKEN=$(echo $REQUEST | jq -r .data.token)
# Set cookie variable to be used by curl
COOKIE="nano-kvm-token=$TOKEN"
After this, below commands can be used.
# curl -s -b $COOKIE http://$HOSTNAME/api/vm/info
{"code":0,"msg":"success","data":{"ip":"<IP>","mdns":"kvm-<LAST 4 DIGITS OF MAC>.local","image":"2024-08-17-18-13-713161.img","firmware":"2.0.9","deviceKey":"<ID>"}}
# curl -s -b $COOKIE http://$HOSTNAME/api/vm/gpio/led
{"code":0,"msg":"success","data":{"pwr":true,"hdd":false}}
Note:
800
is probably 0.8s and is considered a short click, e.g. turning on or sending shutdown request to host. 8000
is considered a long click, e.g. holding power button and letting the host crash.# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/vm/gpio -H "Content-Type: application/json" -d '{"type":"power","duration":800}'
{"code":0,"msg":"success","data":null}
Note: This uses ffmpeg to convert the mjpeg stream to a image with name image.jpg.
# curl -s -N -b $COOKIE http://$HOSTNAME/api/stream/mjpeg -o - | ffmpeg -y -i - -frames:v 1 -vcodec mjpeg image.jpg
If you want to do something fancy:
# tesseract image.jpg -
Warning: Invalid resolution 0 dpi. Using 70 instead.
Estimating resolution as 239
{C 8066 .3211711 vyos—configi39181: Configuration success
Welcome to VyOS — vyos ttyl
vyos login: vyos
[...]
Note:
-t
.unable to decode APP fields: Invalid data found when processing input
is repeatedly thrown. I could not figure out why, but the output video still works.-c:v libx264
). In comparison, x265 (use -c:v libx265
) requires approx. 200 KB instead, but comes with higher CPU cost.# curl -s -N -b $COOKIE http://$HOSTNAME/api/stream/mjpeg -o - | ffmpeg -y -use_wallclock_as_timestamps 1 -f mjpeg -i - -an -t 30 -c:v libx264 -f mp4 output.mp4
# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/hid/paste -H "Content-Type: application/json" -d '{"content": "this is a test"}'
{"code":0,"msg":"success","data":null}
# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/hid/paste -H "Content-Type: application/json" -d '{"content": "vyos\nvyos\n"}'
{"code":0,"msg":"success","data":null}
Examples:
# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/hid/reset
{"code":0,"msg":"success","data":null}
Note: Not specifying -X
makes it use GET by default.
# curl -s -b $COOKIE http://$HOSTNAME/api/stream/mjpeg/detect
{"code":0,"msg":"success","data":{"enabled":false}}
# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/stream/mjpeg/detect
{"code":0,"msg":"success","data":{"enabled":true}}
# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/stream/mjpeg/detect
{"code":0,"msg":"success","data":{"enabled":false}}
# curl -s -b $COOKIE http://$HOSTNAME/api/storage/images | jq -r
{
"code": 0,
"msg": "success",
"data": {
"files": [
"/data/ubuntu-24.04-desktop-amd64.iso"
]
}
}
Note: Path is absolute path to ISO file
# curl -s -X POST -b $COOKIE http://$HOSTNAME/api/storage/image/mount -H "Content-Type: application/json" -d '{"file": "/data/ubuntu-24.04-desktop-amd64.iso"}'
{"code":0,"msg":"success","data":null}
# curl -s -b $COOKIE http://$HOSTNAME/api/storage/images/mounted
{"code":0,"msg":"success","data":{"file":"/data/ubuntu-24.04-desktop-amd64.iso"}}
# curl -s -b $COOKIE http://$HOSTNAME/api/vm/device/virtual
{"code":0,"msg":"success","data":{"network":true,"disk":true}}
Note: Use network
or disk
for device
key
$ curl -s -X POST -b $COOKIE http://$HOSTNAME/api/vm/device/virtual -H "Content-Type: application/json" -d '{"device": "network"}'
{"code":0,"msg":"success","data":{"on":true}}
$ curl -s -X POST -b $COOKIE http://$HOSTNAME/api/vm/device/virtual -H "Content-Type: application/json" -d '{"device": "network"}'
{"code":0,"msg":"success","data":{"on":false}}
Nice! Thanks for your work and sharing this!
kudos for the tesseract example, more people should figure out stuff like this and do it!
@wj-xiao Hi! is there a non-authenticated local API endpoint for scripts running on the device? Would be easier for sharing, unless someone says it's bad for security and I just didn't notice a problem there.
@FlorianHeigl By default, APIs require authentication.
However, you can disable authentication by modifying the configuration file.
Add one line authentication: disable
to the configuration file /etc/kvm/server.yaml
. And restart the service by /etc/init.d/S95nanokvm restart
.
Please note that this will make the device insecure.
Is there an API that will allow me to click? Also press keys
Is there an API that will allow me to click? Also press keys
Could not find a specific API call last time I checked, so I assume it's sent through the established websocket connection. Possible for sure, but way more tricky.
I see the code has now been shared so I may go through and do this myself, but it would be nice to have documentation for the HTTP API used.
I note so far that there is a request to
/api/auth/login
to perform the login, but the password is not sent as plaintext. Subsequent requests to the API seem to use a token that is set as a cookie.Other endpoints include: GET
/api/vm/gpio/led
which returns the LED state:{"code":0,"msg":"success","data":{"pwr":false,"hdd":false}}
GET
/api/storage/images/mounted
{"code":0,"msg":"success","data":{"file":""}}
POST
/api/vm/gpio
with data like:{"type":"power","duration":800}
will press the power button for 800ms. Returns:{"code":0,"msg":"success","data":null}
It would be great to have docs on how to use this to integrate in to other systems (I would like to add an integration in to Home Assistant for example) to control multiple machines.