Compare commits

...

7 Commits

Author SHA1 Message Date
Ben Kochie 513b305606
Merge dd1189e8ad into 2a99c2db9d 2025-02-13 15:59:32 +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
SuperQ dd1189e8ad
Chore: Enable golangci-lint
Add a GitHub actions workflow to run golangci-lint.

Signed-off-by: SuperQ <superq@gmail.com>
2025-01-05 18:36:25 +01:00
SuperQ c380c702c7
Chore: Fix up various linting issues
In prep to add golangci-lint, fix various linting issues.
* Make the `kvm` package a fully-qualified public package.

Signed-off-by: SuperQ <superq@gmail.com>
2025-01-05 18:35:47 +01:00
19 changed files with 147 additions and 30 deletions

34
.github/workflows/golangci-lint.yml vendored Normal file
View File

@ -0,0 +1,34 @@
---
name: golangci-lint
on:
push:
paths:
- "go.sum"
- "go.mod"
- "**.go"
- ".github/workflows/golangci-lint.yml"
- ".golangci.yml"
pull_request:
permissions: # added using https://github.com/step-security/secure-repo
contents: read
jobs:
golangci:
permissions:
contents: read # for actions/checkout to fetch code
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
name: lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Go
uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
with:
go-version: 1.23.x
- name: Lint
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
with:
args: --verbose
version: v1.62.0

12
.golangci.yml Normal file
View File

@ -0,0 +1,12 @@
---
linters:
enable:
# - goimports
# - misspell
# - revive
issues:
exclude-rules:
- path: _test.go
linters:
- errcheck

View File

@ -130,7 +130,7 @@ func runWebsocketClient() error {
if err != nil {
return err
}
defer c.CloseNow()
defer c.CloseNow() //nolint:errcheck
logger.Infof("WS connected to %v", wsURL.String())
runCtx, cancelRun := context.WithCancel(context.Background())
defer cancelRun()

View File

@ -1,7 +1,7 @@
package main
import (
"kvm"
"github.com/jetkvm/kvm"
)
func main() {

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"

2
go.mod
View File

@ -1,4 +1,4 @@
module kvm
module github.com/jetkvm/kvm
go 1.21.0

4
hw.go
View File

@ -14,7 +14,7 @@ func extractSerialNumber() (string, error) {
return "", err
}
r, err := regexp.Compile("Serial\\s*:\\s*(\\S+)")
r, err := regexp.Compile(`Serial\s*:\s*(\S+)`)
if err != nil {
return "", fmt.Errorf("failed to compile regex: %w", err)
}
@ -27,7 +27,7 @@ func extractSerialNumber() (string, error) {
return matches[1], nil
}
func readOtpEntropy() ([]byte, error) {
func readOtpEntropy() ([]byte, error) { //nolint:unused
content, err := os.ReadFile("/sys/bus/nvmem/devices/rockchip-otp0/nvmem")
if err != nil {
return nil, err

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

@ -48,7 +48,7 @@ func Main() {
time.Sleep(15 * time.Minute)
for {
logger.Debugf("UPDATING - Auto update enabled: %v", config.AutoUpdateEnabled)
if config.AutoUpdateEnabled == false {
if !config.AutoUpdateEnabled {
return
}
if currentSession != nil {

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"fmt"
"io"
"kvm/resource"
"log"
"net"
"os"
@ -14,6 +13,8 @@ import (
"syscall"
"time"
"github.com/jetkvm/kvm/resource"
"github.com/pion/webrtc/v4/pkg/media"
)
@ -91,8 +92,8 @@ func WriteCtrlMessage(message []byte) error {
return err
}
var nativeCtrlSocketListener net.Listener
var nativeVideoSocketListener net.Listener
var nativeCtrlSocketListener net.Listener //nolint:unused
var nativeVideoSocketListener net.Listener //nolint:unused
var ctrlClientConnected = make(chan struct{})
@ -152,6 +153,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)
@ -192,7 +196,7 @@ func handleVideoClient(conn net.Conn) {
for {
n, err := conn.Read(inboundPacket)
if err != nil {
log.Println("error during read: %s", err)
log.Printf("error during read: %s\n", err)
return
}
now := time.Now()
@ -304,3 +308,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,10 +26,27 @@ 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 {
fmt.Printf("failed to get eth0 interface: %v\n", err)
fmt.Printf("failed to get eth0 interface: %s\n", err)
return
}
@ -44,12 +62,29 @@ func checkNetworkState() {
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
if err != nil {
fmt.Printf("failed to get addresses for eth0: %v\n", err)
fmt.Printf("failed to get addresses for eth0: %s\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 {
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()
}
@ -98,7 +133,7 @@ func init() {
done := make(chan struct{})
if err := netlink.LinkSubscribe(updates, done); err != nil {
fmt.Println("failed to subscribe to link updates: %v", err)
fmt.Printf("failed to subscribe to link updates: %v\n", err)
return
}
@ -125,6 +160,6 @@ func init() {
fmt.Println("Starting mDNS server")
err := startMDNS()
if err != nil {
fmt.Println("failed to run mDNS: %v", err)
fmt.Printf("failed to run mDNS: %v\n", err)
}
}

2
ntp.go
View File

@ -11,7 +11,7 @@ import (
"github.com/beevik/ntp"
)
var timeSynced = false
var timeSynced = false //nolint:unused
func TimeSyncLoop() {
for {

View File

@ -49,7 +49,7 @@ func (w *WebRTCDiskReader) Read(ctx context.Context, offset int64, size int64) (
if err != nil {
return nil, err
}
buf := make([]byte, 0)
var buf []byte
for {
select {
case data := <-diskReadChan:

View File

@ -55,12 +55,14 @@ func handleTerminalChannel(d *webrtc.DataChannel) {
var size TerminalSize
err := json.Unmarshal([]byte(msg.Data), &size)
if err == nil {
pty.Setsize(ptmx, &pty.Winsize{
err = pty.Setsize(ptmx, &pty.Winsize{
Rows: uint16(size.Rows),
Cols: uint16(size.Cols),
})
if err == nil {
return
}
}
logger.Errorf("Failed to parse terminal size: %v", err)
}
_, err := ptmx.Write(msg.Data)
@ -74,7 +76,7 @@ func handleTerminalChannel(d *webrtc.DataChannel) {
ptmx.Close()
}
if cmd != nil && cmd.Process != nil {
cmd.Process.Kill()
_ = cmd.Process.Kill()
}
})
}

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

2
usb.go
View File

@ -215,7 +215,7 @@ func writeGadgetConfig() error {
return nil
}
func rebindUsb() error {
func rebindUsb() error { //nolint:unused
err := os.WriteFile("/sys/bus/platform/drivers/dwc3/unbind", []byte(udc), 0644)
if err != nil {
return err

View File

@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"io"
"kvm/resource"
"log"
"net/http"
"os"
@ -16,12 +15,12 @@ import (
"syscall"
"time"
"github.com/jetkvm/kvm/resource"
"github.com/gin-gonic/gin"
"github.com/psanford/httpreadat"
"github.com/google/uuid"
"github.com/pion/webrtc/v4"
"github.com/psanford/httpreadat"
)
const massStorageName = "mass_storage.usb0"
@ -60,11 +59,11 @@ func onDiskMessage(msg webrtc.DataChannelMessage) {
func mountImage(imagePath string) error {
err := setMassStorageImage("")
if err != nil {
return fmt.Errorf("Remove Mass Storage Image Error", err)
return fmt.Errorf("Remove Mass Storage Image Error: %w", err)
}
err = setMassStorageImage(imagePath)
if err != nil {
return fmt.Errorf("Set Mass Storage Image Error", err)
return fmt.Errorf("Set Mass Storage Image Error: %w", err)
}
return nil
}

2
wol.go
View File

@ -43,7 +43,7 @@ func createMagicPacket(mac net.HardwareAddr) []byte {
// Write the target MAC address 16 times
for i := 0; i < 16; i++ {
binary.Write(&buf, binary.BigEndian, mac)
_ = binary.Write(&buf, binary.BigEndian, mac)
}
return buf.Bytes()