Compare commits

..

1 Commits

Author SHA1 Message Date
Aveline cc6effa70f
Merge cbf1c7fba6 into ea068414dc 2025-09-11 23:58:18 +02:00
11 changed files with 37 additions and 141 deletions

View File

@ -301,14 +301,13 @@ export JETKVM_PROXY_URL="ws://<IP>"
### Performance Profiling
1. Enable `Developer Mode` on your JetKVM device
2. Add a password on the `Access` tab
```bash
# Access profiling
curl http://api:$JETKVM_PASSWORD@YOUR_DEVICE_IP/developer/pprof/
```
# Enable profiling
go build -o bin/jetkvm_app -ldflags="-X main.enableProfiling=true" cmd/main.go
# Access profiling
curl http://<IP>:6060/debug/pprof/
```
### Advanced Environment Variables
```bash

View File

@ -63,17 +63,14 @@ build_dev_test: build_test2json build_gotestsum
frontend:
cd ui && npm ci && npm run build:device && \
find ../static/ \
find ../static/assets \
-type f \
\( -name '*.js' \
-o -name '*.css' \
-o -name '*.html' \
-o -name '*.ico' \
-o -name '*.png' \
-o -name '*.jpg' \
-o -name '*.jpeg' \
-o -name '*.gif' \
-o -name '*.svg' \
-o -name '*.webp' \
-o -name '*.woff2' \
\) \

View File

@ -179,9 +179,8 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
}
func (u *UsbGadget) scheduleAutoRelease(key byte) {
u.log.Trace().Msg("scheduling autoRelease")
u.kbdAutoReleaseLock.Lock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease scheduled")
defer u.kbdAutoReleaseLock.Unlock()
if u.kbdAutoReleaseTimer != nil {
u.kbdAutoReleaseTimer.Stop()
@ -193,9 +192,8 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) {
}
func (u *UsbGadget) cancelAutoRelease() {
u.log.Trace().Msg("cancelling autoRelease")
u.kbdAutoReleaseLock.Lock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease cancelled")
defer u.kbdAutoReleaseLock.Unlock()
if u.kbdAutoReleaseTimer != nil {
u.kbdAutoReleaseTimer.Stop()
@ -203,9 +201,10 @@ func (u *UsbGadget) cancelAutoRelease() {
}
func (u *UsbGadget) DelayAutoRelease() {
u.log.Trace().Msg("delaying autoRelease")
u.kbdAutoReleaseLock.Lock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease delayed")
defer u.kbdAutoReleaseLock.Unlock()
u.log.Trace().Msg("delaying auto-release")
if u.kbdAutoReleaseTimer == nil {
return
@ -217,9 +216,8 @@ func (u *UsbGadget) DelayAutoRelease() {
}
func (u *UsbGadget) performAutoRelease(key byte) {
u.log.Trace().Msg("performing autoRelease")
u.kbdAutoReleaseLock.Lock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease performed")
defer u.kbdAutoReleaseLock.Unlock()
select {
case <-u.keyboardStateCtx.Done():
@ -466,10 +464,9 @@ func (u *UsbGadget) keypressReport(key byte, press bool, autoRelease bool) (Keys
if press {
{
u.log.Trace().Msg("acquiring kbdAutoReleaseLock to update last key")
u.kbdAutoReleaseLock.Lock()
u.kbdAutoReleaseLastKey = key
unlockWithLog(&u.kbdAutoReleaseLock, u.log, "last key updated")
u.kbdAutoReleaseLock.Unlock()
}
if autoRelease {

View File

@ -9,7 +9,6 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/rs/zerolog"
@ -165,8 +164,3 @@ func (u *UsbGadget) resetLogSuppressionCounter(counterName string) {
u.logSuppressionCounter[counterName] = 0
}
}
func unlockWithLog(lock *sync.Mutex, logger *zerolog.Logger, msg string, args ...any) {
logger.Trace().Msgf(msg, args...)
lock.Unlock()
}

Binary file not shown.

View File

@ -1,77 +0,0 @@
#!/usr/bin/env bash
#
# Exit immediately if a command exits with a non-zero status
set -e
C_RST="$(tput sgr0)"
C_ERR="$(tput setaf 1)"
C_OK="$(tput setaf 2)"
C_WARN="$(tput setaf 3)"
C_INFO="$(tput setaf 5)"
msg() { printf '%s%s%s\n' $2 "$1" $C_RST; }
msg_info() { msg "$1" $C_INFO; }
msg_ok() { msg "$1" $C_OK; }
msg_err() { msg "$1" $C_ERR; }
msg_warn() { msg "$1" $C_WARN; }
# Get the latest release information
msg_info "Getting latest release information ..."
LATEST_RELEASE=$(curl -s \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/netbootxyz/netboot.xyz/releases | jq '
[.[] | select(.prerelease == false and .draft == false and .assets != null and (.assets | length > 0))] |
sort_by(.created_at) |
.[-1]')
# Extract version, download URL, and digest
VERSION=$(echo "$LATEST_RELEASE" | jq -r '.tag_name')
ISO_URL=$(echo "$LATEST_RELEASE" | jq -r '.assets[] | select(.name == "netboot.xyz-multiarch.iso") | .browser_download_url')
EXPECTED_CHECKSUM=$(echo "$LATEST_RELEASE" | jq -r '.assets[] | select(.name == "netboot.xyz-multiarch.iso") | .digest' | sed 's/sha256://')
msg_ok "Latest version: $VERSION"
msg_ok "ISO URL: $ISO_URL"
msg_ok "Expected SHA256: $EXPECTED_CHECKSUM"
# Check if we already have the same version
if [ -f "resource/netboot.xyz-multiarch.iso" ]; then
msg_info "Checking current resource file ..."
# First check by checksum (fastest)
CURRENT_CHECKSUM=$(shasum -a 256 resource/netboot.xyz-multiarch.iso | awk '{print $1}')
if [ "$CURRENT_CHECKSUM" = "$EXPECTED_CHECKSUM" ]; then
msg_ok "Resource file is already up to date (version $VERSION). No update needed."
exit 0
else
msg_info "Checksums differ, proceeding with download ..."
fi
fi
# Download ISO file
TMP_ISO=$(mktemp -t netbootxyziso)
msg_info "Downloading ISO file ..."
curl -L -o "$TMP_ISO" "$ISO_URL"
# Verify SHA256 checksum
msg_info "Verifying SHA256 checksum ..."
ACTUAL_CHECKSUM=$(shasum -a 256 "$TMP_ISO" | awk '{print $1}')
if [ "$EXPECTED_CHECKSUM" = "$ACTUAL_CHECKSUM" ]; then
msg_ok "Verified SHA256 checksum."
mv -f "$TMP_ISO" "resource/netboot.xyz-multiarch.iso"
msg_ok "Updated ISO file."
git add "resource/netboot.xyz-multiarch.iso"
git commit -m "chore: update netboot.xyz-multiarch.iso to $VERSION"
msg_ok "Committed changes."
msg_ok "You can now push the changes to the remote repository."
exit 0
else
msg_err "Inconsistent SHA256 checksum."
msg_err "Expected: $EXPECTED_CHECKSUM"
msg_err "Actual: $ACTUAL_CHECKSUM"
exit 1
fi

View File

@ -6,34 +6,27 @@
<!-- These are the fonts used in the app -->
<link
rel="preload"
href="./public/fonts/CircularXXWeb-Medium.woff2"
href="/fonts/CircularXXWeb-Medium.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="./public/fonts/CircularXXWeb-Book.woff2"
href="/fonts/CircularXXWeb-Book.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="./public/fonts/CircularXXWeb-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="./public/fonts/CircularXXWeb-Black.woff2"
href="/fonts/CircularXXWeb-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<title>JetKVM</title>
<link rel="stylesheet" href="./public/fonts/fonts.css" />
<link rel="stylesheet" href="/fonts/fonts.css" />
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
@ -43,21 +36,23 @@
<meta name="theme-color" content="#051946" />
<meta name="description" content="A web-based KVM console for managing remote servers." />
<script>
function applyThemeFromPreference() {
// dark theme setup
var darkDesired = localStorage.theme === "dark" ||
// Initial theme setup
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
document.documentElement.classList.toggle("dark", darkDesired)
}
// initial theme application
applyThemeFromPreference();
window.matchMedia("(prefers-color-scheme: dark)").matches),
);
// Listen for system theme changes
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyThemeFromPreference);
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", applyThemeFromPreference);
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", ({ matches }) => {
if (!("theme" in localStorage)) {
// Only auto-switch if user hasn't manually set a theme
document.documentElement.classList.toggle("dark", matches);
}
});
</script>
</head>
<body

2
ui/public/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

View File

@ -104,7 +104,7 @@ export default function useKeyboard() {
}
};
const KEEPALIVE_INTERVAL = 75; // TODO: use an adaptive interval based on RTT later
const KEEPALIVE_INTERVAL = 75; // 200ms interval
const cancelKeepAlive = useCallback(() => {
if (keepAliveTimerRef.current) {

View File

@ -31,7 +31,6 @@ export default defineConfig(({ mode, command }) => {
esbuild: {
pure: ["console.debug"],
},
assetsInclude: ["**/*.woff2"],
build: {
outDir: isCloud ? "dist" : "../static",
rollupOptions: {

16
web.go
View File

@ -69,7 +69,8 @@ type SetupRequest struct {
}
var cachableFileExtensions = []string{
".jpg", ".jpeg", ".png", ".svg", ".gif", ".webp", ".ico", ".woff2",
".jpg", ".jpeg", ".png", ".gif", ".webp", ".woff2",
".ico",
}
func setupRouter() *gin.Engine {
@ -82,10 +83,7 @@ func setupRouter() *gin.Engine {
}),
))
staticFS, err := fs.Sub(staticFiles, "static")
if err != nil {
logger.Fatal().Err(err).Msg("failed to get rooted static files subdirectory")
}
staticFS, _ := fs.Sub(staticFiles, "static")
staticFileServer := http.StripPrefix("/static", statigz.FileServer(
staticFS.(fs.ReadDirFS),
))
@ -111,17 +109,9 @@ func setupRouter() *gin.Engine {
c.Next()
})
r.GET("/robots.txt", func(c *gin.Context) {
c.Header("Content-Type", "text/plain")
c.Header("Cache-Control", "public, max-age=31536000, immutable") // Cache for 1 year
c.String(http.StatusOK, "User-agent: *\nDisallow: /")
})
r.Any("/static/*w", func(c *gin.Context) {
staticFileServer.ServeHTTP(c.Writer, c.Request)
})
// Public routes (no authentication required)
r.POST("/auth/login-local", handleLogin)
// We use this to determine if the device is setup