Compare commits

..

6 Commits

Author SHA1 Message Date
Aveline 91aab6074f
Merge 0bb3a6f3c2 into 37b1a8bf34 2025-09-12 11:11:36 +02:00
Aveline 37b1a8bf34
docs: update pprof section of DEVELOPMENT.md (#802) 2025-09-12 11:11:28 +02:00
Siyuan Miao 0bb3a6f3c2 chore: update comment for KEEPALIVE_INTERVAL 2025-09-12 10:41:40 +02:00
Siyuan Miao 67ea3c0432 chore: update logging for kbdAutoReleaseLock 2025-09-12 10:41:23 +02:00
Marc Brooks ca8b06f4cf
chore: enhance the gzip and cacheable handling of static files
Add SVG and ICO to cacheable files.
Emit robots.txt directly.
Recognize WOFF2 (font) files as assets (so the get the immutable treatment)
Pre-gzip the entire /static/ directory (not just /static/assets/) and include SVG, ICO, and HTML files
Ensure fonts.css is processed by vite/rollup so that the preload and css reference the same immutable files (which get long-cached with hashes)
Add CircularXXWeb-Black to the preload list as it is used in the hot-path.
Handle system-driven color-scheme changes from dark to light correctly.
2025-09-12 08:41:41 +02:00
Aveline 33e099f258
update netboot.xyz-multiarch.iso to 2.0.88 (#799)
* chore: update netboot.xyz-multiarch.iso to 2.0.88

* feat: add script to update netboot.xyz iso
2025-09-12 08:41:17 +02:00
11 changed files with 140 additions and 36 deletions

View File

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

View File

@ -63,14 +63,17 @@ build_dev_test: build_test2json build_gotestsum
frontend:
cd ui && npm ci && npm run build:device && \
find ../static/assets \
find ../static/ \
-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,8 +179,9 @@ func (u *UsbGadget) SetOnKeysDownChange(f func(state KeysDownState)) {
}
func (u *UsbGadget) scheduleAutoRelease(key byte) {
u.log.Trace().Msg("scheduling autoRelease")
u.kbdAutoReleaseLock.Lock()
defer u.kbdAutoReleaseLock.Unlock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease scheduled")
if u.kbdAutoReleaseTimer != nil {
u.kbdAutoReleaseTimer.Stop()
@ -192,8 +193,9 @@ func (u *UsbGadget) scheduleAutoRelease(key byte) {
}
func (u *UsbGadget) cancelAutoRelease() {
u.log.Trace().Msg("cancelling autoRelease")
u.kbdAutoReleaseLock.Lock()
defer u.kbdAutoReleaseLock.Unlock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease cancelled")
if u.kbdAutoReleaseTimer != nil {
u.kbdAutoReleaseTimer.Stop()
@ -201,10 +203,9 @@ func (u *UsbGadget) cancelAutoRelease() {
}
func (u *UsbGadget) DelayAutoRelease() {
u.log.Trace().Msg("delaying autoRelease")
u.kbdAutoReleaseLock.Lock()
defer u.kbdAutoReleaseLock.Unlock()
u.log.Trace().Msg("delaying auto-release")
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease delayed")
if u.kbdAutoReleaseTimer == nil {
return
@ -216,8 +217,9 @@ func (u *UsbGadget) DelayAutoRelease() {
}
func (u *UsbGadget) performAutoRelease(key byte) {
u.log.Trace().Msg("performing autoRelease")
u.kbdAutoReleaseLock.Lock()
defer u.kbdAutoReleaseLock.Unlock()
defer unlockWithLog(&u.kbdAutoReleaseLock, u.log, "autoRelease performed")
select {
case <-u.keyboardStateCtx.Done():
@ -464,9 +466,10 @@ 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
u.kbdAutoReleaseLock.Unlock()
unlockWithLog(&u.kbdAutoReleaseLock, u.log, "last key updated")
}
if autoRelease {

View File

@ -9,6 +9,7 @@ import (
"path/filepath"
"strconv"
"strings"
"sync"
"time"
"github.com/rs/zerolog"
@ -164,3 +165,8 @@ 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.

77
scripts/update_netboot_xyz.sh Executable file
View File

@ -0,0 +1,77 @@
#!/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,27 +6,34 @@
<!-- These are the fonts used in the app -->
<link
rel="preload"
href="/fonts/CircularXXWeb-Medium.woff2"
href="./public/fonts/CircularXXWeb-Medium.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/CircularXXWeb-Book.woff2"
href="./public/fonts/CircularXXWeb-Book.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/fonts/CircularXXWeb-Regular.woff2"
href="./public/fonts/CircularXXWeb-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="./public/fonts/CircularXXWeb-Black.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<title>JetKVM</title>
<link rel="stylesheet" href="/fonts/fonts.css" />
<link rel="stylesheet" href="./public/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" />
@ -36,23 +43,21 @@
<meta name="theme-color" content="#051946" />
<meta name="description" content="A web-based KVM console for managing remote servers." />
<script>
// Initial theme setup
document.documentElement.classList.toggle(
"dark",
localStorage.theme === "dark" ||
function applyThemeFromPreference() {
// dark theme setup
var darkDesired = localStorage.theme === "dark" ||
(!("theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches),
);
window.matchMedia("(prefers-color-scheme: dark)").matches)
document.documentElement.classList.toggle("dark", darkDesired)
}
// initial theme application
applyThemeFromPreference();
// Listen for system theme changes
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);
}
});
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyThemeFromPreference);
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", applyThemeFromPreference);
</script>
</head>
<body

View File

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

View File

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

View File

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

16
web.go
View File

@ -69,8 +69,7 @@ type SetupRequest struct {
}
var cachableFileExtensions = []string{
".jpg", ".jpeg", ".png", ".gif", ".webp", ".woff2",
".ico",
".jpg", ".jpeg", ".png", ".svg", ".gif", ".webp", ".ico", ".woff2",
}
func setupRouter() *gin.Engine {
@ -83,7 +82,10 @@ func setupRouter() *gin.Engine {
}),
))
staticFS, _ := fs.Sub(staticFiles, "static")
staticFS, err := fs.Sub(staticFiles, "static")
if err != nil {
logger.Fatal().Err(err).Msg("failed to get rooted static files subdirectory")
}
staticFileServer := http.StripPrefix("/static", statigz.FileServer(
staticFS.(fs.ReadDirFS),
))
@ -109,9 +111,17 @@ 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