mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
updates, switch to new fecmd packet to run all UI commands through
This commit is contained in:
parent
5b1eb383e3
commit
6807547ff7
@ -10,7 +10,6 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -18,7 +17,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/sh2-server/pkg/cmdrunner"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
@ -153,69 +152,6 @@ func HandleGetAllSessions(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// params: name
|
|
||||||
func HandleCreateSession(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
w.Header().Set("Vary", "Origin")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
|
||||||
qvals := r.URL.Query()
|
|
||||||
name := qvals.Get("name")
|
|
||||||
sessionId, err := sstore.InsertSessionWithName(r.Context(), name)
|
|
||||||
if err != nil {
|
|
||||||
WriteJsonError(w, fmt.Errorf("inserting session: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
session, err := sstore.GetSessionById(r.Context(), sessionId)
|
|
||||||
if err != nil {
|
|
||||||
WriteJsonError(w, fmt.Errorf("getting new session: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJsonSuccess(w, session)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// params: sessionid, name
|
|
||||||
func HandleCreateWindow(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
w.Header().Set("Vary", "Origin")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
|
||||||
qvals := r.URL.Query()
|
|
||||||
sessionId := qvals.Get("sessionid")
|
|
||||||
if _, err := uuid.Parse(sessionId); err != nil {
|
|
||||||
WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
name := qvals.Get("name")
|
|
||||||
fmt.Printf("insert-window %s\n", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// params: sessionid, name, activate
|
|
||||||
func HandleCreateScreen(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
w.Header().Set("Vary", "Origin")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
|
||||||
qvals := r.URL.Query()
|
|
||||||
sessionId := qvals.Get("sessionid")
|
|
||||||
if _, err := uuid.Parse(sessionId); err != nil {
|
|
||||||
WriteJsonError(w, fmt.Errorf("invalid sessionid: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
name := qvals.Get("name")
|
|
||||||
activateStr := qvals.Get("activate")
|
|
||||||
activate := activateStr != ""
|
|
||||||
screenId, err := sstore.InsertScreen(r.Context(), sessionId, name, activate)
|
|
||||||
if err != nil {
|
|
||||||
WriteJsonError(w, fmt.Errorf("inserting screen: %w", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
WriteJsonSuccess(w, screenId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// params: [none]
|
// params: [none]
|
||||||
func HandleGetRemotes(w http.ResponseWriter, r *http.Request) {
|
func HandleGetRemotes(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||||
@ -304,7 +240,7 @@ func HandleGetPtyOut(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
func WriteJsonError(w http.ResponseWriter, errVal error) {
|
func WriteJsonError(w http.ResponseWriter, errVal error) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(200)
|
||||||
errMap := make(map[string]interface{})
|
errMap := make(map[string]interface{})
|
||||||
errMap["error"] = errVal.Error()
|
errMap["error"] = errVal.Error()
|
||||||
barr, _ := json.Marshal(errMap)
|
barr, _ := json.Marshal(errMap)
|
||||||
@ -329,11 +265,6 @@ func WriteJsonSuccess(w http.ResponseWriter, data interface{}) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type runCommandResponse struct {
|
|
||||||
Line *sstore.LineType `json:"line"`
|
|
||||||
Cmd *sstore.CmdType `json:"cmd"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
|
func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
r := recover()
|
r := recover()
|
||||||
@ -360,127 +291,15 @@ func HandleRunCommand(w http.ResponseWriter, r *http.Request) {
|
|||||||
WriteJsonError(w, fmt.Errorf("error decoding json: %w", err))
|
WriteJsonError(w, fmt.Errorf("error decoding json: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err = uuid.Parse(commandPk.SessionId); err != nil {
|
update, err := cmdrunner.HandleCommand(r.Context(), &commandPk)
|
||||||
WriteJsonError(w, fmt.Errorf("invalid sessionid '%s': %w", commandPk.SessionId, err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
resp, err := ProcessFeCommandPacket(r.Context(), &commandPk)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
WriteJsonError(w, err)
|
WriteJsonError(w, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
WriteJsonSuccess(w, resp)
|
WriteJsonSuccess(w, update)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProcessFeCommandPacket(ctx context.Context, pk *scpacket.FeCommandPacketType) (*runCommandResponse, error) {
|
|
||||||
// parse metacmd
|
|
||||||
commandStr := strings.TrimSpace(pk.CmdStr)
|
|
||||||
if commandStr == "" {
|
|
||||||
return nil, fmt.Errorf("invalid emtpty command")
|
|
||||||
}
|
|
||||||
metaCmd := ""
|
|
||||||
metaSubCmd := ""
|
|
||||||
if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") {
|
|
||||||
metaCmd = "cd"
|
|
||||||
commandStr = strings.TrimSpace(commandStr[2:])
|
|
||||||
} else if commandStr[0] == '/' {
|
|
||||||
spaceIdx := strings.Index(commandStr, " ")
|
|
||||||
if spaceIdx == -1 {
|
|
||||||
metaCmd = commandStr[1:]
|
|
||||||
commandStr = ""
|
|
||||||
} else {
|
|
||||||
metaCmd = commandStr[1:spaceIdx]
|
|
||||||
commandStr = strings.TrimSpace(commandStr[spaceIdx+1:])
|
|
||||||
}
|
|
||||||
colonIdx := strings.Index(metaCmd, ":")
|
|
||||||
if colonIdx != -1 {
|
|
||||||
metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:]
|
|
||||||
}
|
|
||||||
if metaCmd == "" {
|
|
||||||
return nil, fmt.Errorf("invalid command, got bare '/', with no command")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute metacmd
|
|
||||||
if metaCmd == "comment" {
|
|
||||||
text := commandStr
|
|
||||||
rtnLine, err := sstore.AddCommentLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, text)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &runCommandResponse{Line: rtnLine}, nil
|
|
||||||
} else if metaCmd == "cd" {
|
|
||||||
newDir := commandStr
|
|
||||||
cdPacket := packet.MakeCdPacket()
|
|
||||||
cdPacket.ReqId = uuid.New().String()
|
|
||||||
cdPacket.Dir = newDir
|
|
||||||
localRemote := remote.GetRemoteById(pk.RemoteState.RemoteId)
|
|
||||||
if localRemote == nil {
|
|
||||||
return nil, fmt.Errorf("invalid remote, cannot execute command")
|
|
||||||
}
|
|
||||||
resp, err := localRemote.PacketRpc(ctx, cdPacket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fmt.Printf("GOT cd RESP: %v\n", resp)
|
|
||||||
return nil, nil
|
|
||||||
} else if metaCmd == "s" || metaCmd == "screen" {
|
|
||||||
err := RunScreenCmd(ctx, pk.SessionId, pk.ScreenId, metaSubCmd, commandStr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
} else if metaCmd == "" {
|
|
||||||
cmdId := uuid.New().String()
|
|
||||||
cmd, err := remote.RunCommand(ctx, pk, cmdId)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rtnLine, err := sstore.AddCmdLine(ctx, pk.SessionId, pk.WindowId, pk.UserId, cmd)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &runCommandResponse{Line: rtnLine, Cmd: cmd}, nil
|
|
||||||
} else {
|
|
||||||
fmt.Printf("INVALID metacmd '%s'\n", metaCmd)
|
|
||||||
return nil, fmt.Errorf("Invalid command type /%s", metaCmd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunScreenCmd(ctx context.Context, sessionId string, screenId string, subCmd string, commandStr string) error {
|
|
||||||
if subCmd == "close" {
|
|
||||||
err := sstore.DeleteScreen(ctx, sessionId, screenId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if subCmd != "" {
|
|
||||||
return fmt.Errorf("invalid /screen subcommand '%s'", subCmd)
|
|
||||||
}
|
|
||||||
if commandStr == "" {
|
|
||||||
return fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified")
|
|
||||||
}
|
|
||||||
screens, err := sstore.GetSessionScreens(ctx, sessionId)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not retreive screens for session=%s", sessionId)
|
|
||||||
}
|
|
||||||
screenNum, err := strconv.Atoi(commandStr)
|
|
||||||
if err == nil {
|
|
||||||
if screenNum < 1 || screenNum > len(screens) {
|
|
||||||
return fmt.Errorf("could not switch to screen #%d (out of range), valid screens 1-%d", screenNum, len(screens))
|
|
||||||
}
|
|
||||||
return sstore.SwitchScreenById(ctx, sessionId, screens[screenNum-1].ScreenId)
|
|
||||||
}
|
|
||||||
for _, screen := range screens {
|
|
||||||
if screen.ScreenId == commandStr || screen.Name == commandStr {
|
|
||||||
return sstore.SwitchScreenById(ctx, sessionId, screen.ScreenId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("could not switch to screen '%s' (name/id not found)", commandStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// /api/start-session
|
// /api/start-session
|
||||||
// returns:
|
// returns:
|
||||||
// * userid
|
// * userid
|
||||||
@ -620,11 +439,8 @@ func main() {
|
|||||||
gr := mux.NewRouter()
|
gr := mux.NewRouter()
|
||||||
gr.HandleFunc("/api/ptyout", HandleGetPtyOut)
|
gr.HandleFunc("/api/ptyout", HandleGetPtyOut)
|
||||||
gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions)
|
gr.HandleFunc("/api/get-all-sessions", HandleGetAllSessions)
|
||||||
gr.HandleFunc("/api/create-session", HandleCreateSession)
|
|
||||||
gr.HandleFunc("/api/get-window", HandleGetWindow)
|
gr.HandleFunc("/api/get-window", HandleGetWindow)
|
||||||
gr.HandleFunc("/api/get-remotes", HandleGetRemotes)
|
gr.HandleFunc("/api/get-remotes", HandleGetRemotes)
|
||||||
gr.HandleFunc("/api/create-window", HandleCreateWindow)
|
|
||||||
gr.HandleFunc("/api/create-screen", HandleCreateScreen)
|
|
||||||
gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS")
|
gr.HandleFunc("/api/run-command", HandleRunCommand).Methods("GET", "POST", "OPTIONS")
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: MainServerAddr,
|
Addr: MainServerAddr,
|
||||||
|
367
pkg/cmdrunner/cmdrunner.go
Normal file
367
pkg/cmdrunner/cmdrunner.go
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
package cmdrunner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||||
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/remote"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
||||||
|
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultUserId = "sawka"
|
||||||
|
|
||||||
|
const (
|
||||||
|
R_Session = 1
|
||||||
|
R_Screen = 2
|
||||||
|
R_Window = 4
|
||||||
|
R_Remote = 8
|
||||||
|
R_SessionOpt = 16
|
||||||
|
R_ScreenOpt = 32
|
||||||
|
R_WindowOpt = 64
|
||||||
|
R_RemoteOpt = 128
|
||||||
|
)
|
||||||
|
|
||||||
|
type resolvedIds struct {
|
||||||
|
SessionId string
|
||||||
|
ScreenId string
|
||||||
|
WindowId string
|
||||||
|
RemoteId string
|
||||||
|
RemoteState *sstore.RemoteState
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubMetaCmd(cmd string) string {
|
||||||
|
switch cmd {
|
||||||
|
case "s":
|
||||||
|
return "screen"
|
||||||
|
case "w":
|
||||||
|
return "window"
|
||||||
|
case "r":
|
||||||
|
return "run"
|
||||||
|
case "c":
|
||||||
|
return "comment"
|
||||||
|
case "e":
|
||||||
|
return "eval"
|
||||||
|
default:
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstArg(pk *scpacket.FeCommandPacketType) string {
|
||||||
|
if len(pk.Args) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pk.Args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveBool(arg string, def bool) bool {
|
||||||
|
if arg == "" {
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
if arg == "0" || arg == "false" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveSessionScreen(ctx context.Context, sessionId string, screenArg string) (string, error) {
|
||||||
|
screens, err := sstore.GetSessionScreens(ctx, sessionId)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not retreive screens for session=%s", sessionId)
|
||||||
|
}
|
||||||
|
screenNum, err := strconv.Atoi(screenArg)
|
||||||
|
if err == nil {
|
||||||
|
if screenNum < 1 || screenNum > len(screens) {
|
||||||
|
return "", fmt.Errorf("could not resolve screen #%d (out of range), valid screens 1-%d", screenNum, len(screens))
|
||||||
|
}
|
||||||
|
return screens[screenNum-1].ScreenId, nil
|
||||||
|
}
|
||||||
|
for _, screen := range screens {
|
||||||
|
if screen.ScreenId == screenArg || screen.Name == screenArg {
|
||||||
|
return screen.ScreenId, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("could not resolve screen '%s' (name/id not found)", screenArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveSessionId(pk *scpacket.FeCommandPacketType) (string, error) {
|
||||||
|
sessionId := pk.Kwargs["session"]
|
||||||
|
if sessionId == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if _, err := uuid.Parse(sessionId); err != nil {
|
||||||
|
return "", fmt.Errorf("invalid sessionid '%s'", sessionId)
|
||||||
|
}
|
||||||
|
return sessionId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveWindowId(pk *scpacket.FeCommandPacketType, sessionId string) (string, error) {
|
||||||
|
windowId := pk.Kwargs["window"]
|
||||||
|
if windowId == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if _, err := uuid.Parse(windowId); err != nil {
|
||||||
|
return "", fmt.Errorf("invalid windowid '%s'", windowId)
|
||||||
|
}
|
||||||
|
return windowId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveScreenId(ctx context.Context, pk *scpacket.FeCommandPacketType, sessionId string) (string, error) {
|
||||||
|
screenArg := pk.Kwargs["screen"]
|
||||||
|
if screenArg == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if _, err := uuid.Parse(screenArg); err == nil {
|
||||||
|
return screenArg, nil
|
||||||
|
}
|
||||||
|
if sessionId == "" {
|
||||||
|
return "", fmt.Errorf("cannot resolve screen without session")
|
||||||
|
}
|
||||||
|
return resolveSessionScreen(ctx, sessionId, screenArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveRemote(ctx context.Context, pk *scpacket.FeCommandPacketType, sessionId string, windowId string) (string, *sstore.RemoteState, error) {
|
||||||
|
remoteArg := pk.Kwargs["remote"]
|
||||||
|
if remoteArg == "" {
|
||||||
|
return "", nil, nil
|
||||||
|
}
|
||||||
|
remoteId, state, err := sstore.GetRemoteState(ctx, remoteArg, sessionId, windowId)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteArg, err)
|
||||||
|
}
|
||||||
|
if state == nil {
|
||||||
|
state, err = remote.GetDefaultRemoteStateById(remoteId)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, fmt.Errorf("cannot resolve remote '%s': %w", remoteArg, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remoteId, state, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype int) (resolvedIds, error) {
|
||||||
|
rtn := resolvedIds{}
|
||||||
|
if rtype == 0 {
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if (rtype&R_Session)+(rtype&R_SessionOpt) > 0 {
|
||||||
|
rtn.SessionId, err = resolveSessionId(pk)
|
||||||
|
if err != nil {
|
||||||
|
return rtn, err
|
||||||
|
}
|
||||||
|
if rtn.SessionId == "" && (rtype&R_Session) > 0 {
|
||||||
|
return rtn, fmt.Errorf("no session")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rtype&R_Window)+(rtype&R_WindowOpt) > 0 {
|
||||||
|
rtn.WindowId, err = resolveWindowId(pk, rtn.SessionId)
|
||||||
|
if err != nil {
|
||||||
|
return rtn, err
|
||||||
|
}
|
||||||
|
if rtn.WindowId == "" && (rtype&R_Window) > 0 {
|
||||||
|
return rtn, fmt.Errorf("no window")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if (rtype&R_Screen)+(rtype&R_ScreenOpt) > 0 {
|
||||||
|
rtn.ScreenId, err = resolveScreenId(ctx, pk, rtn.SessionId)
|
||||||
|
if err != nil {
|
||||||
|
return rtn, err
|
||||||
|
}
|
||||||
|
if rtn.ScreenId == "" && (rtype&R_Screen) > 0 {
|
||||||
|
return rtn, fmt.Errorf("no screen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rtype&R_Remote)+(rtype&R_RemoteOpt) > 0 {
|
||||||
|
rtn.RemoteId, rtn.RemoteState, err = resolveRemote(ctx, pk, rtn.SessionId, rtn.WindowId)
|
||||||
|
if err != nil {
|
||||||
|
return rtn, err
|
||||||
|
}
|
||||||
|
if rtn.RemoteId == "" && (rtype&R_Remote) > 0 {
|
||||||
|
return rtn, fmt.Errorf("no remote")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rtn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
switch SubMetaCmd(pk.MetaCmd) {
|
||||||
|
case "run":
|
||||||
|
return RunCommand(ctx, pk)
|
||||||
|
|
||||||
|
case "eval":
|
||||||
|
return EvalCommand(ctx, pk)
|
||||||
|
|
||||||
|
case "screen":
|
||||||
|
return ScreenCommand(ctx, pk)
|
||||||
|
|
||||||
|
case "comment":
|
||||||
|
return CommentCommand(ctx, pk)
|
||||||
|
|
||||||
|
case "cd":
|
||||||
|
return CdCommand(ctx, pk)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid command '/%s', no handler", pk.MetaCmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("/run error: %w", err)
|
||||||
|
}
|
||||||
|
cmdId := uuid.New().String()
|
||||||
|
cmdStr := firstArg(pk)
|
||||||
|
runPacket := packet.MakeRunPacket()
|
||||||
|
runPacket.ReqId = uuid.New().String()
|
||||||
|
runPacket.CK = base.MakeCommandKey(ids.SessionId, cmdId)
|
||||||
|
runPacket.Cwd = ids.RemoteState.Cwd
|
||||||
|
runPacket.Env = nil
|
||||||
|
runPacket.UsePty = true
|
||||||
|
runPacket.TermOpts = &packet.TermOpts{Rows: remote.DefaultTermRows, Cols: remote.DefaultTermCols, Term: remote.DefaultTerm}
|
||||||
|
runPacket.Command = strings.TrimSpace(cmdStr)
|
||||||
|
cmd, err := remote.RunCommand(ctx, cmdId, ids.RemoteId, ids.RemoteState, runPacket)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rtnLine, err := sstore.AddCmdLine(ctx, ids.SessionId, ids.WindowId, DefaultUserId, cmd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sstore.LineUpdate{Line: rtnLine, Cmd: cmd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
if len(pk.Args) == 0 {
|
||||||
|
return nil, fmt.Errorf("usage: /eval [command], no command passed to eval")
|
||||||
|
}
|
||||||
|
// parse metacmd
|
||||||
|
commandStr := strings.TrimSpace(pk.Args[0])
|
||||||
|
if commandStr == "" {
|
||||||
|
return nil, fmt.Errorf("/eval, invalid emtpty command")
|
||||||
|
}
|
||||||
|
metaCmd := ""
|
||||||
|
metaSubCmd := ""
|
||||||
|
if commandStr == "cd" || strings.HasPrefix(commandStr, "cd ") {
|
||||||
|
metaCmd = "cd"
|
||||||
|
commandStr = strings.TrimSpace(commandStr[2:])
|
||||||
|
} else if commandStr[0] == '/' {
|
||||||
|
spaceIdx := strings.Index(commandStr, " ")
|
||||||
|
if spaceIdx == -1 {
|
||||||
|
metaCmd = commandStr[1:]
|
||||||
|
commandStr = ""
|
||||||
|
} else {
|
||||||
|
metaCmd = commandStr[1:spaceIdx]
|
||||||
|
commandStr = strings.TrimSpace(commandStr[spaceIdx+1:])
|
||||||
|
}
|
||||||
|
colonIdx := strings.Index(metaCmd, ":")
|
||||||
|
if colonIdx != -1 {
|
||||||
|
metaCmd, metaSubCmd = metaCmd[0:colonIdx], metaCmd[colonIdx+1:]
|
||||||
|
}
|
||||||
|
if metaCmd == "" {
|
||||||
|
return nil, fmt.Errorf("invalid command, got bare '/', with no command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metaCmd == "" {
|
||||||
|
metaCmd = "run"
|
||||||
|
}
|
||||||
|
var args []string
|
||||||
|
if metaCmd == "run" || metaCmd == "comment" {
|
||||||
|
args = []string{commandStr}
|
||||||
|
} else {
|
||||||
|
args = strings.Fields(commandStr)
|
||||||
|
}
|
||||||
|
newPk := &scpacket.FeCommandPacketType{
|
||||||
|
MetaCmd: metaCmd,
|
||||||
|
MetaSubCmd: metaSubCmd,
|
||||||
|
Args: args,
|
||||||
|
Kwargs: pk.Kwargs,
|
||||||
|
}
|
||||||
|
return HandleCommand(ctx, newPk)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ScreenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
if pk.MetaSubCmd == "close" {
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session|R_Screen)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("/screen:close cannot close screen: %w", err)
|
||||||
|
}
|
||||||
|
err = sstore.DeleteScreen(ctx, ids.SessionId, ids.ScreenId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if pk.MetaSubCmd == "open" || pk.MetaSubCmd == "new" {
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("/screen:open cannot open screen: %w", err)
|
||||||
|
}
|
||||||
|
activate := resolveBool(pk.Kwargs["activate"], true)
|
||||||
|
_, err = sstore.InsertScreen(ctx, ids.SessionId, pk.Kwargs["name"], activate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if pk.MetaSubCmd != "" {
|
||||||
|
return nil, fmt.Errorf("invalid /screen subcommand '%s'", pk.MetaSubCmd)
|
||||||
|
}
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("/screen cannot switch to screen: %w", err)
|
||||||
|
}
|
||||||
|
firstArg := firstArg(pk)
|
||||||
|
if firstArg == "" {
|
||||||
|
return nil, fmt.Errorf("usage /screen [screen-name|screen-index|screen-id], no param specified")
|
||||||
|
}
|
||||||
|
screenIdArg, err := resolveSessionScreen(ctx, ids.SessionId, firstArg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sstore.SwitchScreenById(ctx, ids.SessionId, screenIdArg)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CdCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session|R_Window|R_Remote)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("/cd error: %w", err)
|
||||||
|
}
|
||||||
|
newDir := firstArg(pk)
|
||||||
|
cdPacket := packet.MakeCdPacket()
|
||||||
|
cdPacket.ReqId = uuid.New().String()
|
||||||
|
cdPacket.Dir = newDir
|
||||||
|
localRemote := remote.GetRemoteById(ids.RemoteId)
|
||||||
|
if localRemote == nil {
|
||||||
|
return nil, fmt.Errorf("invalid remote, cannot execute command")
|
||||||
|
}
|
||||||
|
resp, err := localRemote.PacketRpc(ctx, cdPacket)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fmt.Printf("GOT cd RESP: %v\n", resp)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommentCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||||
|
ids, err := resolveIds(ctx, pk, R_Session|R_Window)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("/comment error: %w", err)
|
||||||
|
}
|
||||||
|
text := firstArg(pk)
|
||||||
|
if strings.TrimSpace(text) == "" {
|
||||||
|
return nil, fmt.Errorf("cannot post empty comment")
|
||||||
|
}
|
||||||
|
rtnLine, err := sstore.AddCommentLine(ctx, ids.SessionId, ids.WindowId, DefaultUserId, text)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sstore.LineUpdate{Line: rtnLine}, nil
|
||||||
|
}
|
@ -6,14 +6,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/scpacket"
|
|
||||||
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
"github.com/scripthaus-dev/sh2-server/pkg/sstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -123,6 +120,21 @@ func GetAllRemoteState() []RemoteState {
|
|||||||
return rtn
|
return rtn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetDefaultRemoteStateById(remoteId string) (*sstore.RemoteState, error) {
|
||||||
|
remote := GetRemoteById(remoteId)
|
||||||
|
if remote == nil {
|
||||||
|
return nil, fmt.Errorf("remote not found")
|
||||||
|
}
|
||||||
|
if !remote.IsConnected() {
|
||||||
|
return nil, fmt.Errorf("remote not connected")
|
||||||
|
}
|
||||||
|
state := remote.GetDefaultState()
|
||||||
|
if state == nil {
|
||||||
|
return nil, fmt.Errorf("could not get default remote state")
|
||||||
|
}
|
||||||
|
return state, nil
|
||||||
|
}
|
||||||
|
|
||||||
func MakeMShell(r *sstore.RemoteType) *MShellProc {
|
func MakeMShell(r *sstore.RemoteType) *MShellProc {
|
||||||
rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit}
|
rtn := &MShellProc{Lock: &sync.Mutex{}, Remote: r, Status: StatusInit}
|
||||||
return rtn
|
return rtn
|
||||||
@ -168,6 +180,15 @@ func (msh *MShellProc) IsConnected() bool {
|
|||||||
return msh.Status == StatusConnected
|
return msh.Status == StatusConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (msh *MShellProc) GetDefaultState() *sstore.RemoteState {
|
||||||
|
msh.Lock.Lock()
|
||||||
|
defer msh.Lock.Unlock()
|
||||||
|
if msh.ServerProc == nil || msh.ServerProc.InitPk == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &sstore.RemoteState{Cwd: msh.ServerProc.InitPk.HomeDir}
|
||||||
|
}
|
||||||
|
|
||||||
func (msh *MShellProc) IsCmdRunning(ck base.CommandKey) bool {
|
func (msh *MShellProc) IsCmdRunning(ck base.CommandKey) bool {
|
||||||
msh.Lock.Lock()
|
msh.Lock.Lock()
|
||||||
defer msh.Lock.Unlock()
|
defer msh.Lock.Unlock()
|
||||||
@ -193,30 +214,21 @@ func (msh *MShellProc) SendInput(pk *packet.InputPacketType) error {
|
|||||||
return msh.ServerProc.Input.SendPacket(dataPk)
|
return msh.ServerProc.Input.SendPacket(dataPk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertRemoteState(rs scpacket.RemoteState) sstore.RemoteState {
|
|
||||||
return sstore.RemoteState{Cwd: rs.Cwd}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeTermOpts() sstore.TermOpts {
|
func makeTermOpts() sstore.TermOpts {
|
||||||
return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true}
|
return sstore.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, FlexRows: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId string) (*sstore.CmdType, error) {
|
func RunCommand(ctx context.Context, cmdId string, remoteId string, remoteState *sstore.RemoteState, runPacket *packet.RunPacketType) (*sstore.CmdType, error) {
|
||||||
msh := GetRemoteById(pk.RemoteState.RemoteId)
|
msh := GetRemoteById(remoteId)
|
||||||
if msh == nil {
|
if msh == nil {
|
||||||
return nil, fmt.Errorf("no remote id=%s found", pk.RemoteState.RemoteId)
|
return nil, fmt.Errorf("no remote id=%s found", remoteId)
|
||||||
}
|
}
|
||||||
if !msh.IsConnected() {
|
if !msh.IsConnected() {
|
||||||
return nil, fmt.Errorf("remote '%s' is not connected", msh.Remote.RemoteName)
|
return nil, fmt.Errorf("remote '%s' is not connected", remoteId)
|
||||||
|
}
|
||||||
|
if remoteState == nil {
|
||||||
|
return nil, fmt.Errorf("no remote state passed to RunCommand")
|
||||||
}
|
}
|
||||||
runPacket := packet.MakeRunPacket()
|
|
||||||
runPacket.ReqId = uuid.New().String()
|
|
||||||
runPacket.CK = base.MakeCommandKey(pk.SessionId, cmdId)
|
|
||||||
runPacket.Cwd = pk.RemoteState.Cwd
|
|
||||||
runPacket.Env = nil
|
|
||||||
runPacket.UsePty = true
|
|
||||||
runPacket.TermOpts = &packet.TermOpts{Rows: DefaultTermRows, Cols: DefaultTermCols, Term: DefaultTerm}
|
|
||||||
runPacket.Command = strings.TrimSpace(pk.CmdStr)
|
|
||||||
fmt.Printf("RUN-CMD> %s reqid=%s (msh=%v)\n", runPacket.CK, runPacket.ReqId, msh.Remote)
|
fmt.Printf("RUN-CMD> %s reqid=%s (msh=%v)\n", runPacket.CK, runPacket.ReqId, msh.Remote)
|
||||||
msh.ServerProc.Output.RegisterRpc(runPacket.ReqId)
|
msh.ServerProc.Output.RegisterRpc(runPacket.ReqId)
|
||||||
err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket)
|
err := shexec.SendRunPacketAndRunData(ctx, msh.ServerProc.Input, runPacket)
|
||||||
@ -240,11 +252,11 @@ func RunCommand(ctx context.Context, pk *scpacket.FeCommandPacketType, cmdId str
|
|||||||
status = sstore.CmdStatusDetached
|
status = sstore.CmdStatusDetached
|
||||||
}
|
}
|
||||||
cmd := &sstore.CmdType{
|
cmd := &sstore.CmdType{
|
||||||
SessionId: pk.SessionId,
|
SessionId: runPacket.CK.GetSessionId(),
|
||||||
CmdId: startPk.CK.GetCmdId(),
|
CmdId: startPk.CK.GetCmdId(),
|
||||||
CmdStr: runPacket.Command,
|
CmdStr: runPacket.Command,
|
||||||
RemoteId: msh.Remote.RemoteId,
|
RemoteId: msh.Remote.RemoteId,
|
||||||
RemoteState: convertRemoteState(pk.RemoteState),
|
RemoteState: *remoteState,
|
||||||
TermOpts: makeTermOpts(),
|
TermOpts: makeTermOpts(),
|
||||||
Status: status,
|
Status: status,
|
||||||
StartPk: startPk,
|
StartPk: startPk,
|
||||||
|
@ -16,13 +16,11 @@ type RemoteState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FeCommandPacketType struct {
|
type FeCommandPacketType struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
SessionId string `json:"sessionid"`
|
MetaCmd string `json:"metacmd"`
|
||||||
ScreenId string `json:"screenid"`
|
MetaSubCmd string `json:"metasubcmd,omitempty"`
|
||||||
WindowId string `json:"windowid"`
|
Args []string `json:"args,omitempty"`
|
||||||
UserId string `json:"userid"`
|
Kwargs map[string]string `json:"kwargs,omitempty"`
|
||||||
CmdStr string `json:"cmdstr"`
|
|
||||||
RemoteState RemoteState `json:"remotestate"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -468,3 +468,25 @@ func DeleteScreen(ctx context.Context, sessionId string, screenId string) error
|
|||||||
MainBus.SendUpdate("", update)
|
MainBus.SendUpdate("", update)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetRemoteState(ctx context.Context, rname string, sessionId string, windowId string) (string, *RemoteState, error) {
|
||||||
|
var remoteId string
|
||||||
|
var remoteState *RemoteState
|
||||||
|
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||||
|
var ri RemoteInstance
|
||||||
|
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND windowid = ? AND name = ?`
|
||||||
|
found := tx.GetWrap(&ri, query, sessionId, windowId, rname)
|
||||||
|
if found {
|
||||||
|
remoteId = ri.RemoteId
|
||||||
|
remoteState = &ri.State
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
query = `SELECT remoteid FROM remote WHERE remotename = ?`
|
||||||
|
remoteId = tx.GetString(query, rname)
|
||||||
|
if remoteId == "" {
|
||||||
|
return fmt.Errorf("remote not found", rname)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return remoteId, remoteState, txErr
|
||||||
|
}
|
||||||
|
@ -4,6 +4,16 @@ import "sync"
|
|||||||
|
|
||||||
var MainBus *UpdateBus = MakeUpdateBus()
|
var MainBus *UpdateBus = MakeUpdateBus()
|
||||||
|
|
||||||
|
const PtyDataUpdateStr = "pty"
|
||||||
|
const SessionUpdateStr = "session"
|
||||||
|
const WindowUpdateStr = "window"
|
||||||
|
const CmdUpdateStr = "cmd"
|
||||||
|
const LineCmdUpdateStr = "line+cmd"
|
||||||
|
|
||||||
|
type UpdatePacket interface {
|
||||||
|
UpdateType() string
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateCmd struct {
|
type UpdateCmd struct {
|
||||||
CmdId string
|
CmdId string
|
||||||
Status string
|
Status string
|
||||||
@ -17,15 +27,27 @@ type PtyDataUpdate struct {
|
|||||||
PtyDataLen int64 `json:"ptydatalen"`
|
PtyDataLen int64 `json:"ptydatalen"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (PtyDataUpdate) UpdateType() string {
|
||||||
|
return PtyDataUpdateStr
|
||||||
|
}
|
||||||
|
|
||||||
type WindowUpdate struct {
|
type WindowUpdate struct {
|
||||||
Window WindowType `json:"window"`
|
Window WindowType `json:"window"`
|
||||||
Remove bool `json:"remove,omitempty"`
|
Remove bool `json:"remove,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (WindowUpdate) WindowUpdate() string {
|
||||||
|
return WindowUpdateStr
|
||||||
|
}
|
||||||
|
|
||||||
type SessionUpdate struct {
|
type SessionUpdate struct {
|
||||||
Sessions []*SessionType `json:"sessions"`
|
Sessions []*SessionType `json:"sessions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (SessionUpdate) UpdateType() string {
|
||||||
|
return SessionUpdateStr
|
||||||
|
}
|
||||||
|
|
||||||
func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
|
func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
|
||||||
session := &SessionType{
|
session := &SessionType{
|
||||||
SessionId: sessionId,
|
SessionId: sessionId,
|
||||||
@ -37,9 +59,14 @@ func MakeSingleSessionUpdate(sessionId string) (*SessionUpdate, *SessionType) {
|
|||||||
return update, session
|
return update, session
|
||||||
}
|
}
|
||||||
|
|
||||||
type CmdUpdate struct {
|
type LineUpdate struct {
|
||||||
Cmd CmdType `json:"cmd"`
|
Line *LineType `json:"line"`
|
||||||
Remove bool `json:"remove,omitempty"`
|
Cmd *CmdType `json:"cmd,omitempty"`
|
||||||
|
Remove bool `json:"remove,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (LineUpdate) UpdateType() string {
|
||||||
|
return LineCmdUpdateStr
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateChannel struct {
|
type UpdateChannel struct {
|
||||||
|
Loading…
Reference in New Issue
Block a user