mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
remote connect/disconnect working. fix issue with remoteconnected in resolver. working on remote:new
This commit is contained in:
+134
-1
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -30,9 +31,14 @@ const (
|
||||
|
||||
const DefaultUserId = "sawka"
|
||||
const MaxNameLen = 50
|
||||
const MaxRemoteAliasLen = 50
|
||||
|
||||
var ColorNames = []string{"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
|
||||
var RemoteColorNames = []string{"red", "green", "yellow", "blue", "magenta", "cyan", "white", "orange"}
|
||||
|
||||
var hostNameRe = regexp.MustCompile("^[a-z][a-z0-9.-]*$")
|
||||
var userHostRe = regexp.MustCompile("^(sudo@)?([a-z][a-z0-9-]*)@([a-z][a-z0-9.-]*)$")
|
||||
var remoteAliasRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_-]*$")
|
||||
var genericNameRe = regexp.MustCompile("^[a-zA-Z][a-zA-Z0-9_ .()<>,/\"'\\[\\]{}=+$@!*-]*$")
|
||||
var positionRe = regexp.MustCompile("^((\\+|-)?[0-9]+|(\\+|-))$")
|
||||
var wsRe = regexp.MustCompile("\\s+")
|
||||
@@ -71,6 +77,8 @@ func init() {
|
||||
registerCmdFn("remote:show", RemoteShowCommand)
|
||||
registerCmdFn("remote:showall", RemoteShowAllCommand)
|
||||
registerCmdFn("remote:new", RemoteNewCommand)
|
||||
registerCmdFn("remote:disconnect", RemoteDisconnectCommand)
|
||||
registerCmdFn("remote:connect", RemoteConnectCommand)
|
||||
|
||||
registerCmdFn("history", HistoryCommand)
|
||||
}
|
||||
@@ -361,8 +369,124 @@ func UnSetCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func RemoteConnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ids.Remote.RState.IsConnected() {
|
||||
return sstore.InfoMsgUpdate("remote %q already connected (no action taken)", ids.Remote.DisplayName), nil
|
||||
}
|
||||
go ids.Remote.MShell.Launch()
|
||||
return sstore.InfoMsgUpdate("remote %q reconnecting", ids.Remote.DisplayName), nil
|
||||
}
|
||||
|
||||
func RemoteDisconnectCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
ids, err := resolveUiIds(ctx, pk, R_Session|R_Window|R_Remote)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
force := resolveBool(pk.Kwargs["force"], false)
|
||||
if !ids.Remote.RState.IsConnected() && !force {
|
||||
return sstore.InfoMsgUpdate("remote %q already disconnected (no action taken)", ids.Remote.DisplayName), nil
|
||||
}
|
||||
numCommands := ids.Remote.MShell.GetNumRunningCommands()
|
||||
if numCommands > 0 && !force {
|
||||
return nil, fmt.Errorf("remote not disconnected, %q has %d running commands. use 'force=1' to force disconnection", ids.Remote.DisplayName)
|
||||
}
|
||||
ids.Remote.MShell.Disconnect()
|
||||
return sstore.InfoMsgUpdate("remote %q disconnected", ids.Remote.DisplayName), nil
|
||||
}
|
||||
|
||||
func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
return nil, nil
|
||||
if len(pk.Args) == 0 || pk.Args[0] == "" {
|
||||
return nil, fmt.Errorf("/remote:new requires one positional argument of 'user@host'")
|
||||
}
|
||||
userHost := pk.Args[0]
|
||||
m := userHostRe.FindStringSubmatch(userHost)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("/remote:new invalid format of user@host argument")
|
||||
}
|
||||
sudoStr, remoteUser, remoteHost := m[1], m[2], m[3]
|
||||
alias := pk.Kwargs["alias"]
|
||||
if alias != "" {
|
||||
if len(alias) > MaxRemoteAliasLen {
|
||||
return nil, fmt.Errorf("alias too long, max length = %d", MaxRemoteAliasLen)
|
||||
}
|
||||
if !remoteAliasRe.MatchString(alias) {
|
||||
return nil, fmt.Errorf("invalid alias format")
|
||||
}
|
||||
}
|
||||
connectMode := sstore.ConnectModeStartup
|
||||
if pk.Kwargs["connectmode"] != "" {
|
||||
connectMode = pk.Kwargs["connectmode"]
|
||||
}
|
||||
if !sstore.IsValidConnectMode(connectMode) {
|
||||
return nil, fmt.Errorf("/remote:new invalid connectmode %q: valid modes are %s", connectMode, formatStrs([]string{sstore.ConnectModeStartup, sstore.ConnectModeAuto, sstore.ConnectModeManual}, "or", false))
|
||||
}
|
||||
var isSudo bool
|
||||
if sudoStr != "" {
|
||||
isSudo = true
|
||||
}
|
||||
if pk.Kwargs["sudo"] != "" {
|
||||
sudoArg := resolveBool(pk.Kwargs["sudo"], false)
|
||||
if isSudo && !sudoArg {
|
||||
return nil, fmt.Errorf("/remote:new invalid 'sudo@' argument, with sudo kw arg set to false")
|
||||
}
|
||||
if !isSudo && sudoArg {
|
||||
isSudo = true
|
||||
userHost = "sudo@" + userHost
|
||||
}
|
||||
}
|
||||
sshOpts := &sstore.SSHOpts{
|
||||
Local: false,
|
||||
SSHHost: remoteHost,
|
||||
SSHUser: remoteUser,
|
||||
}
|
||||
if pk.Kwargs["key"] != "" {
|
||||
keyFile := pk.Kwargs["key"]
|
||||
fd, err := os.Open(keyFile)
|
||||
if fd != nil {
|
||||
fd.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("/remote:new invalid key %q (cannot read): %v", keyFile, err)
|
||||
}
|
||||
sshOpts.SSHIdentity = keyFile
|
||||
}
|
||||
remoteOpts := &sstore.RemoteOptsType{}
|
||||
if pk.Kwargs["color"] != "" {
|
||||
color := pk.Kwargs["color"]
|
||||
err := validateRemoteColor(color, "remote color")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remoteOpts.Color = color
|
||||
}
|
||||
r := &sstore.RemoteType{
|
||||
RemoteId: uuid.New().String(),
|
||||
PhysicalId: "",
|
||||
RemoteType: sstore.RemoteTypeSsh,
|
||||
RemoteAlias: alias,
|
||||
RemoteCanonicalName: userHost,
|
||||
RemoteSudo: isSudo,
|
||||
RemoteUser: remoteUser,
|
||||
RemoteHost: remoteHost,
|
||||
ConnectMode: connectMode,
|
||||
SSHOpts: sshOpts,
|
||||
RemoteOpts: remoteOpts,
|
||||
}
|
||||
err := sstore.InsertRemote(ctx, r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err)
|
||||
}
|
||||
update := &sstore.ModelUpdate{
|
||||
Info: &sstore.InfoMsgType{
|
||||
InfoMsg: fmt.Sprintf("remote %q created", r.RemoteCanonicalName),
|
||||
TimeoutMs: 2000,
|
||||
},
|
||||
}
|
||||
return update, nil
|
||||
}
|
||||
|
||||
func RemoteShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
@@ -826,6 +950,15 @@ func validateColor(color string, typeStr string) error {
|
||||
return fmt.Errorf("invalid %s, valid colors are: %s", typeStr, formatStrs(ColorNames, "or", false))
|
||||
}
|
||||
|
||||
func validateRemoteColor(color string, typeStr string) error {
|
||||
for _, c := range RemoteColorNames {
|
||||
if color == c {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("invalid %s, valid colors are: %s", typeStr, formatStrs(RemoteColorNames, "or", false))
|
||||
}
|
||||
|
||||
func SessionOpenCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) {
|
||||
activate := resolveBool(pk.Kwargs["activate"], true)
|
||||
newName := pk.Kwargs["name"]
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
R_Screen = 2
|
||||
R_Window = 4
|
||||
R_Remote = 8
|
||||
R_RemoteConnected = 8 + 16
|
||||
R_RemoteConnected = 16
|
||||
)
|
||||
|
||||
type resolvedIds struct {
|
||||
@@ -128,7 +128,7 @@ func resolveUiIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype i
|
||||
if uictx.Remote != nil && rtn.SessionId != "" && rtn.WindowId != "" {
|
||||
rr, err := resolveRemoteFromPtr(ctx, uictx.Remote, rtn.SessionId, rtn.WindowId)
|
||||
if err != nil {
|
||||
if rtype&R_Remote > 0 {
|
||||
if rtype&R_Remote > 0 || rtype&R_RemoteConnected > 0 {
|
||||
return rtn, err
|
||||
}
|
||||
// otherwise just don't set uictx.Remote
|
||||
@@ -146,7 +146,7 @@ func resolveUiIds(ctx context.Context, pk *scpacket.FeCommandPacketType, rtype i
|
||||
if rtype&R_Window > 0 && rtn.WindowId == "" {
|
||||
return rtn, fmt.Errorf("no window")
|
||||
}
|
||||
if rtype&R_Remote > 0 && rtn.Remote == nil {
|
||||
if (rtype&R_Remote > 0 || rtype&R_RemoteConnected > 0) && rtn.Remote == nil {
|
||||
return rtn, fmt.Errorf("no remote")
|
||||
}
|
||||
if rtype&R_RemoteConnected > 0 {
|
||||
|
||||
+55
-20
@@ -55,6 +55,20 @@ type Store struct {
|
||||
Map map[string]*MShellProc // key=remoteid
|
||||
}
|
||||
|
||||
type MShellProc struct {
|
||||
Lock *sync.Mutex
|
||||
Remote *sstore.RemoteType
|
||||
|
||||
// runtime
|
||||
Status string
|
||||
ServerProc *shexec.ClientProc
|
||||
UName string
|
||||
Err error
|
||||
ControllingPty *os.File
|
||||
|
||||
RunningCmds []base.CommandKey
|
||||
}
|
||||
|
||||
type RemoteRuntimeState struct {
|
||||
RemoteType string `json:"remotetype"`
|
||||
RemoteId string `json:"remoteid"`
|
||||
@@ -93,20 +107,6 @@ func (state RemoteRuntimeState) GetDisplayName(rptr *sstore.RemotePtrType) strin
|
||||
return name
|
||||
}
|
||||
|
||||
type MShellProc struct {
|
||||
Lock *sync.Mutex
|
||||
Remote *sstore.RemoteType
|
||||
|
||||
// runtime
|
||||
Status string
|
||||
ServerProc *shexec.ClientProc
|
||||
UName string
|
||||
Err error
|
||||
ControllingPty *os.File
|
||||
|
||||
RunningCmds []base.CommandKey
|
||||
}
|
||||
|
||||
func LoadRemotes(ctx context.Context) error {
|
||||
GlobalStore = &Store{
|
||||
Lock: &sync.Mutex{},
|
||||
@@ -126,6 +126,28 @@ func LoadRemotes(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadRemoteById(ctx context.Context, remoteId string) error {
|
||||
r, err := sstore.GetRemoteById(ctx, remoteId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r == nil {
|
||||
return fmt.Errorf("remote %s not found", remoteId)
|
||||
}
|
||||
msh := MakeMShell(r)
|
||||
GlobalStore.Lock.Lock()
|
||||
defer GlobalStore.Lock.Unlock()
|
||||
existingRemote := GlobalStore.Map[remoteId]
|
||||
if existingRemote != nil {
|
||||
return fmt.Errorf("cannot add remote %d, already in global map", remoteId)
|
||||
}
|
||||
GlobalStore.Map[r.RemoteId] = msh
|
||||
if r.ConnectMode == sstore.ConnectModeStartup {
|
||||
go msh.Launch()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetRemoteByName(name string) *MShellProc {
|
||||
GlobalStore.Lock.Lock()
|
||||
defer GlobalStore.Lock.Unlock()
|
||||
@@ -353,9 +375,21 @@ func (msh *MShellProc) getRemoteCopy() sstore.RemoteType {
|
||||
return *msh.Remote
|
||||
}
|
||||
|
||||
func (msh *MShellProc) GetNumRunningCommands() int {
|
||||
msh.Lock.Lock()
|
||||
defer msh.Lock.Unlock()
|
||||
return len(msh.RunningCmds)
|
||||
}
|
||||
|
||||
func (msh *MShellProc) Disconnect() {
|
||||
msh.Lock.Lock()
|
||||
defer msh.Lock.Unlock()
|
||||
msh.ServerProc.Close()
|
||||
}
|
||||
|
||||
func (msh *MShellProc) Launch() {
|
||||
remote := msh.getRemoteCopy()
|
||||
ecmd := convertSSHOpts(remote.SSHOpts).MakeSSHExecCmd(MShellServerCommand)
|
||||
remoteCopy := msh.getRemoteCopy()
|
||||
ecmd := convertSSHOpts(remoteCopy.SSHOpts).MakeSSHExecCmd(MShellServerCommand)
|
||||
cmdPty, err := msh.addControllingTty(ecmd)
|
||||
if err != nil {
|
||||
msh.setErrorStatus(fmt.Errorf("cannot attach controlling tty to mshell command: %w", err))
|
||||
@@ -366,9 +400,9 @@ func (msh *MShellProc) Launch() {
|
||||
ecmd.ExtraFiles[len(ecmd.ExtraFiles)-1].Close()
|
||||
}
|
||||
}()
|
||||
remoteName := remote.GetName()
|
||||
remoteName := remoteCopy.GetName()
|
||||
go func() {
|
||||
fmt.Printf("[c-pty %s] starting...\n", remote.GetName())
|
||||
fmt.Printf("[c-pty %s] starting...\n", remoteCopy.GetName())
|
||||
buf := make([]byte, 100)
|
||||
for {
|
||||
n, readErr := cmdPty.Read(buf)
|
||||
@@ -398,10 +432,10 @@ func (msh *MShellProc) Launch() {
|
||||
})
|
||||
if err != nil {
|
||||
msh.setErrorStatus(err)
|
||||
fmt.Printf("[error] connecting remote %s (%s): %v\n", msh.Remote.GetName(), msh.UName, err)
|
||||
fmt.Printf("[error] connecting remote %s (%s): %v\n", remoteCopy.GetName(), msh.UName, err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("connected remote %s\n", msh.Remote.GetName())
|
||||
fmt.Printf("connected remote %s\n", remoteCopy.GetName())
|
||||
msh.WithLock(func() {
|
||||
msh.ServerProc = cproc
|
||||
msh.Status = StatusConnected
|
||||
@@ -643,6 +677,7 @@ func (runner *MShellProc) ProcessPackets() {
|
||||
fmt.Printf("[error] calling HUP on remoteid=%d cmds\n", runner.Remote.RemoteId)
|
||||
}
|
||||
runner.notifyHangups_nolock()
|
||||
go runner.NotifyUpdate()
|
||||
})
|
||||
dataPosMap := make(map[base.CommandKey]int64)
|
||||
for pk := range runner.ServerProc.Output.MainCh {
|
||||
|
||||
+29
-9
@@ -89,17 +89,37 @@ func InsertRemote(ctx context.Context, remote *RemoteType) error {
|
||||
if remote == nil {
|
||||
return fmt.Errorf("cannot insert nil remote")
|
||||
}
|
||||
db, err := GetDB(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
if remote.RemoteId == "" {
|
||||
return fmt.Errorf("cannot insert remote without id")
|
||||
}
|
||||
query := `INSERT INTO remote ( remoteid, physicalid, remotetype, remotealias, remotecanonicalname, remotesudo, remoteuser, remotehost, connectmode, initpk, sshopts, remoteopts, lastconnectts) VALUES
|
||||
(:remoteid,:physicalid,:remotetype,:remotealias,:remotecanonicalname,:remotesudo,:remoteuser,:remotehost,:connectmode,:initpk,:sshopts,:remoteopts,:lastconnectts)`
|
||||
_, err = db.NamedExec(query, remote.ToMap())
|
||||
if err != nil {
|
||||
return err
|
||||
if remote.RemoteCanonicalName == "" {
|
||||
return fmt.Errorf("cannot insert remote with canonicalname")
|
||||
}
|
||||
return nil
|
||||
if remote.RemoteType == "" {
|
||||
return fmt.Errorf("cannot insert remote without type")
|
||||
}
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT remoteid FROM remote WHERE remoteid = ?`
|
||||
if tx.Exists(query, remote.RemoteId) {
|
||||
return fmt.Errorf("duplicate remoteid, cannot create")
|
||||
}
|
||||
if remote.RemoteAlias != "" {
|
||||
query = `SELECT remoteid FROM remote WHERE alias = ?`
|
||||
if tx.Exists(query, remote.RemoteAlias) {
|
||||
return fmt.Errorf("remote has duplicate alias '%s', cannot create", remote.RemoteAlias)
|
||||
}
|
||||
}
|
||||
query = `SELECT remoteid FROM remote WHERE remotecanonicaname = ?`
|
||||
if tx.Exists(query, remote.RemoteCanonicalName) {
|
||||
return fmt.Errorf("remote has duplicate canonicalname '%s', cannot create", remote.RemoteCanonicalName)
|
||||
}
|
||||
query = `INSERT INTO remote
|
||||
( remoteid, physicalid, remotetype, remotealias, remotecanonicalname, remotesudo, remoteuser, remotehost, connectmode, initpk, sshopts, remoteopts, lastconnectts) VALUES
|
||||
(:remoteid,:physicalid,:remotetype,:remotealias,:remotecanonicalname,:remotesudo,:remoteuser,:remotehost,:connectmode,:initpk,:sshopts,:remoteopts,:lastconnectts)`
|
||||
tx.NamedExecWrap(query, remote.ToMap())
|
||||
return nil
|
||||
})
|
||||
return txErr
|
||||
}
|
||||
|
||||
func InsertHistoryItem(ctx context.Context, hitem *HistoryItemType) error {
|
||||
|
||||
@@ -70,6 +70,10 @@ func GetSessionDBName() string {
|
||||
return path.Join(scHome, DBFileName)
|
||||
}
|
||||
|
||||
func IsValidConnectMode(mode string) bool {
|
||||
return mode == ConnectModeStartup || mode == ConnectModeAuto || mode == ConnectModeManual
|
||||
}
|
||||
|
||||
func GetDB(ctx context.Context) (*sqlx.DB, error) {
|
||||
if IsTxWrapContext(ctx) {
|
||||
return nil, fmt.Errorf("cannot call GetDB from within a running transaction")
|
||||
|
||||
+11
-1
@@ -1,6 +1,9 @@
|
||||
package sstore
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var MainBus *UpdateBus = MakeUpdateBus()
|
||||
|
||||
@@ -65,6 +68,13 @@ func ReadHistoryDataFromUpdate(update UpdatePacket) (string, string, *RemotePtrT
|
||||
return modelUpdate.Line.LineId, modelUpdate.Line.CmdId, rptr
|
||||
}
|
||||
|
||||
func InfoMsgUpdate(infoMsgFmt string, args ...interface{}) *ModelUpdate {
|
||||
msg := fmt.Sprintf(infoMsgFmt, args...)
|
||||
return &ModelUpdate{
|
||||
Info: &InfoMsgType{InfoMsg: msg},
|
||||
}
|
||||
}
|
||||
|
||||
type InfoMsgType struct {
|
||||
InfoTitle string `json:"infotitle"`
|
||||
InfoError string `json:"infoerror,omitempty"`
|
||||
|
||||
Reference in New Issue
Block a user