From 2d832c2b63d4be4da0badaa37a68a008aa30d716 Mon Sep 17 00:00:00 2001 From: AlexSSD7 Date: Fri, 1 Sep 2023 14:40:17 +0100 Subject: [PATCH] Implement debug shell in run cmd --- cmd/run.go | 19 +++++- cmd/shell.go | 167 ++++++++++++++++++++++++++------------------------- 2 files changed, 104 insertions(+), 82 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 747cac8..e1a0d66 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -86,7 +86,22 @@ var runCmd = &cobra.Command{ fmt.Fprintf(os.Stderr, "===========================\n[Network File Share Config]\nThe network file share was started. Please use the credentials below to connect to the file server.\n\nType: "+strings.ToUpper(shareBackendFlag)+"\nURL: %v\nUsername: linsk\nPassword: %v\n===========================\n", shareURI, sharePWD) - <-ctx.Done() + ctxWait := true + + if debugShellFlag { + slog.Warn("Starting a debug VM shell") + err := runVMShell(ctx, i) + if err != nil { + slog.Error("Failed to run VM shell", "error", err.Error()) + } else { + ctxWait = false + } + } + + if ctxWait { + <-ctx.Done() + } + return 0 }, vmOpts.Ports, unrestrictedNetworkingFlag, vmOpts.EnableTap)) }, @@ -99,11 +114,13 @@ var ( ftpExtIPFlag string shareBackendFlag string smbUseExternAddrFlag bool + debugShellFlag bool ) func init() { runCmd.Flags().BoolVarP(&luksFlag, "luks", "l", false, "Use cryptsetup to open a LUKS volume (password will be prompted).") runCmd.Flags().BoolVar(&allowLUKSLowMemoryFlag, "allow-luks-low-memory", false, "Allow VM memory allocation lower than 2048 MiB when LUKS is enabled.") + runCmd.Flags().BoolVar(&debugShellFlag, "debug-shell", false, "Start a VM shell when the network file share is active.") var defaultShareType string switch runtime.GOOS { diff --git a/cmd/shell.go b/cmd/shell.go index c3dcdff..8934786 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -9,6 +9,7 @@ import ( "github.com/AlexSSD7/linsk/share" "github.com/AlexSSD7/linsk/vm" + "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" "golang.org/x/term" @@ -41,95 +42,16 @@ var shellCmd = &cobra.Command{ } os.Exit(runVM(passthroughArg, func(ctx context.Context, i *vm.VM, fm *vm.FileManager, trc *share.NetTapRuntimeContext) int { - sc, err := i.DialSSH() - if err != nil { - slog.Error("Failed to dial VM SSH", "error", err.Error()) - return 1 - } - if trc != nil { slog.Info("Tap networking is active", "host-ip", trc.Net.HostIP, "vm-ip", trc.Net.GuestIP) } - defer func() { _ = sc.Close() }() - - sess, err := sc.NewSession() + err := runVMShell(ctx, i) if err != nil { - slog.Error("Failed to create new VM SSH session", "error", err.Error()) + slog.Error("Failed to run VM shell", "error", err.Error()) return 1 } - defer func() { _ = sess.Close() }() - - termFD := int(os.Stdin.Fd()) - termState, err := term.MakeRaw(termFD) - if err != nil { - slog.Error("Failed to make raw terminal", "error", err.Error()) - return 1 - } - - defer func() { - err := term.Restore(termFD, termState) - if err != nil { - slog.Error("Failed to restore terminal", "error", err.Error()) - } - }() - - termFDGetSize := termFD - if runtime.GOOS == "windows" { - // Another Windows workaround :/ - termFDGetSize = int(os.Stdout.Fd()) - } - - termWidth, termHeight, err := term.GetSize(termFDGetSize) - if err != nil { - slog.Error("Failed to get terminal size", "error", err.Error()) - return 1 - } - - termModes := ssh.TerminalModes{ - ssh.ECHO: 1, - ssh.TTY_OP_ISPEED: 14400, - ssh.TTY_OP_OSPEED: 14400, - } - - term := os.Getenv("TERM") - if term == "" { - term = "xterm-256color" - } - - err = sess.RequestPty(term, termHeight, termWidth, termModes) - if err != nil { - slog.Error("Failed to request VM SSH pty", "error", err.Error()) - return 1 - } - - sess.Stdin = os.Stdin - sess.Stdout = os.Stdout - sess.Stderr = os.Stderr - - err = sess.Shell() - if err != nil { - slog.Error("Start VM SSH shell", "error", err.Error()) - return 1 - } - - doneCh := make(chan struct{}, 1) - - go func() { - err = sess.Wait() - if err != nil { - slog.Error("Failed to wait for VM SSH session to finish", "error", err.Error()) - } - - doneCh <- struct{}{} - }() - - select { - case <-ctx.Done(): - case <-doneCh: - } - return 0 }, forwardPortRules, true, enableTapNetFlag)) }, @@ -142,3 +64,86 @@ func init() { shellCmd.Flags().StringVar(&forwardPortsFlagStr, "forward-ports", "", "Extra TCP port forwarding rules. Syntax: ':' OR '::'. Multiple rules split by comma are accepted.") shellCmd.Flags().BoolVar(&enableTapNetFlag, "enable-net-tap", false, "Enables host-VM tap networking.") } + +func runVMShell(ctx context.Context, vi *vm.VM) error { + sc, err := vi.DialSSH() + if err != nil { + return errors.Wrap(err, "dial ssh") + } + + defer func() { _ = sc.Close() }() + + sess, err := sc.NewSession() + if err != nil { + return errors.Wrap(err, "new vm ssh session") + } + + defer func() { _ = sess.Close() }() + + termFD := int(os.Stdin.Fd()) + termState, err := term.MakeRaw(termFD) + if err != nil { + return errors.Wrap(err, "make raw terminal") + } + + defer func() { + err := term.Restore(termFD, termState) + if err != nil { + slog.Error("Failed to restore terminal", "error", err.Error()) + } + }() + + termFDGetSize := termFD + if runtime.GOOS == "windows" { + // Workaround for Windows. + termFDGetSize = int(os.Stdout.Fd()) + } + + termWidth, termHeight, err := term.GetSize(termFDGetSize) + if err != nil { + return errors.Wrap(err, "get terminal size") + } + + termModes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + + term := os.Getenv("TERM") + if term == "" { + term = "xterm-256color" + } + + err = sess.RequestPty(term, termHeight, termWidth, termModes) + if err != nil { + return errors.Wrap(err, "request vm ssh pty") + } + + sess.Stdin = os.Stdin + sess.Stdout = os.Stdout + sess.Stderr = os.Stderr + + err = sess.Shell() + if err != nil { + return errors.Wrap(err, "start vm ssh shell") + } + + doneCh := make(chan struct{}, 1) + + go func() { + err = sess.Wait() + if err != nil { + slog.Error("Failed to wait for VM SSH session to finish", "error", err.Error()) + } + + doneCh <- struct{}{} + }() + + select { + case <-ctx.Done(): + case <-doneCh: + } + + return nil +}