Files
NanoKVM-MIRROR/server/service/vm/terminal.go
wj-xiao 6eb4a4ea62 update version to 2.1.6
feat: support downloading image from online URL
feat: add keyboard shortcut Ctrl+Alt+Del
fix: fix the CSRF issue
perf: add an option to configure custom ICE servers
perf: removed unnecessary modifications to DNS configuration
perf: add an SSH enable/disable toggle in the web UI
perf: add a Tailscale enable/disable toggle in the web UI
perf: download Tailscale installation package from the official source
perf: automatic enable/disable GOMEMLIMIT on tailscale start/stop
perf: add JWT configuration
perf: implement secure password storage using bcrypt hashing
perf: implement integrity checks for online updates
refactor: refactor HDMI module and remove the dependency libmaixcam_lib.so
refactor: web terminal use pty instead of SSH
refactor: move Tailscale APIs from the network module to the extensions module
2025-02-14 14:18:13 +08:00

111 lines
1.9 KiB
Go

package vm
import (
"encoding/json"
"net/http"
"os"
"os/exec"
"time"
"github.com/creack/pty"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
)
const (
messageWait = 10 * time.Second
maxMessageSize = 1024
)
type WinSize struct {
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
}
var upgrader = websocket.Upgrader{
ReadBufferSize: maxMessageSize,
WriteBufferSize: maxMessageSize,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func (s *Service) Terminal(c *gin.Context) {
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Errorf("failed to init websocket: %s", err)
return
}
defer func() {
_ = ws.Close()
}()
cmd := exec.Command("/bin/sh")
ptmx, err := pty.Start(cmd)
if err != nil {
log.Errorf("failed to start pty: %s", err)
return
}
defer func() {
_ = ptmx.Close()
_ = cmd.Process.Kill()
}()
go wsWrite(ws, ptmx)
wsRead(ws, ptmx)
}
// pty to ws
func wsWrite(ws *websocket.Conn, ptmx *os.File) {
data := make([]byte, maxMessageSize)
for {
n, err := ptmx.Read(data)
if err != nil {
return
}
if n > 0 {
_ = ws.SetWriteDeadline(time.Now().Add(messageWait))
err = ws.WriteMessage(websocket.BinaryMessage, data[:n])
if err != nil {
log.Errorf("write ws message failed: %s", err)
return
}
}
}
}
// ws to pty
func wsRead(ws *websocket.Conn, ptmx *os.File) {
var zeroTime time.Time
_ = ws.SetReadDeadline(zeroTime)
for {
msgType, p, err := ws.ReadMessage()
if err != nil {
return
}
// resize message
if msgType == websocket.BinaryMessage {
var winSize WinSize
if err := json.Unmarshal(p, &winSize); err == nil {
_ = pty.Setsize(ptmx, &pty.Winsize{
Rows: winSize.Rows,
Cols: winSize.Cols,
})
}
continue
}
_, err = ptmx.Write(p)
if err != nil {
log.Errorf("failed to write to pty: %s", err)
return
}
}
}