mirror of
https://github.com/fankes/komari-agent.git
synced 2025-12-10 23:43:40 +08:00
164 lines
3.4 KiB
Go
164 lines
3.4 KiB
Go
package terminal
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
|
|
pkg_flags "github.com/komari-monitor/komari-agent/cmd/flags"
|
|
)
|
|
|
|
var flags = pkg_flags.GlobalConfig
|
|
|
|
// Terminal 接口定义平台特定的终端操作
|
|
type Terminal interface {
|
|
Close() error
|
|
Read(p []byte) (int, error)
|
|
Write(p []byte) (int, error)
|
|
Resize(cols, rows int) error
|
|
Wait() error
|
|
}
|
|
|
|
// terminalImpl 封装终端和平台特定逻辑
|
|
type terminalImpl struct {
|
|
shell string
|
|
workingDir string
|
|
term Terminal
|
|
}
|
|
|
|
// StartTerminal 启动终端并处理 WebSocket 通信
|
|
func StartTerminal(conn *websocket.Conn) {
|
|
if flags.DisableWebSsh {
|
|
conn.WriteMessage(websocket.TextMessage, []byte("\n\nWeb SSH is disabled. Enable it by running without the --disable-web-ssh flag."))
|
|
conn.Close()
|
|
return
|
|
}
|
|
impl, err := newTerminalImpl()
|
|
if err != nil {
|
|
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("Error: %v\r\n", err)))
|
|
return
|
|
}
|
|
|
|
errChan := make(chan error, 3) // 增加容量以容纳多个错误源
|
|
done := make(chan struct{})
|
|
|
|
defer func() {
|
|
gracefulShutdown(impl.term)
|
|
impl.term.Close()
|
|
conn.Close()
|
|
close(done)
|
|
}()
|
|
|
|
// 从 WebSocket 读取消息并写入终端
|
|
go handleWebSocketInput(conn, impl.term, errChan, done)
|
|
|
|
// 从终端读取输出并写入 WebSocket
|
|
go handleTerminalOutput(conn, impl.term, errChan, done)
|
|
|
|
// 等待终端进程结束或出现错误
|
|
select {
|
|
case err := <-errChan:
|
|
// WebSocket 连接断开或出现错误
|
|
if err != nil {
|
|
conn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("\r\nConnection error: %v\r\n", err)))
|
|
}
|
|
case <-done:
|
|
// 已经被其他地方关闭
|
|
}
|
|
}
|
|
|
|
// gracefulShutdown 尝试优雅地关闭终端
|
|
func gracefulShutdown(term Terminal) {
|
|
// Ctrl+C
|
|
for i := 0; i < 3; i++ {
|
|
if _, err := term.Write([]byte{3}); err != nil {
|
|
return
|
|
}
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Ctrl+D (EOF)
|
|
term.Write([]byte{4})
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
term.Write([]byte("exit\n"))
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
// handleWebSocketInput 处理 WebSocket 输入
|
|
func handleWebSocketInput(conn *websocket.Conn, term Terminal, errChan chan<- error, done <-chan struct{}) {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
}
|
|
|
|
t, p, err := conn.ReadMessage()
|
|
if err != nil {
|
|
select {
|
|
case errChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
if t == websocket.TextMessage {
|
|
var cmd struct {
|
|
Type string `json:"type"`
|
|
Cols int `json:"cols,omitempty"`
|
|
Rows int `json:"rows,omitempty"`
|
|
Input string `json:"input,omitempty"`
|
|
}
|
|
if err := json.Unmarshal(p, &cmd); err == nil {
|
|
switch cmd.Type {
|
|
case "resize":
|
|
if cmd.Cols > 0 && cmd.Rows > 0 {
|
|
term.Resize(cmd.Cols, cmd.Rows)
|
|
}
|
|
case "input":
|
|
if cmd.Input != "" {
|
|
term.Write([]byte(cmd.Input))
|
|
}
|
|
}
|
|
} else {
|
|
term.Write(p)
|
|
}
|
|
}
|
|
if t == websocket.BinaryMessage {
|
|
term.Write(p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleTerminalOutput 处理终端输出
|
|
func handleTerminalOutput(conn *websocket.Conn, term Terminal, errChan chan<- error, done <-chan struct{}) {
|
|
buf := make([]byte, 4096)
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
}
|
|
|
|
n, err := term.Read(buf)
|
|
if err != nil {
|
|
select {
|
|
case errChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
if err := conn.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil {
|
|
select {
|
|
case errChan <- err:
|
|
default:
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|