diff --git a/.tool-versions b/.tool-versions
index c06aa6a..a1e8ca8 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,4 +1,4 @@
# REMEMBER TO UPDATE mise.toml TOO
pnpm 10.4.0
node 22.16
-go 1.24.4
+go 1.25.1
diff --git a/app/routes/machines/dialogs/expire.tsx b/app/routes/machines/dialogs/expire.tsx
index 12f78b7..5c0863b 100644
--- a/app/routes/machines/dialogs/expire.tsx
+++ b/app/routes/machines/dialogs/expire.tsx
@@ -16,8 +16,8 @@ export default function Expire({ machine, isOpen, setIsOpen }: ExpireProps) {
This will disconnect the machine from your Tailnet. In order to
reconnect, you will need to re-authenticate from the device.
-
-
+
+
);
diff --git a/app/routes/ssh/hp_ssh.d.ts b/app/routes/ssh/hp_ssh.d.ts
index 8b9784b..8c43b4a 100644
--- a/app/routes/ssh/hp_ssh.d.ts
+++ b/app/routes/ssh/hp_ssh.d.ts
@@ -39,15 +39,16 @@ type IPNState =
| 'Running';
interface XtermConfig {
- Rows: number;
- Cols: number;
+ rows: number;
+ cols: number;
+ timeout?: number;
- OnStdout: (data: string) => void;
- OnStderr: (data: string) => void;
- OnStdin: (func: (input: string) => void) => void;
+ onStdout: (data: string) => void;
+ onStderr: (data: string) => void;
+ onStdin: (func: (input: string) => void) => void;
- OnConnect: () => void;
- OnDisconnect: () => void;
+ onConnect: () => void;
+ onDisconnect: () => void;
}
interface SSHSession {
diff --git a/app/routes/ssh/xterm.client.tsx b/app/routes/ssh/xterm.client.tsx
index 3c152e9..6313c13 100644
--- a/app/routes/ssh/xterm.client.tsx
+++ b/app/routes/ssh/xterm.client.tsx
@@ -81,24 +81,26 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
let onUnload: ((e: Event) => void) | null = null;
const session = ipn.OpenSSH(hostname, username, {
- Rows: terminal.rows,
- Cols: terminal.cols,
+ rows: terminal.rows,
+ cols: terminal.cols,
- OnStdout: (data) => terminal.write(data),
- OnStderr: (data) => {
+ onStdout: (data) => terminal.write(data),
+ onStderr: (data) => {
terminal.write(data);
console.log('SSH stderr:', data);
toast(data);
},
- OnStdin: (func) => {
+ onStdin: (func) => {
+ console.log('SSH session is ready to receive input');
inputRef.current = func;
+ console.log('Stdin handler set', func);
},
- OnConnect: () => {
+ onConnect: () => {
console.log('SSH session connected');
setIsLoading(false);
},
- OnDisconnect: () => {
+ onDisconnect: () => {
ro?.disconnect();
terminal.dispose();
if (onUnload) {
@@ -156,8 +158,8 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
) : undefined}
{term.current && isResizing ? (
diff --git a/go.mod b/go.mod
index 314bbca..f46a9bb 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,11 @@
module github.com/tale/headplane
-go 1.24.4
+go 1.25.1
require (
go4.org/mem v0.0.0-20240501181205-ae6ca9944745
golang.org/x/crypto v0.41.0
- tailscale.com v1.86.4
+ tailscale.com v1.88.2
)
require (
@@ -32,7 +32,7 @@ require (
github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e // indirect
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
github.com/gaissmai/bart v0.18.0 // indirect
- github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // indirect
+ github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -61,7 +61,7 @@ require (
github.com/tailscale/peercred v0.0.0-20250107143737-35a0c7bd7edc // indirect
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 // indirect
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da // indirect
- github.com/vishvananda/netns v0.0.4 // indirect
+ github.com/vishvananda/netns v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
diff --git a/go.sum b/go.sum
index e2368ed..92ac417 100644
--- a/go.sum
+++ b/go.sum
@@ -67,8 +67,8 @@ github.com/gaissmai/bart v0.18.0 h1:jQLBT/RduJu0pv/tLwXE+xKPgtWJejbxuXAR+wLJafo=
github.com/gaissmai/bart v0.18.0/go.mod h1:JJzMAhNF5Rjo4SF4jWBrANuJfqY+FvsFhW7t1UZJ+XY=
github.com/github/fakeca v0.1.0 h1:Km/MVOFvclqxPM9dZBC4+QE564nU4gz4iZ0D9pMw28I=
github.com/github/fakeca v0.1.0/go.mod h1:+bormgoGMMuamOscx7N91aOuUST7wdaJ2rNjeohylyo=
-github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY=
-github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
+github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced h1:Q311OHjMh/u5E2TITc++WlTP5We0xNseRMkHDyvhW7I=
+github.com/go-json-experiment/json v0.0.0-20250813024750-ebf49471dced/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
github.com/go4org/plan9netshell v0.0.0-20250324183649-788daa080737 h1:cf60tHxREO3g1nroKr2osU3JWZsJzkfi7rEg+oAB0Lo=
@@ -174,8 +174,8 @@ github.com/u-root/u-root v0.14.0/go.mod h1:hAyZorapJe4qzbLWlAkmSVCJGbfoU9Pu4jpJ1
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
-github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
-github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
+github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
+github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek=
@@ -216,8 +216,8 @@ golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeu
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
-google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
-google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
+google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
+google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -230,5 +230,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
-tailscale.com v1.86.4 h1:KHAmLyzVn50t2P5877wohBU0UPVeIMHC9XDzRw4Ycz4=
-tailscale.com v1.86.4/go.mod h1:Lm8dnzU2i/Emw15r6sl3FRNp/liSQ/nYw6ZSQvIdZ1M=
+tailscale.com v1.88.2 h1:S8S+gt/Vx4KDlVjNHk7spcyGihTcJflKMroSnwjp5kQ=
+tailscale.com v1.88.2/go.mod h1:LHaTiwRgzebPDLgZ6RQQVzX+1SR5fbNl51fzm7UtMaw=
diff --git a/internal/hp_ipn/ipnserver.go b/internal/hp_ipn/ipnserver.go
index d77f290..d299b70 100644
--- a/internal/hp_ipn/ipnserver.go
+++ b/internal/hp_ipn/ipnserver.go
@@ -14,7 +14,7 @@ import (
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnserver"
"tailscale.com/ipn/store/mem"
- "tailscale.com/net/netmon"
+ // "tailscale.com/net/netmon"
"tailscale.com/net/netns"
"tailscale.com/net/tsdial"
"tailscale.com/safesocket"
@@ -52,30 +52,31 @@ func NewTsWasmIpn(options *TsWasmNetOptions, callbacks *TsWasmNetCallbacks) (*Ts
// Base system (NewSystem() creates a bus automatically)
// We supply an in-memory store
sys := tsd.NewSystem()
- bus := sys.Bus.Get()
+ // bus := sys.Bus.Get()
sys.Set(new(mem.Store))
dialer := &tsdial.Dialer{Logf: logf}
- netmon, err := netmon.New(bus, logf)
- if err != nil {
- return nil, err
- }
+ // netmon, err := netmon.New(bus, logf)
+ // if err != nil {
+ // return nil, err
+ // }
// Userspace Wireguard engine
engine, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{
- Dialer: dialer,
- NetMon: netmon,
+ Dialer: dialer,
+ // NetMon: netmon,
SetSubsystem: sys.Set,
ControlKnobs: sys.ControlKnobs(),
HealthTracker: sys.HealthTracker(),
Metrics: sys.UserMetricsRegistry(),
+ EventBus: sys.Bus.Get(),
})
- sys.Set(engine)
if err != nil {
return nil, err
}
+ sys.Set(engine)
tun := sys.Tun.Get()
msock := sys.MagicSock.Get()
dnsman := sys.DNSManager.Get()
diff --git a/internal/hp_ipn/jsmap.go b/internal/hp_ipn/jsmap.go
index d0c8940..dff3046 100644
--- a/internal/hp_ipn/jsmap.go
+++ b/internal/hp_ipn/jsmap.go
@@ -59,12 +59,12 @@ func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) {
return nil, errors.New("SSHXtermConfig cannot be undefined or null")
}
- timeout := safeInt("Timeout", obj)
- rows := safeInt("Rows", obj)
- cols := safeInt("Cols", obj)
+ timeout := safeInt("timeout", obj)
+ rows := safeInt("rows", obj)
+ cols := safeInt("cols", obj)
if rows <= 0 || cols <= 0 {
- return nil, errors.New("Rows and Cols must be positive integers")
+ return nil, errors.New("`rows` and `cols` must be positive integers")
}
if timeout <= 0 {
@@ -77,63 +77,43 @@ func ParseSSHXtermConfig(obj js.Value) (*SSHXtermConfig, error) {
Cols: cols,
}
- onStdout := obj.Get("OnStdout")
- if onStdout.IsUndefined() || onStdout.IsNull() {
- return nil, errors.New("OnStdout must be a function")
- }
-
- if onStdout.Type() != js.TypeFunction {
- return nil, errors.New("OnStdout must be a function")
+ onStdout := obj.Get("onStdout")
+ if onStdout.IsUndefined() || onStdout.IsNull() || (onStdout.Type() != js.TypeFunction) {
+ return nil, errors.New("`onStdout` is required and must be a function")
}
config.OnStdout = func(data string) {
onStdout.Invoke(data)
}
- onStderr := obj.Get("OnStderr")
- if onStderr.IsUndefined() || onStderr.IsNull() {
- return nil, errors.New("OnStderr must be a function")
- }
-
- if onStderr.Type() != js.TypeFunction {
- return nil, errors.New("OnStderr must be a function")
+ onStderr := obj.Get("onStderr")
+ if onStderr.IsUndefined() || onStderr.IsNull() || (onStderr.Type() != js.TypeFunction) {
+ return nil, errors.New("`onStderr` is required and must be a function")
}
config.OnStderr = func(error string) {
onStderr.Invoke(error)
}
- onStdin := obj.Get("OnStdin")
- if onStdin.IsUndefined() || onStdin.IsNull() {
- return nil, errors.New("OnStdin must be a function")
- }
-
- if onStdin.Type() != js.TypeFunction {
- return nil, errors.New("OnStdin must be a function")
+ onStdin := obj.Get("onStdin")
+ if onStdin.IsUndefined() || onStdin.IsNull() || (onStdin.Type() != js.TypeFunction) {
+ return nil, errors.New("`onStdin` is required and must be a function")
}
config.OnStdin = onStdin
- onConnect := obj.Get("OnConnect")
- if onConnect.IsUndefined() || onConnect.IsNull() {
- return nil, errors.New("OnConnect must be a function")
- }
-
- if onConnect.Type() != js.TypeFunction {
- return nil, errors.New("OnConnect must be a function")
+ onConnect := obj.Get("onConnect")
+ if onConnect.IsUndefined() || onConnect.IsNull() || (onConnect.Type() != js.TypeFunction) {
+ return nil, errors.New("`onConnect` is required and must be a function")
}
config.OnConnect = func() {
onConnect.Invoke()
}
- onDisconnect := obj.Get("OnDisconnect")
- if onDisconnect.IsUndefined() || onDisconnect.IsNull() {
- return nil, errors.New("OnDisconnect must be a function")
- }
-
- if onDisconnect.Type() != js.TypeFunction {
- return nil, errors.New("OnDisconnect must be a function")
+ onDisconnect := obj.Get("onDisconnect")
+ if onDisconnect.IsUndefined() || onDisconnect.IsNull() || (onDisconnect.Type() != js.TypeFunction) {
+ return nil, errors.New("`onDisconnect` is required and must be a function")
}
config.OnDisconnect = func() {
diff --git a/mise.toml b/mise.toml
index 345ecdf..5462207 100644
--- a/mise.toml
+++ b/mise.toml
@@ -1,6 +1,6 @@
[tools]
# REMEMBER TO UPDATE .tool-versions TOO
-go = "1.24.4"
+go = "1.25.1"
pnpm = "10.4.0"
node = "22.16"
diff --git a/vite.config.ts b/vite.config.ts
index e24cd98..1e6655b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -23,8 +23,8 @@ if (!version) {
const config = await readFile('config.example.yaml', 'utf-8');
const { server } = parse(config);
-export default defineConfig(({ isSsrBuild }) => ({
- base: `${prefix}/`,
+export default defineConfig(({ mode, isSsrBuild }) => ({
+ base: mode !== 'development' ? `${prefix}/` : undefined,
plugins: [
reactRouterHonoServer(),
reactRouter(),