Skip to main content

tuna ❤️ JetKVM

JetKVM is a compact IP-KVM device based on the Rockchip RV1106 (ARM Cortex-A7). It allows you to remotely control a computer: see the screen, control the keyboard and mouse, mount disk images — all through a web interface in the browser.

By default, JetKVM is only accessible on the local network. With Tuna, you can expose it to the internet — without a public IP, without a VPN, and without port forwarding on the router.

Tuna + JetKVM
info

This guide is tested on JetKVM with system version v0.2.7 or newer. Earlier versions do not support /userdata/init.d/ — update JetKVM via the web interface (Settings → Device → Check for updates).


Preparation

In addition to installation, the setup guide is split into 2 types depending on whether you have a paid Tuna subscription.

  1. On the free plan
  2. With a paid subscription

What you will need

  • JetKVM with system version v0.2.7 or newer
  • Developer Mode enabled in the JetKVM web interface (Settings → Advanced → Developer Mode)
  • An SSH key added via the JetKVM web interface
  • An account on tuna.am (free or with a subscription)
  • An auth token from the token page

JetKVM system specifics

JetKVM runs on a custom embedded Linux (Buildroot + Rockchip SDK). Important differences from a regular Linux system:

FeatureValue
Shell/bin/sh (BusyBox ash). bash is not available
SSH serverDropbear (no scp, sftp, rsync)
Init systemBusyBox init + scripts in /userdata/init.d/
Persistent partition/userdata/ — preserved across OTA updates

Storage layout:

PathPurposePreserved across OTA
/Root FSNo
/userdata/User dataYes

We will store tuna in /userdata/tuna/ so that the binary and configuration are preserved across updates.

Connecting via SSH

Enable Developer Mode in the JetKVM web interface and add a public RSA key (Settings → Advanced → Developer Mode). Then connect:

ssh root@<JetKVM-IP-address>

Or open the terminal in the web interface:

JetKVM terminal
tip

You can find the IP address on the JetKVM built-in screen or in your router's DHCP table.

Installing tuna on JetKVM

Connect to JetKVM via SSH and install the binary:

mkdir -p /userdata/tuna
INSECURE=true INSTALL_DIR=/userdata/tuna sh -c "$(wget -qO- http://releases.tuna.am/tuna/get.sh)"
Why INSECURE=true?

JetKVM does not have root CA certificates, so wget cannot verify the server's SSL certificate. The INSECURE=true flag switches the script to download over HTTP. This is safe because the tuna binary can be verified by checksum after download.

tip

The /userdata partition is preserved across OTA updates, so you will not need to reinstall tuna after firmware updates.

Save the auth token:

/userdata/tuna/tuna --config=/userdata/tuna/.tuna.yml config save-token <YOUR_TOKEN>
tip

We recommend creating a dedicated token for the device.


JetKVM auto-start system

Unlike NanoKVM, where auto-start is configured via /etc/inittab with the respawn directive, JetKVM uses init scripts in the /userdata/init.d/ directory, which needs to be created.

mkdir -p /userdata/init.d

At boot, the JetKVM init system runs all scripts in /userdata/init.d/ whose names match the pattern S??* (for example, S99tuna-http). Scripts are invoked with the start argument, and with the stop argument when stopping.

Critical rules for init scripts

Failing to follow these rules can cause the device to fail to boot:

  1. Use #!/bin/sh — there is no /bin/bash on JetKVM. The #!/bin/bash shebang will cause an error and may block booting.
  2. Do not use the .sh extension — files with the .sh extension are included via source (not invoked as a separate process). An exit command in such a script will terminate the entire init process.
  3. Do not block boot — all long-running processes must be started in the background with &. If the script does not return control, boot will hang.
  4. Handle the start argument — the init system invokes the script as S99tuna-http start. Without case "$1", the script may not run correctly.
No automatic restart

Unlike NanoKVM (where inittab respawn automatically restarts a crashed process), on JetKVM /etc/inittab is overwritten during OTA updates. That's why we use a while true loop inside the init script to auto-restart on failure.

Test before rebooting

Always run the script manually (/userdata/init.d/S99tuna-http start) and make sure the prompt returns. If the console "hangs" after running it — the script will block boot on reboot.


Type 1. Free plan — HTTP tunnel

The free plan provides an HTTP tunnel with a dynamic address. This is enough to expose the JetKVM web interface to the internet and control your computer through a browser.

Free plan limitations
  • The tunnel address changes every 30 minutes
  • You cannot set a fixed subdomain
  • The current address is always available in the personal cabinet

Creating the init script

cat << 'INITEOF' > /userdata/init.d/S99tuna-http
#!/bin/sh

PIDFILE=/tmp/tuna-http.pid
LOGFILE=/tmp/tuna-http.log

wait_network() {
i=0
while ! ip route | grep -q default; do
sleep 1
i=$((i + 1))
if [ "$i" -ge 60 ]; then
echo "$(date): network timeout" >> "$LOGFILE"
return 1
fi
done
return 0
}

start() {
echo "$(date): starting tuna-http" >> "$LOGFILE"
(
wait_network || exit 1
while true; do
/userdata/tuna/tuna \
--config=/userdata/tuna/.tuna.yml \
--log="$LOGFILE" \
http \
--inspect=false \
--basic-auth=tuna:SecurePasswd \
--https-redirect \
80 >> "$LOGFILE" 2>&1
echo "$(date): tuna-http exited, restarting in 5s" >> "$LOGFILE"
sleep 5
done
) &
echo $! > "$PIDFILE"
}

stop() {
if [ -f "$PIDFILE" ]; then
kill "$(cat "$PIDFILE")" 2>/dev/null
pkill -f "/userdata/tuna/tuna.*http" 2>/dev/null
rm -f "$PIDFILE"
fi
}

case "$1" in
start) start ;;
stop) stop ;;
restart) stop; sleep 1; start ;;
*) echo "Usage: $0 {start|stop|restart}" ;;
esac
INITEOF
chmod +x /userdata/init.d/S99tuna-http

Init script explanation

ElementDescription
S99tuna-httpName without .sh — init invokes the script with the start argument rather than including it via source
wait_network()Waits for a default route to appear (up to 60 seconds)
( ... ) &Subshell runs in the background — does not block boot
while trueAuto-restarts tuna on failure (5 second pause)
echo $! > "$PIDFILE"Saves the subshell PID for proper stopping
pkill -fOn stop, kills both the wrapper and the child tuna process

tuna flag explanation

FlagRequiredDescription
--inspect=falseNoDisables the traffic inspector (not needed for KVM) to save RAM
--basic-auth=admin:YourPasswordNoAdditional HTTP authentication (details)
--https-redirectNoRedirects HTTP to HTTPS
80YesJetKVM web interface port

You can also restrict access by IP subnets.

Verification

Run the script manually:

/userdata/init.d/S99tuna-http start

Make sure the prompt returns (the script did not block the console). Check that the process is running:

ps | grep tuna

The output should contain a line with /userdata/tuna/tuna ... http .... If there is no such process — see the Troubleshooting section.

In the logs, you will see your tunnel URL:

cat /tmp/tuna-http.log

The current address is also available in the personal cabinet.


Type 2. Subscription — SSH and HTTP tunnels

With a subscription you get:

  • Fixed subdomain for the HTTP tunnel — the address does not change
  • SSH tunnel — direct access to the JetKVM console from anywhere in the world

HTTP tunnel with a fixed address

cat << 'INITEOF' > /userdata/init.d/S99tuna-http
#!/bin/sh

PIDFILE=/tmp/tuna-http.pid
LOGFILE=/tmp/tuna-http.log

wait_network() {
i=0
while ! ip route | grep -q default; do
sleep 1
i=$((i + 1))
if [ "$i" -ge 60 ]; then
echo "$(date): network timeout" >> "$LOGFILE"
return 1
fi
done
return 0
}

start() {
echo "$(date): starting tuna-http" >> "$LOGFILE"
(
wait_network || exit 1
while true; do
/userdata/tuna/tuna \
--config=/userdata/tuna/.tuna.yml \
--log="$LOGFILE" \
http \
--subdomain=jetkvm \
--inspect=false \
--basic-auth=admin:YourPassword \
--https-redirect \
80 >> "$LOGFILE" 2>&1
echo "$(date): tuna-http exited, restarting in 5s" >> "$LOGFILE"
sleep 5
done
) &
echo $! > "$PIDFILE"
}

stop() {
if [ -f "$PIDFILE" ]; then
kill "$(cat "$PIDFILE")" 2>/dev/null
pkill -f "/userdata/tuna/tuna.*http" 2>/dev/null
rm -f "$PIDFILE"
fi
}

case "$1" in
start) start ;;
stop) stop ;;
restart) stop; sleep 1; start ;;
*) echo "Usage: $0 {start|stop|restart}" ;;
esac
INITEOF
chmod +x /userdata/init.d/S99tuna-http

The difference from the free version is the --subdomain=jetkvm flag, which reserves a permanent address for you. After restarting the tunnel, the URL will remain the same.

info

The example subdomain may already be taken. Reserve your subdomain in advance in the personal cabinet, then use it in the --subdomain=your-subdomain flag.

HTTP tunnel flag explanation

FlagRequiredDescription
--subdomain=jetkvmNoReserves a permanent tunnel address (subscription only)
--inspect=falseNoDisables the traffic inspector (not needed for KVM) to save RAM
--basic-auth=admin:YourPasswordNoAdditional HTTP authentication (details)
--https-redirectNoRedirects HTTP to HTTPS
80YesJetKVM web interface port

You can also restrict access by IP subnets.

SSH tunnel

The SSH tunnel allows you to connect to the JetKVM console over the internet. This is convenient for administration, firmware updates, and diagnostics.

cat << 'INITEOF' > /userdata/init.d/S98tuna-ssh
#!/bin/sh

PIDFILE=/tmp/tuna-ssh.pid
LOGFILE=/tmp/tuna-ssh.log

wait_network() {
i=0
while ! ip route | grep -q default; do
sleep 1
i=$((i + 1))
if [ "$i" -ge 60 ]; then
echo "$(date): network timeout" >> "$LOGFILE"
return 1
fi
done
return 0
}

start() {
echo "$(date): starting tuna-ssh" >> "$LOGFILE"
(
wait_network || exit 1
while true; do
/userdata/tuna/tuna \
--config=/userdata/tuna/.tuna.yml \
--log="$LOGFILE" \
ssh \
--port=jetkvm-ssh \
--record-session=false \
--password-auth=false >> "$LOGFILE" 2>&1
echo "$(date): tuna-ssh exited, restarting in 5s" >> "$LOGFILE"
sleep 5
done
) &
echo $! > "$PIDFILE"
}

stop() {
if [ -f "$PIDFILE" ]; then
kill "$(cat "$PIDFILE")" 2>/dev/null
pkill -f "/userdata/tuna/tuna.*ssh" 2>/dev/null
rm -f "$PIDFILE"
fi
}

case "$1" in
start) start ;;
stop) stop ;;
restart) stop; sleep 1; start ;;
*) echo "Usage: $0 {start|stop|restart}" ;;
esac
INITEOF
chmod +x /userdata/init.d/S98tuna-ssh

SSH tunnel flag explanation

FlagRequiredDescription
--port=jetkvm-sshNoReserves the SSH tunnel port. Without this flag, the port is assigned randomly on each startup. The port alias must be reserved in advance before startup.
--record-session=falseNoDisables recording SSH sessions to disk. Recommended on embedded devices with limited storage to save space.
--password-auth=falseNoDisallows password authentication — access via SSH keys only (details)

You can also set a static login and password or restrict access by IP subnets.

Verifying tunnels

Run both scripts manually:

/userdata/init.d/S99tuna-http start
/userdata/init.d/S98tuna-ssh start

Make sure both processes are running:

ps | grep tuna

The output should contain two lines — one with http and one with ssh. If a process is missing — see the Troubleshooting section.

View the logs:

cat /tmp/tuna-http.log
cat /tmp/tuna-ssh.log

The HTTP tunnel logs will contain the URL, and the SSH tunnel logs will contain connection instructions with the address and port.


Managing tunnels

Auto-restart on failure is provided by the while true loop inside the init script. Use the following commands to manage the tunnels.

Check status

ps | grep tuna

Stop a tunnel

# Stop the HTTP tunnel
/userdata/init.d/S99tuna-http stop

# Stop the SSH tunnel
/userdata/init.d/S98tuna-ssh stop

Start a tunnel

# Start the HTTP tunnel
/userdata/init.d/S99tuna-http start

# Start the SSH tunnel
/userdata/init.d/S98tuna-ssh start

Restart a tunnel

# Restart the HTTP tunnel
/userdata/init.d/S99tuna-http restart

# Restart the SSH tunnel
/userdata/init.d/S98tuna-ssh restart

Fully disabling auto-start

To prevent a tunnel from starting at boot, rename the script removing the S prefix:

# Disable HTTP tunnel auto-start
mv /userdata/init.d/S99tuna-http /userdata/init.d/disabled-tuna-http

# Re-enable it
mv /userdata/init.d/disabled-tuna-http /userdata/init.d/S99tuna-http

Recovery after an OTA update

The binary, configuration, and init scripts are stored in /userdata/, which is preserved across OTA updates. After a firmware update via the JetKVM web interface, the tunnels will keep working without any additional steps.

info

Unlike NanoKVM, on JetKVM you do not need to restore /etc/inittab after an update — auto-start via /userdata/init.d/ fully survives OTA.

After a full re-flash (when /userdata was cleared), you will need to install the binary, save the token, and create the init scripts again.


Additional security settings

Why you need --basic-auth

JetKVM has its own authentication in the web interface. However, given the history of vulnerabilities in IoT devices, we strongly recommend adding an extra layer of protection. When exposing access through the internet, it's better to be safe — the --basic-auth flag adds standard HTTP authentication (username and password in the browser) before the request even reaches JetKVM.

--basic-auth=mylogin:MyStr0ngP@ss
Important

Set your own login and password. Do not use the values from the examples.

SSH tunnel authentication

Option 1. SSH keys (recommended)

The --password-auth=false flag disables password login. To connect, you will need to add a public SSH key in advance in the personal cabinet.

Detailed guide: SSH keys setup

warning

Tunnel nodes have public addresses. Attackers can scan ports and start brute-forcing passwords. For long-lived SSH tunnels, we strongly recommend disabling password authentication.

Option 2. Static login and password

If SSH keys are not suitable, you can set fixed credentials:

--auth=user:pass

Replace user and pass with your own values.

Restricting access by IP subnets

For HTTP and SSH tunnels, you can restrict access by IP addresses.

Allow access only from specific subnets:

--cidr-allow="203.0.113.0/24"

Deny access from specific subnets:

--cidr-deny="198.51.100.0/24"

You can combine — allow a subnet but exclude individual addresses:

--cidr-allow="10.0.0.0/24" --cidr-deny="10.0.0.33/32"
Caution

Use IP filtering carefully. If you make a mistake with the subnet or your IP address changes — you will lose remote access to JetKVM. Make sure you have an alternative way to manage the device (for example, local access).


Troubleshooting

Device fails to boot after adding an init script

Emergency recovery

If JetKVM stopped booting after adding a script to /userdata/init.d/, the most common causes are:

  1. #!/bin/bash shebang — bash is not available on JetKVM
  2. Script blocks boot — the process is not started in the background (&)
  3. .sh extension — the script was included via source, which broke the init process
  4. Syntax error — interrupts init execution

Recovery methods:

  • UART serial console — if you have access to the JetKVM UART port, connect via serial console (115200 baud) and remove or rename the problematic script
  • Factory reset — press and hold the reset button on JetKVM (note: this may clear /userdata/)

tuna process does not start

Check that the process is in the list:

ps | grep tuna

If the output has no lines with /userdata/tuna/tuna, check:

  1. The script exists and is executable:
ls -la /userdata/init.d/S9*tuna*
  1. The script runs without errors:
/userdata/init.d/S99tuna-http start
  1. The tuna binary works:
/userdata/tuna/tuna version

Viewing logs

Tunnel logs are written to /tmp/ and are available until the device reboots:

# HTTP tunnel logs
cat /tmp/tuna-http.log

# SSH tunnel logs
cat /tmp/tuna-ssh.log

To watch logs in real time:

tail -f /tmp/tuna-http.log

Common errors in logs

ErrorCauseSolution
token is invalidInvalid or expired tokenRepeat config save-token with a fresh token from the page
Domain already reservedSubdomain is taken by another userChoose another name via --subdomain=
port alias not foundPort alias is not reservedReserve the alias in the personal cabinet
connection refusedNo internet accessCheck the network: ping -c 3 8.8.8.8
exec format errorWrong binary architectureMake sure you are using tuna_linux_arm (ARMv7)

No internet access

Check that JetKVM has a network connection:

# Check for an IP address
ip addr

# Check the default route
ip route

# Check internet access
ping -c 3 8.8.8.8

Logs disappeared after a reboot

Logs are stored in /tmp/, which is cleared on reboot. This is normal behavior. If you need to keep logs across reboots, change the path in the init scripts:

LOGFILE=/userdata/tuna/tuna-http.log
warning

Frequent log writes may shorten the storage's lifespan. Use this only for temporary debugging.

Tunnel was working and stopped

  1. Check the logs for errors
  2. Make sure the token is still valid in the personal cabinet
  3. Check that the script in /userdata/init.d/ is in place and executable:
ls -la /userdata/init.d/S9*tuna*


Availability monitoring

In addition to tunnels for remote access, you can set up availability monitoring for JetKVM. Heartbeat monitoring will send you notifications if the device loses internet connectivity or stops responding.

Full guide: JetKVM availability monitoring