Windows terminal support using the termtest library.

Should even support resizing. Fully untested.
This commit is contained in:
Erik Brakkee 2024-07-22 23:55:37 +02:00
parent d2801d0019
commit 55c93ad4e2
4 changed files with 83 additions and 7 deletions

2
go.mod
View File

@ -3,6 +3,7 @@ module converge
go 1.21
require (
github.com/ActiveState/termtest/conpty v0.5.0
github.com/creack/pty v1.1.21
github.com/gliderlabs/ssh v0.3.7
github.com/gorilla/websocket v1.5.3
@ -13,6 +14,7 @@ require (
)
require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/kr/fs v0.1.0 // indirect
golang.org/x/sys v0.22.0 // indirect

5
go.sum
View File

@ -1,3 +1,7 @@
github.com/ActiveState/termtest/conpty v0.5.0 h1:JLUe6YDs4Jw4xNPCU+8VwTpniYOGeKzQg4SM2YHQNA8=
github.com/ActiveState/termtest/conpty v0.5.0/go.mod h1:LO4208FLsxw6DcNZ1UtuGUMW+ga9PFtX4ntv8Ymg9og=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
@ -36,6 +40,7 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -10,3 +10,15 @@ type ReadWriteAddrCloser interface {
RemoteAddr() net.Addr
}
type ReadWriterCombiner struct {
io.Reader
io.Writer
}
func (rw *ReadWriterCombiner) Read(p []byte) (n int, err error) {
return rw.Reader.Read(p)
}
func (rw *ReadWriterCombiner) Write(p []byte) (n int, err error) {
return rw.Writer.Write(p)
}

View File

@ -1,24 +1,81 @@
package terminal
import (
"fmt"
"github.com/ActiveState/termtest/conpty"
"github.com/gliderlabs/ssh"
"io"
"log"
"os"
"os/exec"
"syscall"
)
var PtySpawner = Spawner(func(sshSession ssh.Session, env []string, name string, arg ...string) (Process, error) {
return nil, nil
var PtySpawner = Spawner(func(sshSession ssh.Session, env []string, name string, args ...string) (Process, error) {
_, winCh, isPty := sshSession.Pty()
if !isPty {
return nil, fmt.Errorf("ssh session is not a pty")
}
cpty, err := conpty.New(80, 25)
if err != nil {
return nil, err
}
pid, _, err := cpty.Spawn(
"cmd.exe",
args,
&syscall.ProcAttr{
Env: env,
},
)
if err != nil {
cpty.Close()
return nil, err
}
fmt.Printf("New process with pid %d spawned\n", pid)
process, err := os.FindProcess(pid)
if err != nil {
cpty.Close()
return nil, fmt.Errorf("Failed to find process: %v", err)
}
go func() {
for win := range winCh {
err = cpty.Resize(uint16(win.Width), uint16(win.Height))
if err != nil {
log.Printf("Feiled to resize terminal to %d x %d", win.Width, win.Height)
}
}
}()
return ptyProcess{
cpty: cpty,
process: process,
}, nil
})
type ptyProcess struct {
cmd *exec.Cmd
f *os.File
cpty *conpty.ConPty
process *os.Process
}
func (proc ptyProcess) Read(p []byte) (n int, err error) {
return proc.cpty.OutPipe().Read(p)
}
func (proc ptyProcess) Write(p []byte) (n int, err error) {
return proc.Write(p)
}
func (p ptyProcess) Pipe() io.ReadWriter {
return p.f
return p
}
func (p ptyProcess) Wait() error {
return p.cmd.Wait()
defer p.cpty.Close()
ps, err := p.process.Wait()
if err != nil {
return fmt.Errorf("Error waiting for process: %v", err)
}
if ps.ExitCode() != 0 {
return fmt.Errorf("exit code was: %d\n", ps.ExitCode())
}
return nil
}