kvm/DEVELOPMENT.md

17 KiB

JetKVM Development Guide

Welcome to JetKVM development! This guide will help you get started quickly, whether you're fixing bugs, adding features, or just exploring the codebase.

Get Started

Prerequisites

Development Environment

Recommended: Development is best done on Linux or macOS.

If you're using Windows, we strongly recommend using WSL (Windows Subsystem for Linux) for the best development experience:

This ensures compatibility with shell scripts and build tools used in the project.

Project Setup

  1. Clone the repository:

    git clone https://github.com/jetkvm/kvm.git
    cd kvm
    
  2. Check your tools:

    go version && node --version
    
  3. Find your JetKVM IP address (check your router or device screen)

  4. Deploy and test:

    ./dev_deploy.sh -r 192.168.1.100  # Replace with your device IP
    
  5. Open in browser: http://192.168.1.100

That's it! You're now running your own development version of JetKVM.


Common Tasks

Modify the UI

cd ui
npm install
./dev_device.sh 192.168.1.100  # Replace with your device IP

Now edit files in ui/src/ and see changes live in your browser!

Modify the backend

# Edit Go files (config.go, web.go, etc.)
./dev_deploy.sh -r 192.168.1.100 --skip-ui-build

Run tests

./dev_deploy.sh -r 192.168.1.100 --run-go-tests

View logs

ssh root@192.168.1.100
tail -f /var/log/jetkvm.log

Project Layout

/kvm/
├── main.go                   # App entry point
├── config.go                 # Settings & configuration
├── display.go                # Device UI control
├── web.go                    # API endpoints
├── cmd/                      # Command line main
├── internal/                 # Internal Go packages
│   ├── confparser/           # Configuration file implementation
│   ├── hidrpc/               # HIDRPC implementation for HID devices (keyboard, mouse, etc.)
│   ├── logging/              # Logging implementation
│   ├── mdns/                 # mDNS implementation
│   ├── native/               # CGO / Native code glue layer (on-device hardware)
│   │   ├── cgo/              # C files for the native library (HDMI, Touchscreen, etc.)
│   │   └── eez/              # EEZ Studio Project files (for Touchscreen)
│   ├── network/              # Network implementation
│   ├── timesync/             # Time sync/NTP implementation
│   ├── tzdata/               # Timezone data and generation
│   ├── udhcpc/               # DHCP implementation
│   ├── usbgadget/            # USB gadget
│   ├── utils/                # SSH handling
│   └── websecure/            # TLS certificate management
├── resource/                 # netboot iso and other resources
├── scripts/                  # Bash shell scripts for building and deploying
└── static/                   #  (react client build output)
└── ui/                       # React frontend
    ├── localization/         # Client UI localization (i18n)
    │   ├── jetKVM.UI.inlang/ # Settings for inlang
    │   └── messages/         # Messages localized
    ├── public/               # UI website static images and fonts
    └── src/                  # Client React UI
        ├── assets/           # UI in-page images
        ├── components/       # UI components
        ├── hooks/            # Hooks (stores, RPC handling, virtual devices)
        ├── keyboardLayouts/  # Keyboard layout definitions
        ├── paraglide/        #  (localization compiled messages output)
        ├── providers/        # Feature flags
        └── routes/           # Pages (login, settings, etc.)

Key files for beginners:

  • web.go - Add new API endpoints here
  • config.go - Add new settings here
  • ui/src/routes/ - Add new pages here
  • ui/src/components/ - Add new UI components here

Development Modes

Best for: Complete feature development

# Deploy everything to your JetKVM device
./dev_deploy.sh -r <YOUR_DEVICE_IP>

Frontend Only

Best for: UI changes without device

cd ui
npm install
./dev_device.sh <YOUR_DEVICE_IP>

Touchscreen Changes

Please click the Build button in EEZ Studio then run ./dev_deploy.sh -r <YOUR_DEVICE_IP> --skip-ui-build to deploy the changes to your device. Initial build might take more than 10 minutes as it will also need to fetch and build LVGL and other dependencies.

Quick Backend Changes

Best for: API or backend logic changes

# Skip frontend build for faster deployment
./dev_deploy.sh -r <YOUR_DEVICE_IP> --skip-ui-build

Debugging Made Easy

Check if everything is working

# Test connection to device
ping 192.168.1.100

# Check if JetKVM is running
ssh root@192.168.1.100 ps aux | grep jetkvm

View live logs

ssh root@192.168.1.100
tail -f /var/log/jetkvm.log

Reset everything (if stuck)

ssh root@192.168.1.100
rm /userdata/kvm_config.json
systemctl restart jetkvm

Testing Your Changes

Manual Testing

  1. Deploy your changes: ./dev_deploy.sh -r <IP>
  2. Open browser: http://<IP>
  3. Test your feature
  4. Check logs: ssh root@<IP> tail -f /var/log/jetkvm.log

Automated Testing

# Run all tests
./dev_deploy.sh -r <IP> --run-go-tests

# Frontend linting
cd ui && npm run lint

API Testing

# Test login endpoint
curl -X POST http://<IP>/auth/password-local \
  -H "Content-Type: application/json" \
  -d '{"password": "test123"}'

Common Issues & Solutions

"Build failed" or "Permission denied"

# Fix permissions
ssh root@<IP> chmod +x /userdata/jetkvm/bin/jetkvm_app_debug

# Clean and rebuild
go clean -modcache
go mod tidy
make build_dev

"Can't connect to device"

# Check network
ping <IP>

# Check SSH
ssh root@<IP> echo "Connection OK"

"Frontend not updating"

# Clear cache and rebuild
cd ui
npm cache clean --force
rm -rf node_modules
npm install

"Device UI Fails to Build"

If while trying to build you run into an error message similar to :

In file included from /workspaces/kvm/internal/native/cgo/ctrl.c:15:
/workspaces/kvm/internal/native/cgo/ui_index.h:4:10: fatal error: ui/ui.h: No such file or directory
 #include "ui/ui.h"
          ^~~~~~~~~
compilation terminated.

This means that your system didn't create the directory-link to from ./internal/native/cgo/ui to ./internal/native/eez/src/ui when the repository was checked out. You can verify this is the case if ./internal/native/cgo/ui appears as a plain text file with only the textual contents:

../eez/src/ui

If this happens to you need to enable git creation of symbolic links either globally or for the KVM repository:

   # Globally enable git to create symlinks
   git config --global core.symlinks true
   git restore internal/native/cgo/ui
   # Enable git to create symlinks only in this project
   git config core.symlinks true
   git restore internal/native/cgo/ui

Or if you want to manually create the symlink use:

   # linux
   cd internal/native/cgo
   rm ui
   ln -s ../eez/src/ui ui
   rem Windows
   cd internal/native/cgo
   del ui
   mklink /d ui ..\eez\src\ui

Next Steps

Adding a New Feature

  1. Backend: Add API endpoint in web.go
  2. Config: Add settings in config.go
  3. Frontend: Add UI in ui/src/routes/
  4. Test: Deploy and test with ./dev_deploy.sh

Code Style

  • Go: Follow standard Go conventions
  • TypeScript: Use TypeScript for type safety
  • React: Keep components small and reusable
  • Localization: Ensure all user-facing strings in the frontend are localized

Environment Variables

# Enable debug logging
export LOG_TRACE_SCOPES="jetkvm,cloud,websocket,native,jsonrpc"

# Frontend development
export JETKVM_PROXY_URL="ws://<IP>"

Need Help?

  1. Check logs first: ssh root@<IP> tail -f /var/log/jetkvm.log
  2. Search issues: GitHub Issues
  3. Ask on Discord: JetKVM Discord
  4. Read docs: JetKVM Documentation

Contributing

Ready to contribute?

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test thoroughly
  5. Submit a pull request

Before submitting

  • Code works on device
  • Tests pass
  • Code follows style guidelines
  • Frontend user-facing strings localized
  • Documentation updated (if needed)

Advanced Topics

Performance Profiling

  1. Enable Developer Mode on your JetKVM device
  2. Add a password on the Access tab
# Access profiling
curl http://api:$JETKVM_PASSWORD@YOUR_DEVICE_IP/developer/pprof/

Advanced Environment Variables

# Enable trace logging (useful for debugging)
export LOG_TRACE_SCOPES="jetkvm,cloud,websocket,native,jsonrpc"

# For frontend development
export JETKVM_PROXY_URL="ws://<JETKVM_IP>"

# Enable SSL in development
export USE_SSL=true

Configuration Management

The application uses a JSON configuration file stored at /userdata/kvm_config.json.

Adding New Configuration Options

  1. Update the Config struct in config.go:

    type Config struct {
        // ... existing fields
        NewFeatureEnabled bool `json:"new_feature_enabled"`
    }
    
  2. Update the default configuration:

    var defaultConfig = &Config{
        // ... existing defaults
        NewFeatureEnabled: false,
    }
    
  3. Add migration logic if needed for existing installations

LVGL Build

We modified the LVGL code a little bit to remove unused fonts and examples. The patches are generated by

git diff --cached --diff-filter=d > ../internal/native/cgo/lvgl-minify.patch && \
git diff --name-only --diff-filter=D --cached > ../internal/native/cgo/lvgl-minify.del

Localization

The browser/client frontend uses the paraglide-js plug-in from the inlang.com project to allow compile-time validated localization of all user-facing UI strings in the browser/client UI. This includes title, text, name, description, placeholder, label, aria-label, message attributes (such as confirmText, unit, badge, tag, or flag), HTML element text (such as <h?>, <span>, or <p> elements), notifications messages, and option label strings, etc.

We do not translate the console log messages, CSS class names, theme names, nor the various value strings (e.g. for value/label pair options), nor URL routes.

The localizations are stored in .json files in the ui/localizations/messages directory, with one language-per-file using the ISO 3166-1 alpha-2 country code (e.g. en for English, de for German, etc.)

m-function-matcher

The translations are extracted into language files (e.g. en.json for English) and then paraglide-js compiles them into helpers for use with the m-function-matcher. An example:

<SettingsPageHeader
  title={m.extensions_atx_power_control()}
  description={m.extensions_atx_power_control_description()}
/>

shakespere plug-in

If you enable the Sherlock plug-in, the localized text "tooltip" is shown in the VSCode editor after any localized text in the language you've selected for preview. In this image, it's the blue text at the end of the line :

Showing the translation preview

Process

Localizing a UI
  1. Locate a string that is visible to the end user on the client/browser

  2. Assign that string a "key" that reflects the logical meaning of the string in snake-case (look at existing localizations for examples), for example if there's a string This is a test on the thing edit page it would be "thing_edit_this_is_a_test"

    "thing_edit_this_is_a_test": "This is a test",
    
  3. Add the key and string to the en.json like this:

    • Note if the string has replacement parameters (line a user-entered name), the syntax for the localized string has { } around the replacement token (e.g. This is your name: {name}). An complex example:
    {m.mount_button_showing_results({
       from: indexOfFirstFile + 1,
       to: Math.min(indexOfLastFile, onStorageFiles.length),
       total: onStorageFiles.length
    })}
    
  4. Save the en.json file and execute npm run i18n to resort the language files, validate the translations, and create the m-functions

  5. Edit the .tsx file and replace the string with the calls to the new m-function which will be the key-string you chose in snake-case. For example This is a test in thing edit page turns into m.thing_edit_this_is_a_test()

    • Note if the string has a replacement token, supply that to the m-function, for example for the literal I will call you {name}, use m.profile_i_will_call_you({ name: edit.value })
  6. When all your strings are extracted, run npm run i18n:machine-translate to get a first-stab at the translations for the other supported languages. Make sure you use an LLM (you can use aifiesta to use multiple LLMs) or a translator of some form to back-translate each new machine-generation in each language to ensure those terms translate reasonably.

Adding a new language

  1. Get the ISO 3166-1 alpha-2 country code (for example AT for Austria)
  2. Create a new file in the ui/localization/messages directory (example at.json)
  3. Add the new country code to the ui/localizations/settings.json file in both the "locales" and the "languageTags" section (inlang and Sherlock aren't exactly current to each other, so we need it in both places).
  4. That file also declares the baseLocale/sourceLanguageTag which is "en" because this project started out in English. Do NOT change that.
  5. Run npm run i18n:machine-translate to do an initial pass at localizing all existing messages to the new language.
    • Note you will get an error DB has been closed, ignore that message, we're not using a database.
    • Note you likely will get errors while running this command due to rate limits and such (it uses anonymous Google Translate). Just keep running the command over and over... it'll translate a bunch each time until it says Machine translate complete

Other notes

  • Run npm run i18n:validate to ensure that language files and settings are well-formed.
  • Run npm run i18n:find-excess to look for extra keys in other language files that have been deleted from the master-list in en.json.
  • Run npm run i18n:find-dupes to look for multiple keys in en.json that have the same translated value (this is normal)
  • Run npm run i18n:find-unused to look for keys in en.json that are not referenced in the UI anywhere.
    • Note there are a few that are not currently used, only concern yourself with ones you obsoleted.
  • Run npm run i18n:audit to do all the above checks.
  • Using inlang CLI to support the npm commands.
  • You can install the Sherlock VS Code extension in your devcontainer.

Happy coding!

For more information, visit the JetKVM Documentation or join our Discord Server.