Implement debug shell in run cmd

This commit is contained in:
AlexSSD7 2023-09-01 14:40:17 +01:00
commit 2d832c2b63
2 changed files with 104 additions and 82 deletions

View file

@ -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 {

View file

@ -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: '<HOST PORT>:<VM PORT>' OR '<HOST BIND IP>:<HOST PORT>:<VM PORT>'. 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
}