diff --git a/Makefile b/Makefile index 04c7402..eea9730 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -VERSION_DEV := 0.3.5-dev$(shell date +%Y%m%d%H%M) -VERSION := 0.3.4 +VERSION_DEV := 0.3.6-dev$(shell date +%Y%m%d%H%M) +VERSION := 0.3.5 hash_resource: @shasum -a 256 resource/jetkvm_native | cut -d ' ' -f 1 > resource/jetkvm_native.sha256 diff --git a/cloud.go b/cloud.go index db47727..3520e2f 100644 --- a/cloud.go +++ b/cloud.go @@ -7,13 +7,14 @@ import ( "fmt" "net/http" "net/url" - "github.com/coder/websocket/wsjson" "time" + "github.com/coder/websocket/wsjson" + "github.com/coreos/go-oidc/v3/oidc" - "github.com/gin-gonic/gin" "github.com/coder/websocket" + "github.com/gin-gonic/gin" ) type CloudRegisterRequest struct { @@ -68,6 +69,11 @@ func handleCloudRegister(c *gin.Context) { return } + if config.CloudToken == "" { + logger.Info("Starting websocket client due to adoption") + go RunWebsocketClient() + } + config.CloudToken = tokenResp.SecretToken config.CloudURL = req.CloudAPI @@ -187,7 +193,11 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess return fmt.Errorf("google identity mismatch") } - session, err := newSession() + session, err := newSession(SessionConfig{ + ICEServers: req.ICEServers, + LocalIP: req.IP, + IsCloud: true, + }) if err != nil { _ = wsjson.Write(context.Background(), c, gin.H{"error": err}) return err diff --git a/main.go b/main.go index f4021c9..ce9d1fb 100644 --- a/main.go +++ b/main.go @@ -67,8 +67,14 @@ func Main() { }() //go RunFuseServer() go RunWebServer() - go RunWebsocketClient() go plugin.ReconcilePlugins() + + // If the cloud token isn't set, the client won't be started by default. + // However, if the user adopts the device via the web interface, handleCloudRegister will start the client. + if config.CloudToken != "" { + go RunWebsocketClient() + } + sigs := make(chan os.Signal, 1) signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) <-sigs diff --git a/native.go b/native.go index 89e6803..d34ab07 100644 --- a/native.go +++ b/native.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "sync" + "syscall" "time" "github.com/pion/webrtc/v4/pkg/media" @@ -224,6 +225,12 @@ func ExtractAndRunNativeBin() error { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + // Set the process group ID so we can kill the process and its children when this process exits + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + Pdeathsig: syscall.SIGKILL, + } + // Start the command if err := cmd.Start(); err != nil { return fmt.Errorf("failed to start binary: %w", err) diff --git a/ui/src/components/MountMediaDialog.tsx b/ui/src/components/MountMediaDialog.tsx index 8deb4a5..6f7c96b 100644 --- a/ui/src/components/MountMediaDialog.tsx +++ b/ui/src/components/MountMediaDialog.tsx @@ -534,17 +534,17 @@ function UrlView({ }, { name: "Debian 12", - url: "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.7.0-amd64-netinst.iso", + url: "https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-12.9.0-amd64-netinst.iso", icon: DebianIcon, }, { - name: "Fedora 38", - url: "https://mirror.ihost.md/fedora/releases/38/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-38-1.6.iso", + name: "Fedora 41", + 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: "Arch Linux", - url: "https://archlinux.doridian.net/iso/2024.10.01/archlinux-2024.10.01-x86_64.iso", + url: "https://archlinux.doridian.net/iso/2025.02.01/archlinux-2025.02.01-x86_64.iso", icon: ArchIcon, }, { diff --git a/ui/src/components/WebRTCVideo.tsx b/ui/src/components/WebRTCVideo.tsx index f5f083b..7603369 100644 --- a/ui/src/components/WebRTCVideo.tsx +++ b/ui/src/components/WebRTCVideo.tsx @@ -425,7 +425,7 @@ export default function WebRTCVideo() { disablePictureInPicture controlsList="nofullscreen" className={cx( - "outline-50 max-h-full max-w-full rounded-md object-contain transition-all duration-1000", + "outline-50 max-h-full max-w-full object-contain transition-all duration-1000", { "cursor-none": settings.isCursorHidden, "opacity-0": isLoading || isConnectionError || hdmiError, diff --git a/web.go b/web.go index 64f8de7..02c7eea 100644 --- a/web.go +++ b/web.go @@ -17,8 +17,10 @@ import ( var staticFiles embed.FS type WebRTCSessionRequest struct { - Sd string `json:"sd"` - OidcGoogle string `json:"OidcGoogle,omitempty"` + Sd string `json:"sd"` + OidcGoogle string `json:"OidcGoogle,omitempty"` + IP string `json:"ip,omitempty"` + ICEServers []string `json:"iceServers,omitempty"` } type SetPasswordRequest struct { @@ -116,7 +118,7 @@ func handleWebRTCSession(c *gin.Context) { return } - session, err := newSession() + session, err := newSession(SessionConfig{}) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err}) return diff --git a/webrtc.go b/webrtc.go index 49a7b41..2cbed4c 100644 --- a/webrtc.go +++ b/webrtc.go @@ -4,6 +4,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "net" "strings" "github.com/pion/webrtc/v4" @@ -19,6 +20,12 @@ type Session struct { shouldUmountVirtualMedia bool } +type SessionConfig struct { + ICEServers []string + LocalIP string + IsCloud bool +} + func (s *Session) ExchangeOffer(offerStr string) (string, error) { b, err := base64.StdEncoding.DecodeString(offerStr) if err != nil { @@ -61,9 +68,29 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) { return base64.StdEncoding.EncodeToString(localDescription), nil } -func newSession() (*Session, error) { - peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{ - ICEServers: []webrtc.ICEServer{{}}, +func newSession(config SessionConfig) (*Session, error) { + webrtcSettingEngine := webrtc.SettingEngine{} + iceServer := webrtc.ICEServer{} + + if config.IsCloud { + if config.ICEServers == nil { + fmt.Printf("ICE Servers not provided by cloud") + } else { + iceServer.URLs = config.ICEServers + fmt.Printf("Using ICE Servers provided by cloud: %v\n", iceServer.URLs) + } + + if config.LocalIP == "" || net.ParseIP(config.LocalIP) == nil { + fmt.Printf("Local IP address %v not provided or invalid, won't set NAT1To1IPs\n", config.LocalIP) + } else { + webrtcSettingEngine.SetNAT1To1IPs([]string{config.LocalIP}, webrtc.ICECandidateTypeSrflx) + fmt.Printf("Setting NAT1To1IPs to %s\n", config.LocalIP) + } + } + + api := webrtc.NewAPI(webrtc.WithSettingEngine(webrtcSettingEngine)) + peerConnection, err := api.NewPeerConnection(webrtc.Configuration{ + ICEServers: []webrtc.ICEServer{iceServer}, }) if err != nil { return nil, err