waveterm/waveshell/main-waveshell.go
Mike Sawka bff51c851a
sync command to synchronize shell state with wave prompt (#444)
* remove two unused packet types, remove unused detatched command code

* CmdStart is invalid in this command loop

* slight refactor, remove closure funcs

* pass rct through to 'handle' funcs

* deal with rct (running command), update handler funcs accordingly

* update for runningcmdtype to be a pointer in the map (for updates)

* lots of changes related to ephemeral commands (for sync), checkpoint

* fix ephemeral setting

* sync shell state when you switch to a new tab
2024-03-13 18:52:41 -07:00

151 lines
4.1 KiB
Go

// Copyright 2023, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package main
import (
"fmt"
"os"
"strings"
"syscall"
"time"
"github.com/wavetermdev/waveterm/waveshell/pkg/base"
"github.com/wavetermdev/waveterm/waveshell/pkg/packet"
"github.com/wavetermdev/waveterm/waveshell/pkg/server"
"github.com/wavetermdev/waveterm/waveshell/pkg/shexec"
"github.com/wavetermdev/waveterm/waveshell/pkg/wlog"
)
var BuildTime = "0"
func readFullRunPacket(packetParser *packet.PacketParser) (*packet.RunPacketType, error) {
rpb := packet.MakeRunPacketBuilder()
for pk := range packetParser.MainCh {
ok, runPacket := rpb.ProcessPacket(pk)
if runPacket != nil {
return runPacket, nil
}
if !ok {
return nil, fmt.Errorf("invalid packet '%s' sent to mshell", pk.GetType())
}
}
return nil, fmt.Errorf("no run packet received")
}
func handleSingle() {
packetParser := packet.MakePacketParser(os.Stdin, nil)
sender := packet.MakePacketSender(os.Stdout, nil)
defer func() {
sender.Close()
sender.WaitForDone()
}()
wlog.LogConsumer = sender.SendLogPacket
initPacket := shexec.MakeInitPacket()
sender.SendPacket(initPacket)
if len(os.Args) >= 3 && os.Args[2] == "--version" {
return
}
runPacket, err := readFullRunPacket(packetParser)
if err != nil {
sender.SendErrorResponse(runPacket.ReqId, err)
return
}
err = shexec.ValidateRunPacket(runPacket)
if err != nil {
sender.SendErrorResponse(runPacket.ReqId, err)
return
}
err = runPacket.CK.Validate("run packet")
if err != nil {
sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("run packets from server must have a CK: %v", err))
}
if runPacket.Detached {
sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("detached mode not supported"))
return
} else {
shexec.IgnoreSigPipe()
ticker := time.NewTicker(1 * time.Minute)
go func() {
for range ticker.C {
// this will let the command detect when the server has gone away
// that will then trigger cmd.SendHup() to send SIGHUP to the exec'ed process
sender.SendPacket(packet.MakePingPacket())
}
}()
defer ticker.Stop()
cmd, err := shexec.RunCommandSimple(runPacket, sender, true)
if err != nil {
sender.SendErrorResponse(runPacket.ReqId, fmt.Errorf("error running command: %w", err))
return
}
defer cmd.Close()
startPacket := cmd.MakeCmdStartPacket(runPacket.ReqId)
sender.SendPacket(startPacket)
go func() {
exitErr := sender.WaitForDone()
if exitErr != nil {
base.Logf("I/O error talking to server, sending SIGHUP to children\n")
cmd.SendSignal(syscall.SIGHUP)
}
}()
cmd.RunRemoteIOAndWait(packetParser, sender)
return
}
}
func handleUsage() {
usage := `
mshell is a helper program for wave terminal. it is used to execute commands
Options:
--help - prints this message
--version - print version
--server - multiplexer to run multiple commands
--single - run a single command (connected to multiplexer)
--single --version - return an init packet with version info
mshell does not open any external ports and does not require any additional permissions.
it communicates exclusively through stdin/stdout with an attached process
via a JSON packet format.
`
fmt.Printf("%s\n\n", strings.TrimSpace(usage))
}
func main() {
base.SetBuildTime(BuildTime)
if len(os.Args) == 1 {
handleUsage()
return
}
firstArg := os.Args[1]
if firstArg == "--help" {
handleUsage()
return
} else if firstArg == "--version" {
fmt.Printf("mshell %s+%s\n", base.MShellVersion, base.BuildTime)
return
} else if firstArg == "--single" || firstArg == "--single-from-server" {
base.ProcessType = base.ProcessType_WaveShellSingle
wlog.GlobalSubsystem = base.ProcessType_WaveShellSingle
base.InitDebugLog("single")
handleSingle()
return
} else if firstArg == "--server" {
base.ProcessType = base.ProcessType_WaveShellServer
wlog.GlobalSubsystem = base.ProcessType_WaveShellServer
base.InitDebugLog("server")
rtnCode, err := server.RunServer()
if err != nil {
fmt.Fprintf(os.Stderr, "[error] %v\n", err)
}
if rtnCode != 0 {
os.Exit(rtnCode)
}
return
} else {
handleUsage()
return
}
}