Skip to content

Latest commit

 

History

History
597 lines (418 loc) · 21.4 KB

File metadata and controls

597 lines (418 loc) · 21.4 KB

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 /userdata/jetkvm/last.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
│   ├── sync/                 # Synchronization primatives with automatic logging (if synctrace enabled)
│   ├── timesync/             # Time sync/NTP implementation
│   ├── tzdata/               # Timezone data and generation
│   ├── udhcpc/               # DHCP implementation
│   ├── usbgadget/            # USB gadget
│   ├── utils/                # SSH handling
│   └── websecure/            # TLS certificate management
├── pkg/                      # External packages that have customizations
│   ├── myip/                 # Get public IP information
│   └── nmlite/               # Network link manager
├── 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
  • tailscale.go - Tailscale status and control-server logic
  • ui/src/routes/ - Add new pages here
  • ui/src/components/ - Add new UI components here

Development Modes

Full Development (Recommended)

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 ci
./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

The file /userdata/jetkvm/last.log contains the JetKVM logs. You can view live logs with:

ssh root@192.168.1.100
tail -f /userdata/jetkvm/last.log

Reset everything (if stuck)

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

Debug native code with gdbserver

Change the TARGET_IP in .vscode/settings.json to your JetKVM device IP, then set breakpoints in your native code and start the Debug Native configuration in VSCode.

The code and GDB server will be deployed automatically.


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 /userdata/jetkvm/last.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"}'

Tailscale control server testing

JetKVM exposes Tailscale control-server configuration through JSON-RPC so self-hosted control planes (for example Headscale) can be used.

  • getTailscaleStatus returns current state and effective controlURL
  • getTailscaleControlURL returns the effective control server URL
  • setTailscaleControlURL updates and persists the URL (empty value resets to default)

Notes:

  • URLs must be http:// or https:// and include a host.
  • Query strings, fragments, user info, and non-root paths are rejected.
  • Control server changes are applied with tailscale set --login-server=....
  • When apply fails, the previous configured URL is restored and not persisted.

End to End Testing

The UI has been set up with some end-to-end tests to ensure basic functionality. It's ideal that as you add featured, you add new tests and update existing ones. At minimum, ensure that existing end-to-end tests continue to pass.

Setup

The end-to-end tests require a connection to GitHub and the GitHub GH CLI to be installed and authorized. See installation instructions. After confirming the GH install works, authorize the CLI using

gh auth login

Running the tests

Before starting a pull-request (PR) on GitHub, make sure that the system still passes all end-to-end tests. Use the following command after ensuring the setup above has been completed. The test will do a complete native, UI build, and device service. It will then ask for the IP of your test device. Warning, this will deploy your changes to the specified JetKVM device, so recovery may be required if something severe breaks. It may also reset the configuration of the test device, so be prepared to re-adopt and configure when done. You will need to ensure the KVM is connected to an HDMI and USB port of an actual machine that is on and active so that keyboard status, mouse movement, and display capture are testable.

make test_e2e DEVICE_IP=<IP>

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

Build is unstable even before you changed anything

Make sure you clean up your node modules and do an npm ci (not npm i) to ensure that you get the exact packages required by package-lock.json. This is especially important when switching branches!

cd ui && rm -rf node_modules/ && npm ci && cd ..

If you are working on upgrades to the UI packages use this command to wipe the slate clean and get a new valid package-lock.json:

cd ui && rm -rf node_modules/ package-lock.json && npm i && cd ..

Device panics or becomes unresponsive

You can also run the device-side go code under a debug session to view the logs as the device is booting up and being used. To do this use the following command in your development command-line (where the IP is the JetKVM device's IP on your network) to see a very detailed synctrace of all mutex activity:

./dev_deploy.sh -r <IP> --enable-sync-trace

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 /userdata/jetkvm/last.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 ui/localization/messages/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:resort to resort the language files, npm run i18n:validate to validate the translations, and npm run i18n:compile to create the m-functions (you can use npm run i18n to do all three steps in order)

  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 empty file in the ui/localization/messages directory (example at.json)
  3. Add the new country code to the ui/localization/jetKVM.UI.inlang/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). That file also declares the baseLocale/sourceLanguageTag which is "en" because this project started out in English. Do NOT change that.
  4. Add the locale name of the language to all the ui/localization/messages/ files (example "locale_at.json": "Österreichisches Deutsch",)
    • In the en.json file, use the name of the language in that language. For example "locale_es": "Español".
    • In all other translation files, use the name of the language in the language of the containing file (example, in local_da.json (Danish), we have "locale_de": "Tysk", for German).
  5. Run npm run i18n:machine-translate to do an initial pass at localizing all other existing messages to the new language then correct anything that looks incorrect. We're aiming for translations that make sense to the native speakers of the target 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.