2023-08-26 11:27:38 +01:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"os"
|
2023-08-28 11:35:57 +02:00
|
|
|
"runtime"
|
2023-08-26 16:43:04 +01:00
|
|
|
"strings"
|
2023-08-26 11:27:38 +01:00
|
|
|
|
2023-08-31 16:23:40 +01:00
|
|
|
"github.com/AlexSSD7/linsk/share"
|
2023-08-26 11:27:38 +01:00
|
|
|
"github.com/AlexSSD7/linsk/vm"
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
|
"golang.org/x/term"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var shellCmd = &cobra.Command{
|
2023-08-29 10:37:52 +01:00
|
|
|
Use: "shell",
|
|
|
|
|
Short: "Start a VM and access the shell. Useful for formatting drives and debugging.",
|
|
|
|
|
Args: cobra.RangeArgs(0, 1),
|
2023-08-27 15:30:51 +01:00
|
|
|
Run: func(cmd *cobra.Command, args []string) {
|
2023-08-26 11:27:38 +01:00
|
|
|
var passthroughArg string
|
|
|
|
|
if len(args) > 0 {
|
|
|
|
|
passthroughArg = args[0]
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 13:44:57 +01:00
|
|
|
var forwardPortRules []vm.PortForwardingRule
|
2023-08-26 16:43:04 +01:00
|
|
|
|
|
|
|
|
for i, fp := range strings.Split(forwardPortsFlagStr, ",") {
|
|
|
|
|
if fp == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 13:44:57 +01:00
|
|
|
fpr, err := vm.ParsePortForwardString(fp)
|
2023-08-26 16:43:04 +01:00
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to parse port forward string", "index", i, "value", fp, "error", err.Error())
|
2023-08-26 16:43:04 +01:00
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-27 13:44:57 +01:00
|
|
|
forwardPortRules = append(forwardPortRules, fpr)
|
2023-08-26 16:43:04 +01:00
|
|
|
}
|
|
|
|
|
|
2023-08-31 16:23:40 +01:00
|
|
|
os.Exit(runVM(passthroughArg, func(ctx context.Context, i *vm.VM, fm *vm.FileManager, trc *share.NetTapRuntimeContext) int {
|
2023-08-26 11:27:38 +01:00
|
|
|
sc, err := i.DialSSH()
|
|
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to dial VM SSH", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() { _ = sc.Close() }()
|
|
|
|
|
|
|
|
|
|
sess, err := sc.NewSession()
|
|
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to create new VM SSH session", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() { _ = sess.Close() }()
|
|
|
|
|
|
|
|
|
|
termFD := int(os.Stdin.Fd())
|
|
|
|
|
termState, err := term.MakeRaw(termFD)
|
|
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to make raw terminal", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
|
err := term.Restore(termFD, termState)
|
|
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to restore terminal", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2023-08-28 11:35:57 +02:00
|
|
|
termFDGetSize := termFD
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
|
// Another Windows workaround :/
|
|
|
|
|
termFDGetSize = int(os.Stdout.Fd())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
termWidth, termHeight, err := term.GetSize(termFDGetSize)
|
2023-08-26 11:27:38 +01:00
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to get terminal size", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
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 {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to request VM SSH pty", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sess.Stdin = os.Stdin
|
|
|
|
|
sess.Stdout = os.Stdout
|
|
|
|
|
sess.Stderr = os.Stderr
|
|
|
|
|
|
|
|
|
|
err = sess.Shell()
|
|
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Start VM SSH shell", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
doneCh := make(chan struct{}, 1)
|
|
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
err = sess.Wait()
|
|
|
|
|
if err != nil {
|
2023-08-29 10:59:50 +01:00
|
|
|
slog.Error("Failed to wait for VM SSH session to finish", "error", err.Error())
|
2023-08-26 11:27:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
doneCh <- struct{}{}
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
case <-doneCh:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0
|
2023-08-31 16:23:40 +01:00
|
|
|
}, forwardPortRules, true, false))
|
|
|
|
|
// TODO: Enable tap option.
|
2023-08-26 11:27:38 +01:00
|
|
|
},
|
|
|
|
|
}
|
2023-08-26 11:57:12 +01:00
|
|
|
|
|
|
|
|
var forwardPortsFlagStr string
|
|
|
|
|
|
|
|
|
|
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.")
|
|
|
|
|
}
|