Compare commits

...

15 Commits

Author SHA1 Message Date
Scai 5440c44a01
Merge 57fbee1490 into 2a99c2db9d 2025-02-13 15:59:16 +01:00
Cameron Fleming 2a99c2db9d
fix(net): stop dhcp client and release all v4 addr on linkdown (#16)
This commit fixes jetkvm/kvm#12 by disabling the udhcpc client when the
link goes down, it then removes all the active IPv4 addresses from the
deivce.

Once the link comes back up, it re-activates the udhcpc client so it can
fetch a new IPv4 address for the device.

This doesn't make any changes to the IPv6 side of things yet.
2025-02-13 15:41:10 +01:00
Cameron Fleming 0b5033f798
feat: restore EDID on reboot (#34)
This commit adds the config entry "EdidString" and saves the EDID string
when it's modified via the RPC.

The EDID is restored when the jetkvm_native control socket connects
(usually at boot)

Signed-off-by: Cameron Fleming <cameron@nevexo.space>
2025-02-13 14:42:07 +01:00
Scai d07bedb323
Invert colors on Icons (#123)
* feat(ui): invert colors on icons

* feat(ui): fix tailwindcss class for invert
2025-02-13 14:33:03 +01:00
Dominik Heidler aa0f38bc0b
Add openSUSE ISOs (#151) 2025-02-13 14:05:07 +01:00
Scai 57fbee1490 chore: remove unnecessary actions 2025-01-08 00:51:44 +00:00
Scai 0e65c0a9a9 chore: add last go version to matrix 2025-01-08 00:41:33 +00:00
Scai 2dafb5c9c1 chore: update existing comment 2025-01-08 00:37:19 +00:00
Scai 566305549f chore: fix table comment 2025-01-08 00:34:49 +00:00
Scai 1505c37e4c chore: comment fix issues 2025-01-08 00:29:04 +00:00
Scai 564eee9b00 chore: change to artifact 2025-01-08 00:19:52 +00:00
Scai fab575dbe0 chore: permissions update on push 2025-01-08 00:17:38 +00:00
Scai 97958e7b86 chore: push workflow 2025-01-08 00:13:52 +00:00
Scai 2f7042df18 chore: increase permissions to content workflow 2025-01-07 22:58:23 +00:00
Scai 2cadda4e00 chore: create release workflow 2025-01-07 22:40:03 +00:00
8 changed files with 289 additions and 3 deletions

126
.github/workflows/push.yaml vendored Normal file
View File

@ -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

91
.github/workflows/release.yaml vendored Normal file
View File

@ -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

View File

@ -22,6 +22,7 @@ type Config struct {
LocalAuthToken string `json:"local_auth_token"`
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
EdidString string `json:"hdmi_edid_string"`
}
const configPath = "/userdata/kvm_config.json"

View File

@ -183,6 +183,12 @@ func rpcSetEDID(edid string) error {
if err != nil {
return err
}
// Save EDID to config, allowing it to be restored on reboot.
LoadConfig()
config.EdidString = edid
SaveConfig()
return nil
}

View File

@ -152,6 +152,9 @@ func handleCtrlClient(conn net.Conn) {
ctrlSocketConn = conn
// Restore HDMI EDID if applicable
go restoreHdmiEdid()
readBuf := make([]byte, 4096)
for {
n, err := conn.Read(readBuf)
@ -304,3 +307,16 @@ func ensureBinaryUpdated(destPath string) error {
return nil
}
// Restore the HDMI EDID value from the config.
// Called after successful connection to jetkvm_native.
func restoreHdmiEdid() {
LoadConfig()
if config.EdidString != "" {
logger.Infof("Restoring HDMI EDID to %v", config.EdidString)
_, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": config.EdidString})
if err != nil {
logger.Errorf("Failed to restore HDMI EDID: %v", err)
}
}
}

View File

@ -6,6 +6,7 @@ import (
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
"net"
"os/exec"
"time"
"github.com/vishvananda/netlink"
@ -25,6 +26,23 @@ type LocalIpInfo struct {
MAC string
}
// setDhcpClientState sends signals to udhcpc to change it's current mode
// of operation. Setting active to true will force udhcpc to renew the DHCP lease.
// Setting active to false will put udhcpc into idle mode.
func setDhcpClientState(active bool) {
var signal string;
if active {
signal = "-SIGUSR1"
} else {
signal = "-SIGUSR2"
}
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc");
if err := cmd.Run(); err != nil {
fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err)
}
}
func checkNetworkState() {
iface, err := netlink.LinkByName("eth0")
if err != nil {
@ -47,9 +65,26 @@ func checkNetworkState() {
fmt.Printf("failed to get addresses for eth0: %v\n", err)
}
// If the link is going down, put udhcpc into idle mode.
// If the link is coming back up, activate udhcpc and force it to renew the lease.
if newState.Up != networkState.Up {
setDhcpClientState(newState.Up)
}
for _, addr := range addrs {
if addr.IP.To4() != nil {
newState.IPv4 = addr.IP.String()
if !newState.Up && networkState.Up {
// If the network is going down, remove all IPv4 addresses from the interface.
fmt.Printf("network: state transitioned to down, removing IPv4 address %s\n", addr.IP.String())
err := netlink.AddrDel(iface, &addr)
if err != nil {
fmt.Printf("network: failed to delete %s", addr.IP.String())
}
newState.IPv4 = "..."
} else {
newState.IPv4 = addr.IP.String()
}
} else if addr.IP.To16() != nil && newState.IPv6 == "" {
newState.IPv6 = addr.IP.String()
}

View File

@ -26,6 +26,7 @@ import { InputFieldWithLabel } from "./InputField";
import DebianIcon from "@/assets/debian-icon.png";
import UbuntuIcon from "@/assets/ubuntu-icon.png";
import FedoraIcon from "@/assets/fedora-icon.png";
import OpenSUSEIcon from "@/assets/opensuse-icon.png";
import ArchIcon from "@/assets/arch-icon.png";
import NetBootIcon from "@/assets/netboot-icon.svg";
import { TrashIcon } from "@heroicons/react/16/solid";
@ -542,6 +543,16 @@ function UrlView({
url: "https://download.fedoraproject.org/pub/fedora/linux/releases/41/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-41-1.4.iso",
icon: FedoraIcon,
},
{
name: "openSUSE Leap 15.6",
url: "https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso",
icon: OpenSUSEIcon,
},
{
name: "openSUSE Tumbleweed",
url: "https://download.opensuse.org/tumbleweed/iso/openSUSE-Tumbleweed-NET-x86_64-Current.iso",
icon: OpenSUSEIcon,
},
{
name: "Arch Linux",
url: "https://archlinux.doridian.net/iso/2025.02.01/archlinux-2025.02.01-x86_64.iso",

View File

@ -466,7 +466,7 @@ export default function SettingsSidebar() {
<GridCard>
<div className="flex items-center px-4 py-3 group gap-x-4">
<img
className="w-6 shrink-0"
className="w-6 shrink-0 dark:invert"
src={PointingFinger}
alt="Finger touching a screen"
/>
@ -490,7 +490,7 @@ export default function SettingsSidebar() {
>
<GridCard>
<div className="flex items-center px-4 py-3 gap-x-4">
<img className="w-6 shrink-0" src={MouseIcon} alt="Mouse icon" />
<img className="w-6 shrink-0 dark:invert" src={MouseIcon} alt="Mouse icon" />
<div className="flex items-center justify-between grow">
<div className="text-left">
<h3 className="text-sm font-semibold text-black dark:text-white">