init: 自 zomaintain/backend/remote-core 平移独立成库; module path 改为 git.zeroonesoft.cn/golib/remote-core; 修复 ResizeDataType 未导出字段致 json tag 无效的遗留问题

This commit is contained in:
w11
2026-09-19 21:14:49 +08:00
commit 4936f5baf0
8 changed files with 912 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
package shell
import (
"encoding/json"
"io"
"sync"
"time"
)
type RecType string
const (
InputType RecType = "i"
OutPutType RecType = "o"
)
type RecHeader struct {
Version int `json:"version"`
Width int `json:"width"`
Height int `json:"height"`
Timestamp int64 `json:"timestamp"`
Env struct {
Shell string `json:"SHELL"`
Term string `json:"TERM"`
} `json:"env"`
}
func defaultRecHeader() *RecHeader {
recHeader := new(RecHeader)
recHeader.Version = 2
recHeader.Env.Shell = "/bin/bash"
recHeader.Env.Term = "xterm-256color"
return recHeader
}
type Recorder struct {
StartTime time.Time
Writer io.Writer
sync.Mutex
}
func NewRecorder(writer io.Writer) *Recorder {
return &Recorder{
StartTime: time.Now(),
Writer: writer,
}
}
func (rec *Recorder) WriteHeader(height, width int) {
header := defaultRecHeader()
header.Timestamp = rec.StartTime.Unix()
header.Height = height
header.Width = width
b, _ := json.Marshal(header)
rec.Writer.Write(b)
rec.Writer.Write([]byte("\r\n"))
}
func (rec *Recorder) WriteData(rectype RecType, data string) {
recData := make([]interface{}, 3)
recData[0] = float64(time.Since(rec.StartTime).Microseconds()) / float64(1000000)
recData[1] = rectype
recData[2] = data
b, _ := json.Marshal(recData)
rec.Writer.Write(b)
rec.Writer.Write([]byte("\r\n"))
}
+218
View File
@@ -0,0 +1,218 @@
package shell
// 移植自 remote-backend-server internal/handlers/shell(去 DB 依赖, ConPTY 终端 + asciinema 录制)。
import (
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/runletapp/go-console"
"github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"os"
"path"
"git.zeroonesoft.cn/golib/remote-core/common"
"runtime"
"strings"
"time"
)
type windowSize struct {
Rows uint16 `json:"rows"`
Cols uint16 `json:"cols"`
X uint16
Y uint16
}
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
// 解决跨域问题
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type OpDataType struct {
Op string `json:"op" form:"op"`
Data interface{} `json:"data" form:"data" `
}
type ResizeDataType struct {
Cols int `json:"cols"`
Rows int `json:"rows"`
}
// RegisterRoutersWebShell 注册路由
func RegisterRoutersWebShell(r *gin.RouterGroup, recDir string) {
r.GET("/web/shell", getWebShell(recDir))
r.GET("/web/shell/record", RecoderList)
}
func getWebShell(recDir string) gin.HandlerFunc {
return func(c *gin.Context) {
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
logrus.Errorf("Unable to upgrade connection")
return
}
proc, err := console.New(180, 30)
if err != nil {
panic(err)
}
arr := make([]string, 0)
if runtime.GOOS == "windows" {
if ok, _ := common.PathExists("C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"); ok {
arr = append(arr, "powershell.exe") // powershell.exe cmd.exe
} else {
arr = append(arr, "cmd.exe")
}
} else {
arr = []string{"bash"} // powershell.exe cmd.exe
}
if err := proc.Start(arr); err != nil {
logrus.Errorf("启动出错:%s", err.Error())
panic(err)
}
if err != nil {
logrus.Errorf("Unable to start pty/cmd")
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
return
}
defer func() {
proc.Close()
conn.Close()
}()
var recorder *Recorder
os.MkdirAll("./rec", 0766)
fileName := path.Join("./rec/", fmt.Sprintf("%s.cast", time.Now().Format("20060102_150405")))
logrus.Debugf("记录文件名:%s", fileName)
f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0766)
if err != nil {
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
logrus.Errorf("创建记录文件失败")
return
}
defer f.Close()
recorder = NewRecorder(f)
if recorder != nil {
recorder.Lock()
recorder.WriteHeader(30, 150)
recorder.Unlock()
}
go func() {
for {
buf := make([]byte, 0x10000)
read, err := proc.Read(buf)
if err != nil {
conn.WriteMessage(websocket.TextMessage, []byte(err.Error()))
logrus.Errorf("读取错误:%s", err.Error())
return
}
conn.WriteMessage(websocket.BinaryMessage, buf[:read])
if recorder != nil {
recorder.Lock()
recorder.WriteData(InputType, string(buf[:read]))
recorder.Unlock()
}
}
}()
for {
messageType, reader, err := conn.NextReader()
if err != nil {
logrus.Errorf("Unable to grab next reader")
return
}
if messageType != websocket.TextMessage {
logrus.Errorf("无效的消息")
//conn.WriteMessage(websocket.TextMessage, []byte("Unexpected text message"))
//continue
}
buf := make([]byte, 0x10000)
n, err := reader.Read(buf)
if err != nil {
logrus.Errorf("Error after copying %d bytes", n)
}
if n > 0 {
opData := OpDataType{}
newBuf := buf[:n]
fmt.Println("收到数据", string(newBuf))
err = json.Unmarshal(newBuf, &opData)
if err != nil {
logrus.Errorf("解析错误:%s", err.Error())
}
if opData.Op == "stdin" {
value, ok := opData.Data.(string)
if ok {
n, err = proc.Write([]byte(value))
if err != nil {
logrus.Errorf("写放数据错误g %d bytes", n)
} else {
fmt.Println("写入成功")
}
} else {
}
} else if opData.Op == "resize" {
fmt.Println("resize")
value, ok := opData.Data.(ResizeDataType)
if ok {
err = proc.SetSize(value.Cols, value.Rows)
if err != nil {
logrus.Errorf("重置大小错误 %d bytes", n)
} else {
fmt.Println("重置大小成功")
}
} else {
}
}
}
}
}
}
func RecoderList(c *gin.Context) {
files, err := ioutil.ReadDir("./rec")
if err != nil {
c.AbortWithStatusJSON(200, gin.H{"ok": false, "msg": err.Error()})
return
}
var filesName []string
for _, f := range files {
if f.IsDir() {
continue
}
if strings.HasSuffix(f.Name(), ".cast") {
filesName = append(filesName, f.Name())
}
}
c.JSON(200, filesName)
}