mirror of https://github.com/jetkvm/kvm.git
Compare commits
1 Commits
c853d3fa65
...
58aee9a5a7
| Author | SHA1 | Date |
|---|---|---|
|
|
58aee9a5a7 |
|
|
@ -449,14 +449,13 @@ export JETKVM_PROXY_URL="ws://<IP>"
|
||||||
|
|
||||||
### Performance Profiling
|
### Performance Profiling
|
||||||
|
|
||||||
1. Enable `Developer Mode` on your JetKVM device
|
|
||||||
2. Add a password on the `Access` tab
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Access profiling
|
# Enable profiling
|
||||||
curl http://api:$JETKVM_PASSWORD@YOUR_DEVICE_IP/developer/pprof/
|
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
|
### Advanced Environment Variables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
39
Makefile
39
Makefile
|
|
@ -17,7 +17,6 @@ dev_env: build_audio_deps
|
||||||
JETKVM_HOME ?= $(HOME)/.jetkvm
|
JETKVM_HOME ?= $(HOME)/.jetkvm
|
||||||
TOOLCHAIN_DIR ?= $(JETKVM_HOME)/rv1106-system
|
TOOLCHAIN_DIR ?= $(JETKVM_HOME)/rv1106-system
|
||||||
AUDIO_LIBS_DIR ?= $(JETKVM_HOME)/audio-libs
|
AUDIO_LIBS_DIR ?= $(JETKVM_HOME)/audio-libs
|
||||||
|
|
||||||
BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
BUILDDATE ?= $(shell date -u +%FT%T%z)
|
BUILDDATE ?= $(shell date -u +%FT%T%z)
|
||||||
BUILDTS ?= $(shell date -u +%s)
|
BUILDTS ?= $(shell date -u +%s)
|
||||||
|
|
@ -29,21 +28,9 @@ VERSION ?= 0.4.6
|
||||||
ALSA_VERSION ?= 1.2.14
|
ALSA_VERSION ?= 1.2.14
|
||||||
OPUS_VERSION ?= 1.5.2
|
OPUS_VERSION ?= 1.5.2
|
||||||
|
|
||||||
# Set PKG_CONFIG_PATH globally for all targets that use CGO with audio libraries
|
|
||||||
export PKG_CONFIG_PATH := $(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/utils:$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)
|
|
||||||
|
|
||||||
# Optimization flags for ARM Cortex-A7 with NEON
|
# Optimization flags for ARM Cortex-A7 with NEON
|
||||||
OPTIM_CFLAGS := -O3 -mfpu=neon -mtune=cortex-a7 -mfloat-abi=hard -ftree-vectorize -ffast-math -funroll-loops
|
OPTIM_CFLAGS := -O3 -mfpu=neon -mtune=cortex-a7 -mfloat-abi=hard -ftree-vectorize -ffast-math -funroll-loops
|
||||||
|
|
||||||
# Cross-compilation environment for ARM - exported globally
|
|
||||||
export GOOS := linux
|
|
||||||
export GOARCH := arm
|
|
||||||
export GOARM := 7
|
|
||||||
export CC := $(TOOLCHAIN_DIR)/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
|
|
||||||
export CGO_ENABLED := 1
|
|
||||||
export CGO_CFLAGS := $(OPTIM_CFLAGS) -I$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/celt
|
|
||||||
export CGO_LDFLAGS := -L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static
|
|
||||||
|
|
||||||
PROMETHEUS_TAG := github.com/prometheus/common/version
|
PROMETHEUS_TAG := github.com/prometheus/common/version
|
||||||
KVM_PKG_NAME := github.com/jetkvm/kvm
|
KVM_PKG_NAME := github.com/jetkvm/kvm
|
||||||
|
|
||||||
|
|
@ -66,6 +53,11 @@ hash_resource:
|
||||||
|
|
||||||
build_dev: build_audio_deps hash_resource
|
build_dev: build_audio_deps hash_resource
|
||||||
@echo "Building..."
|
@echo "Building..."
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 \
|
||||||
|
CC=$(TOOLCHAIN_DIR)/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc \
|
||||||
|
CGO_ENABLED=1 \
|
||||||
|
CGO_CFLAGS="$(OPTIM_CFLAGS) -I$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/celt" \
|
||||||
|
CGO_LDFLAGS="-L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static" \
|
||||||
go build \
|
go build \
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||||
$(GO_RELEASE_BUILD_ARGS) \
|
$(GO_RELEASE_BUILD_ARGS) \
|
||||||
|
|
@ -89,6 +81,11 @@ build_dev_test: build_audio_deps build_test2json build_gotestsum
|
||||||
test_pkg_name=$$(echo $$test | sed 's/^.\///g'); \
|
test_pkg_name=$$(echo $$test | sed 's/^.\///g'); \
|
||||||
test_pkg_full_name=$(KVM_PKG_NAME)/$$(echo $$test | sed 's/^.\///g'); \
|
test_pkg_full_name=$(KVM_PKG_NAME)/$$(echo $$test | sed 's/^.\///g'); \
|
||||||
test_filename=$$(echo $$test_pkg_name | sed 's/\//__/g')_test; \
|
test_filename=$$(echo $$test_pkg_name | sed 's/\//__/g')_test; \
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 \
|
||||||
|
CC=$(TOOLCHAIN_DIR)/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc \
|
||||||
|
CGO_ENABLED=1 \
|
||||||
|
CGO_CFLAGS="$(OPTIM_CFLAGS) -I$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/celt" \
|
||||||
|
CGO_LDFLAGS="-L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static" \
|
||||||
go test -v \
|
go test -v \
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||||
$(GO_BUILD_ARGS) \
|
$(GO_BUILD_ARGS) \
|
||||||
|
|
@ -102,17 +99,14 @@ build_dev_test: build_audio_deps build_test2json build_gotestsum
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
cd ui && npm ci && npm run build:device && \
|
cd ui && npm ci && npm run build:device && \
|
||||||
find ../static/ \
|
find ../static/assets \
|
||||||
-type f \
|
-type f \
|
||||||
\( -name '*.js' \
|
\( -name '*.js' \
|
||||||
-o -name '*.css' \
|
-o -name '*.css' \
|
||||||
-o -name '*.html' \
|
|
||||||
-o -name '*.ico' \
|
|
||||||
-o -name '*.png' \
|
-o -name '*.png' \
|
||||||
-o -name '*.jpg' \
|
-o -name '*.jpg' \
|
||||||
-o -name '*.jpeg' \
|
-o -name '*.jpeg' \
|
||||||
-o -name '*.gif' \
|
-o -name '*.gif' \
|
||||||
-o -name '*.svg' \
|
|
||||||
-o -name '*.webp' \
|
-o -name '*.webp' \
|
||||||
-o -name '*.woff2' \
|
-o -name '*.woff2' \
|
||||||
\) \
|
\) \
|
||||||
|
|
@ -126,6 +120,11 @@ dev_release: frontend build_dev
|
||||||
|
|
||||||
build_release: frontend build_audio_deps hash_resource
|
build_release: frontend build_audio_deps hash_resource
|
||||||
@echo "Building release..."
|
@echo "Building release..."
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 \
|
||||||
|
CC=$(TOOLCHAIN_DIR)/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc \
|
||||||
|
CGO_ENABLED=1 \
|
||||||
|
CGO_CFLAGS="$(OPTIM_CFLAGS) -I$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/celt" \
|
||||||
|
CGO_LDFLAGS="-L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static" \
|
||||||
go build \
|
go build \
|
||||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" \
|
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" \
|
||||||
$(GO_RELEASE_BUILD_ARGS) \
|
$(GO_RELEASE_BUILD_ARGS) \
|
||||||
|
|
@ -150,6 +149,9 @@ lint: lint-go lint-ui
|
||||||
lint-go: build_audio_deps
|
lint-go: build_audio_deps
|
||||||
@echo "Running golangci-lint..."
|
@echo "Running golangci-lint..."
|
||||||
@mkdir -p static && touch static/.gitkeep
|
@mkdir -p static && touch static/.gitkeep
|
||||||
|
CGO_ENABLED=1 \
|
||||||
|
CGO_CFLAGS="-I$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/celt" \
|
||||||
|
CGO_LDFLAGS="-L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static" \
|
||||||
golangci-lint run --verbose
|
golangci-lint run --verbose
|
||||||
|
|
||||||
# Run both Go and UI linting with auto-fix
|
# Run both Go and UI linting with auto-fix
|
||||||
|
|
@ -160,6 +162,9 @@ lint-fix: lint-go-fix lint-ui-fix
|
||||||
lint-go-fix: build_audio_deps
|
lint-go-fix: build_audio_deps
|
||||||
@echo "Running golangci-lint with auto-fix..."
|
@echo "Running golangci-lint with auto-fix..."
|
||||||
@mkdir -p static && touch static/.gitkeep
|
@mkdir -p static && touch static/.gitkeep
|
||||||
|
CGO_ENABLED=1 \
|
||||||
|
CGO_CFLAGS="-I$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/include -I$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/celt" \
|
||||||
|
CGO_LDFLAGS="-L$(AUDIO_LIBS_DIR)/alsa-lib-$(ALSA_VERSION)/src/.libs -lasound -L$(AUDIO_LIBS_DIR)/opus-$(OPUS_VERSION)/.libs -lopus -lm -ldl -static" \
|
||||||
golangci-lint run --fix --verbose
|
golangci-lint run --fix --verbose
|
||||||
|
|
||||||
# Run UI linting locally (mirrors GitHub workflow ui-lint.yml)
|
# Run UI linting locally (mirrors GitHub workflow ui-lint.yml)
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
//go:build arm && linux
|
|
||||||
|
|
||||||
package usbgadget
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
usbConfig = &Config{
|
|
||||||
VendorId: "0x1d6b", //The Linux Foundation
|
|
||||||
ProductId: "0x0104", //Multifunction Composite Gadget
|
|
||||||
SerialNumber: "",
|
|
||||||
Manufacturer: "JetKVM",
|
|
||||||
Product: "USB Emulation Device",
|
|
||||||
strictMode: true,
|
|
||||||
}
|
|
||||||
usbDevices = &Devices{
|
|
||||||
AbsoluteMouse: true,
|
|
||||||
RelativeMouse: true,
|
|
||||||
Keyboard: true,
|
|
||||||
MassStorage: true,
|
|
||||||
}
|
|
||||||
usbGadgetName = "jetkvm"
|
|
||||||
usbGadget *UsbGadget
|
|
||||||
)
|
|
||||||
|
|
||||||
var oldAbsoluteMouseCombinedReportDesc = []byte{
|
|
||||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
|
||||||
0x09, 0x02, // Usage (Mouse)
|
|
||||||
0xA1, 0x01, // Collection (Application)
|
|
||||||
|
|
||||||
// Report ID 1: Absolute Mouse Movement
|
|
||||||
0x85, 0x01, // Report ID (1)
|
|
||||||
0x09, 0x01, // Usage (Pointer)
|
|
||||||
0xA1, 0x00, // Collection (Physical)
|
|
||||||
0x05, 0x09, // Usage Page (Button)
|
|
||||||
0x19, 0x01, // Usage Minimum (0x01)
|
|
||||||
0x29, 0x03, // Usage Maximum (0x03)
|
|
||||||
0x15, 0x00, // Logical Minimum (0)
|
|
||||||
0x25, 0x01, // Logical Maximum (1)
|
|
||||||
0x75, 0x01, // Report Size (1)
|
|
||||||
0x95, 0x03, // Report Count (3)
|
|
||||||
0x81, 0x02, // Input (Data, Var, Abs)
|
|
||||||
0x95, 0x01, // Report Count (1)
|
|
||||||
0x75, 0x05, // Report Size (5)
|
|
||||||
0x81, 0x03, // Input (Cnst, Var, Abs)
|
|
||||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
|
||||||
0x09, 0x30, // Usage (X)
|
|
||||||
0x09, 0x31, // Usage (Y)
|
|
||||||
0x16, 0x00, 0x00, // Logical Minimum (0)
|
|
||||||
0x26, 0xFF, 0x7F, // Logical Maximum (32767)
|
|
||||||
0x36, 0x00, 0x00, // Physical Minimum (0)
|
|
||||||
0x46, 0xFF, 0x7F, // Physical Maximum (32767)
|
|
||||||
0x75, 0x10, // Report Size (16)
|
|
||||||
0x95, 0x02, // Report Count (2)
|
|
||||||
0x81, 0x02, // Input (Data, Var, Abs)
|
|
||||||
0xC0, // End Collection
|
|
||||||
|
|
||||||
// Report ID 2: Relative Wheel Movement
|
|
||||||
0x85, 0x02, // Report ID (2)
|
|
||||||
0x09, 0x38, // Usage (Wheel)
|
|
||||||
0x15, 0x81, // Logical Minimum (-127)
|
|
||||||
0x25, 0x7F, // Logical Maximum (127)
|
|
||||||
0x75, 0x08, // Report Size (8)
|
|
||||||
0x95, 0x01, // Report Count (1)
|
|
||||||
0x81, 0x06, // Input (Data, Var, Rel)
|
|
||||||
|
|
||||||
0xC0, // End Collection
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsbGadgetInit(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
usbGadget = NewUsbGadget(usbGadgetName, usbDevices, usbConfig, nil)
|
|
||||||
|
|
||||||
assert.NotNil(usbGadget)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsbGadgetStrictModeInitFail(t *testing.T) {
|
|
||||||
usbConfig.strictMode = true
|
|
||||||
u := NewUsbGadget("test", usbDevices, usbConfig, nil)
|
|
||||||
assert.Nil(t, u, "should be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUsbGadgetUDCNotBoundAfterReportDescrChanged(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
usbGadget = NewUsbGadget(usbGadgetName, usbDevices, usbConfig, nil)
|
|
||||||
assert.NotNil(usbGadget)
|
|
||||||
|
|
||||||
// release the usb gadget and create a new one
|
|
||||||
usbGadget = nil
|
|
||||||
|
|
||||||
altGadgetConfig := defaultGadgetConfig
|
|
||||||
|
|
||||||
oldAbsoluteMouseConfig := altGadgetConfig["absolute_mouse"]
|
|
||||||
oldAbsoluteMouseConfig.reportDesc = oldAbsoluteMouseCombinedReportDesc
|
|
||||||
altGadgetConfig["absolute_mouse"] = oldAbsoluteMouseConfig
|
|
||||||
|
|
||||||
usbGadget = newUsbGadget(usbGadgetName, altGadgetConfig, usbDevices, usbConfig, nil)
|
|
||||||
assert.NotNil(usbGadget)
|
|
||||||
|
|
||||||
udcs := getUdcs()
|
|
||||||
assert.Equal(1, len(udcs), "should be only one UDC")
|
|
||||||
// check if the UDC is bound
|
|
||||||
udc := udcs[0]
|
|
||||||
assert.NotNil(udc, "UDC should exist")
|
|
||||||
|
|
||||||
udcStr, err := os.ReadFile("/sys/kernel/config/usb_gadget/jetkvm/UDC")
|
|
||||||
assert.Nil(err, "usb_gadget/UDC should exist")
|
|
||||||
assert.Equal(strings.TrimSpace(udc), strings.TrimSpace(string(udcStr)), "UDC should be the same")
|
|
||||||
}
|
|
||||||
|
|
@ -86,7 +86,6 @@ func TestUsbGadgetHardwareInit(t *testing.T) {
|
||||||
|
|
||||||
// Validate gadget state
|
// Validate gadget state
|
||||||
assert.NotNil(t, gadget, "USB gadget should not be nil")
|
assert.NotNil(t, gadget, "USB gadget should not be nil")
|
||||||
validateHardwareState(t, gadget)
|
|
||||||
|
|
||||||
// Test UDC binding state
|
// Test UDC binding state
|
||||||
bound, err := gadget.IsUDCBound()
|
bound, err := gadget.IsUDCBound()
|
||||||
|
|
@ -145,7 +144,6 @@ func TestUsbGadgetHardwareReconfiguration(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
assert.NotNil(t, gadget2, "Second USB gadget should be initialized")
|
assert.NotNil(t, gadget2, "Second USB gadget should be initialized")
|
||||||
validateHardwareState(t, gadget2)
|
|
||||||
|
|
||||||
// Validate UDC binding after reconfiguration
|
// Validate UDC binding after reconfiguration
|
||||||
udcs := getUdcs()
|
udcs := getUdcs()
|
||||||
|
|
@ -189,7 +187,6 @@ func TestUsbGadgetHardwareStressTest(t *testing.T) {
|
||||||
|
|
||||||
// Validate gadget
|
// Validate gadget
|
||||||
assert.NotNil(t, gadget, "USB gadget should be created in iteration %d", i+1)
|
assert.NotNil(t, gadget, "USB gadget should be created in iteration %d", i+1)
|
||||||
validateHardwareState(t, gadget)
|
|
||||||
|
|
||||||
// Test basic operations
|
// Test basic operations
|
||||||
bound, err := gadget.IsUDCBound()
|
bound, err := gadget.IsUDCBound()
|
||||||
|
|
@ -330,4 +327,4 @@ func validateHardwareState(t *testing.T, gadget *UsbGadget) {
|
||||||
} else {
|
} else {
|
||||||
t.Logf("configfs is available")
|
t.Logf("configfs is available")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
|
@ -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
|
|
||||||
|
|
@ -6,34 +6,27 @@
|
||||||
<!-- These are the fonts used in the app -->
|
<!-- These are the fonts used in the app -->
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="./public/fonts/CircularXXWeb-Medium.woff2"
|
href="/fonts/CircularXXWeb-Medium.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="./public/fonts/CircularXXWeb-Book.woff2"
|
href="/fonts/CircularXXWeb-Book.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="./public/fonts/CircularXXWeb-Regular.woff2"
|
href="/fonts/CircularXXWeb-Regular.woff2"
|
||||||
as="font"
|
|
||||||
type="font/woff2"
|
|
||||||
crossorigin
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="./public/fonts/CircularXXWeb-Black.woff2"
|
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<title>JetKVM</title>
|
<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/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<link rel="shortcut icon" href="/favicon.ico" />
|
<link rel="shortcut icon" href="/favicon.ico" />
|
||||||
|
|
@ -43,21 +36,23 @@
|
||||||
<meta name="theme-color" content="#051946" />
|
<meta name="theme-color" content="#051946" />
|
||||||
<meta name="description" content="A web-based KVM console for managing remote servers." />
|
<meta name="description" content="A web-based KVM console for managing remote servers." />
|
||||||
<script>
|
<script>
|
||||||
function applyThemeFromPreference() {
|
// Initial theme setup
|
||||||
// dark theme setup
|
document.documentElement.classList.toggle(
|
||||||
var darkDesired = localStorage.theme === "dark" ||
|
"dark",
|
||||||
|
localStorage.theme === "dark" ||
|
||||||
(!("theme" in localStorage) &&
|
(!("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
|
// Listen for system theme changes
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyThemeFromPreference);
|
window
|
||||||
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", applyThemeFromPreference);
|
.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>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body
|
<body
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /
|
||||||
|
|
@ -27,7 +27,6 @@ export default defineConfig(({ mode, command }) => {
|
||||||
esbuild: {
|
esbuild: {
|
||||||
pure: ["console.debug"],
|
pure: ["console.debug"],
|
||||||
},
|
},
|
||||||
assetsInclude: ["**/*.woff2"],
|
|
||||||
build: {
|
build: {
|
||||||
outDir: isCloud ? "dist" : "../static",
|
outDir: isCloud ? "dist" : "../static",
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
|
|
||||||
16
web.go
16
web.go
|
|
@ -69,7 +69,8 @@ type SetupRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachableFileExtensions = []string{
|
var cachableFileExtensions = []string{
|
||||||
".jpg", ".jpeg", ".png", ".svg", ".gif", ".webp", ".ico", ".woff2",
|
".jpg", ".jpeg", ".png", ".gif", ".webp", ".woff2",
|
||||||
|
".ico",
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRouter() *gin.Engine {
|
func setupRouter() *gin.Engine {
|
||||||
|
|
@ -82,10 +83,7 @@ func setupRouter() *gin.Engine {
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
|
|
||||||
staticFS, err := fs.Sub(staticFiles, "static")
|
staticFS, _ := 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(
|
staticFileServer := http.StripPrefix("/static", statigz.FileServer(
|
||||||
staticFS.(fs.ReadDirFS),
|
staticFS.(fs.ReadDirFS),
|
||||||
))
|
))
|
||||||
|
|
@ -111,17 +109,9 @@ func setupRouter() *gin.Engine {
|
||||||
c.Next()
|
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) {
|
r.Any("/static/*w", func(c *gin.Context) {
|
||||||
staticFileServer.ServeHTTP(c.Writer, c.Request)
|
staticFileServer.ServeHTTP(c.Writer, c.Request)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Public routes (no authentication required)
|
|
||||||
r.POST("/auth/login-local", handleLogin)
|
r.POST("/auth/login-local", handleLogin)
|
||||||
|
|
||||||
// We use this to determine if the device is setup
|
// We use this to determine if the device is setup
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue