mirror of https://github.com/jetkvm/kvm.git
Compare commits
15 Commits
879720585e
...
3f9fc0f7e9
| Author | SHA1 | Date |
|---|---|---|
|
|
3f9fc0f7e9 | |
|
|
1717549578 | |
|
|
37b1a8bf34 | |
|
|
ca8b06f4cf | |
|
|
33e099f258 | |
|
|
57fbee1490 | |
|
|
0e65c0a9a9 | |
|
|
2dafb5c9c1 | |
|
|
566305549f | |
|
|
1505c37e4c | |
|
|
564eee9b00 | |
|
|
fab575dbe0 | |
|
|
97958e7b86 | |
|
|
2f7042df18 | |
|
|
2cadda4e00 |
|
|
@ -0,0 +1,126 @@
|
||||||
|
name: Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-22.04]
|
||||||
|
go: [1.21, 1.23.4]
|
||||||
|
node: [21]
|
||||||
|
goos: [linux]
|
||||||
|
goarch: [arm]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node }}
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ui
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build UI
|
||||||
|
working-directory: ui
|
||||||
|
run: npm run build:device
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Install Go Dependencies
|
||||||
|
run: |
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
- name: Build Binaries
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
run: |
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X kvm.builtAppVersion=dev-${GIT_COMMIT:0:7}" -o bin/jetkvm_app cmd/main.go
|
||||||
|
chmod 755 bin/jetkvm_app
|
||||||
|
|
||||||
|
- name: Upload Debug Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: ${{ (github.ref == 'refs/heads/main' || github.event_name == 'pull_request') && matrix.go == '1.21' }}
|
||||||
|
with:
|
||||||
|
name: jetkvm_app_debug
|
||||||
|
path: bin/jetkvm_app
|
||||||
|
|
||||||
|
comment:
|
||||||
|
name: Comment
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Generate Links
|
||||||
|
id: linksa
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ARTIFACT_ID=$(gh api repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts --jq '.artifacts[0].id')
|
||||||
|
echo "ARTIFACT_URL=https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/$ARTIFACT_ID" >> $GITHUB_ENV
|
||||||
|
echo "LATEST_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Comment on PR
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
||||||
|
else
|
||||||
|
TITLE="main branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMENT=$(cat << EOF
|
||||||
|
✅ **Build successfully for $TITLE!**
|
||||||
|
|
||||||
|
| Name | Link |
|
||||||
|
|------------------|----------------------------------------------------------------------|
|
||||||
|
| 🔗 Debug Binary | [Download](${{ env.ARTIFACT_URL }}) |
|
||||||
|
| 🔗 Latest commit | [${{ env.LATEST_COMMIT }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}) |
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
# Post Comment
|
||||||
|
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||||
|
# Look for an existing comment
|
||||||
|
COMMENT_ID=$(gh api repos/${{ github.repository }}/issues/$PR_NUMBER/comments \
|
||||||
|
--jq '.[] | select(.body | contains("✅ **Build successfully for")) | .id')
|
||||||
|
|
||||||
|
if [ -z "$COMMENT_ID" ]; then
|
||||||
|
# Create a new comment if none exists
|
||||||
|
gh pr comment $PR_NUMBER --body "$COMMENT"
|
||||||
|
else
|
||||||
|
# Update the existing comment
|
||||||
|
gh api repos/${{ github.repository }}/issues/comments/$COMMENT_ID \
|
||||||
|
--method PATCH \
|
||||||
|
-f body="$COMMENT"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# Log the comment for main branch
|
||||||
|
echo "$COMMENT"
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 21
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
working-directory: ui
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build UI
|
||||||
|
working-directory: ui
|
||||||
|
run: npm run build:device
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.21
|
||||||
|
|
||||||
|
- name: Build Release Binaries
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
GOOS=linux GOARCH=arm GOARM=7 go build -ldflags="-s -w -X kvm.builtAppVersion=${REF:11}" -o bin/jetkvm_app cmd/main.go
|
||||||
|
chmod 755 bin/jetkvm_app
|
||||||
|
|
||||||
|
- name: Create checksum
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
SUM=$(shasum -a 256 bin/jetkvm_app | cut -d ' ' -f 1)
|
||||||
|
echo -e "\n#### SHA256 Checksum\n\`\`\`\n$SUM bin/jetkvm_app\n\`\`\`\n" >> ./RELEASE_CHANGELOG
|
||||||
|
echo -e "$SUM bin/jetkvm_app\n" > checksums.txt
|
||||||
|
|
||||||
|
- name: Create Release Branch
|
||||||
|
env:
|
||||||
|
REF: ${{ github.ref }}
|
||||||
|
run: |
|
||||||
|
BRANCH=release/${REF:10}
|
||||||
|
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --local user.name "github-actions[bot]"
|
||||||
|
git checkout -b ${BRANCH}
|
||||||
|
git push -u origin ${BRANCH}
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||||
|
body_path: ./RELEASE_CHANGELOG
|
||||||
|
|
||||||
|
- name: Upload JetKVM binary
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: bin/jetkvm_app
|
||||||
|
asset_name: jetkvm_app
|
||||||
|
asset_content_type: application/octet-stream
|
||||||
|
|
||||||
|
- name: Upload checksum
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: ./checksums.txt
|
||||||
|
asset_name: checksums.txt
|
||||||
|
asset_content_type: text/plain
|
||||||
|
|
@ -301,13 +301,14 @@ export JETKVM_PROXY_URL="ws://<IP>"
|
||||||
|
|
||||||
### Performance Profiling
|
### Performance Profiling
|
||||||
|
|
||||||
```bash
|
1. Enable `Developer Mode` on your JetKVM device
|
||||||
# Enable profiling
|
2. Add a password on the `Access` tab
|
||||||
go build -o bin/jetkvm_app -ldflags="-X main.enableProfiling=true" cmd/main.go
|
|
||||||
|
|
||||||
|
```bash
|
||||||
# Access profiling
|
# Access profiling
|
||||||
curl http://<IP>:6060/debug/pprof/
|
curl http://api:$JETKVM_PASSWORD@YOUR_DEVICE_IP/developer/pprof/
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced Environment Variables
|
### Advanced Environment Variables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
5
Makefile
5
Makefile
|
|
@ -63,14 +63,17 @@ build_dev_test: build_test2json build_gotestsum
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
cd ui && npm ci && npm run build:device && \
|
cd ui && npm ci && npm run build:device && \
|
||||||
find ../static/assets \
|
find ../static/ \
|
||||||
-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' \
|
||||||
\) \
|
\) \
|
||||||
|
|
|
||||||
92
display.go
92
display.go
|
|
@ -1,6 +1,7 @@
|
||||||
package kvm
|
package kvm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -110,12 +111,6 @@ func clearDisplayState() {
|
||||||
currentScreen = "ui_Boot_Screen"
|
currentScreen = "ui_Boot_Screen"
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
cloudBlinkLock sync.Mutex = sync.Mutex{}
|
|
||||||
cloudBlinkStopped bool
|
|
||||||
cloudBlinkTicker *time.Ticker
|
|
||||||
)
|
|
||||||
|
|
||||||
func updateDisplay() {
|
func updateDisplay() {
|
||||||
updateLabelIfChanged("ui_Home_Content_Ip", networkState.IPv4String())
|
updateLabelIfChanged("ui_Home_Content_Ip", networkState.IPv4String())
|
||||||
if usbState == "configured" {
|
if usbState == "configured" {
|
||||||
|
|
@ -152,48 +147,81 @@ func updateDisplay() {
|
||||||
stopCloudBlink()
|
stopCloudBlink()
|
||||||
case CloudConnectionStateConnecting:
|
case CloudConnectionStateConnecting:
|
||||||
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
||||||
startCloudBlink()
|
restartCloudBlink()
|
||||||
case CloudConnectionStateConnected:
|
case CloudConnectionStateConnected:
|
||||||
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
||||||
stopCloudBlink()
|
stopCloudBlink()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startCloudBlink() {
|
const (
|
||||||
if cloudBlinkTicker == nil {
|
cloudBlinkInterval = 2 * time.Second
|
||||||
cloudBlinkTicker = time.NewTicker(2 * time.Second)
|
cloudBlinkDuration = 1 * time.Second
|
||||||
} else {
|
)
|
||||||
// do nothing if the blink isn't stopped
|
|
||||||
if cloudBlinkStopped {
|
|
||||||
cloudBlinkLock.Lock()
|
|
||||||
defer cloudBlinkLock.Unlock()
|
|
||||||
|
|
||||||
cloudBlinkStopped = false
|
var (
|
||||||
cloudBlinkTicker.Reset(2 * time.Second)
|
cloudBlinkTicker *time.Ticker
|
||||||
|
cloudBlinkCancel context.CancelFunc
|
||||||
|
cloudBlinkLock = sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func doCloudBlink(ctx context.Context) {
|
||||||
|
for range cloudBlinkTicker.C {
|
||||||
|
if cloudConnectionState != CloudConnectionStateConnecting {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = lvObjFadeOut("ui_Home_Header_Cloud_Status_Icon", uint32(cloudBlinkDuration.Milliseconds()))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(cloudBlinkDuration):
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = lvObjFadeIn("ui_Home_Header_Cloud_Status_Icon", uint32(cloudBlinkDuration.Milliseconds()))
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(cloudBlinkDuration):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
func restartCloudBlink() {
|
||||||
for range cloudBlinkTicker.C {
|
stopCloudBlink()
|
||||||
if cloudConnectionState != CloudConnectionStateConnecting {
|
startCloudBlink()
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
_, _ = lvObjFadeOut("ui_Home_Header_Cloud_Status_Icon", 1000)
|
func startCloudBlink() {
|
||||||
time.Sleep(1000 * time.Millisecond)
|
cloudBlinkLock.Lock()
|
||||||
_, _ = lvObjFadeIn("ui_Home_Header_Cloud_Status_Icon", 1000)
|
defer cloudBlinkLock.Unlock()
|
||||||
time.Sleep(1000 * time.Millisecond)
|
|
||||||
}
|
if cloudBlinkTicker == nil {
|
||||||
}()
|
cloudBlinkTicker = time.NewTicker(cloudBlinkInterval)
|
||||||
|
} else {
|
||||||
|
cloudBlinkTicker.Reset(cloudBlinkInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cloudBlinkCancel = cancel
|
||||||
|
|
||||||
|
go doCloudBlink(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCloudBlink() {
|
func stopCloudBlink() {
|
||||||
|
cloudBlinkLock.Lock()
|
||||||
|
defer cloudBlinkLock.Unlock()
|
||||||
|
|
||||||
|
if cloudBlinkCancel != nil {
|
||||||
|
cloudBlinkCancel()
|
||||||
|
cloudBlinkCancel = nil
|
||||||
|
}
|
||||||
|
|
||||||
if cloudBlinkTicker != nil {
|
if cloudBlinkTicker != nil {
|
||||||
cloudBlinkTicker.Stop()
|
cloudBlinkTicker.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
cloudBlinkLock.Lock()
|
|
||||||
defer cloudBlinkLock.Unlock()
|
|
||||||
cloudBlinkStopped = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1 +1 @@
|
||||||
6dabd0e657dd099280d9173069687786a4a8c9c25cf7f9e7ce2f940cab67c521
|
01db2bbcd0bad46c3e21eb3cc5687d15df2153c3d8e2d4665b37acb55f0b5a57
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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
|
||||||
|
|
@ -6,27 +6,34 @@
|
||||||
<!-- These are the fonts used in the app -->
|
<!-- These are the fonts used in the app -->
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="/fonts/CircularXXWeb-Medium.woff2"
|
href="./public/fonts/CircularXXWeb-Medium.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
rel="preload"
|
||||||
href="/fonts/CircularXXWeb-Book.woff2"
|
href="./public/fonts/CircularXXWeb-Book.woff2"
|
||||||
as="font"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
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"
|
as="font"
|
||||||
type="font/woff2"
|
type="font/woff2"
|
||||||
crossorigin
|
crossorigin
|
||||||
/>
|
/>
|
||||||
<title>JetKVM</title>
|
<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/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" />
|
||||||
|
|
@ -36,23 +43,21 @@
|
||||||
<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>
|
||||||
// Initial theme setup
|
function applyThemeFromPreference() {
|
||||||
document.documentElement.classList.toggle(
|
// dark theme setup
|
||||||
"dark",
|
var darkDesired = localStorage.theme === "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
|
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", applyThemeFromPreference);
|
||||||
.matchMedia("(prefers-color-scheme: dark)")
|
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", applyThemeFromPreference);
|
||||||
.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
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
User-agent: *
|
|
||||||
Disallow: /
|
|
||||||
|
|
@ -31,6 +31,7 @@ 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,8 +69,7 @@ type SetupRequest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var cachableFileExtensions = []string{
|
var cachableFileExtensions = []string{
|
||||||
".jpg", ".jpeg", ".png", ".gif", ".webp", ".woff2",
|
".jpg", ".jpeg", ".png", ".svg", ".gif", ".webp", ".ico", ".woff2",
|
||||||
".ico",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRouter() *gin.Engine {
|
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(
|
staticFileServer := http.StripPrefix("/static", statigz.FileServer(
|
||||||
staticFS.(fs.ReadDirFS),
|
staticFS.(fs.ReadDirFS),
|
||||||
))
|
))
|
||||||
|
|
@ -109,9 +111,17 @@ 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