diff --git a/cmd/imgbuilder/builder/build.go b/cmd/imgbuilder/builder/build.go index 2e19dee..71798e2 100644 --- a/cmd/imgbuilder/builder/build.go +++ b/cmd/imgbuilder/builder/build.go @@ -88,7 +88,7 @@ func (bc *BuildContext) BuildWithInterruptHandler() error { defer func() { err := bc.vi.Cancel() if err != nil { - bc.logger.Error("Failed to cancel VM context", "error", err) + bc.logger.Error("Failed to cancel VM context", "error", err.Error()) } }() @@ -129,7 +129,7 @@ func (bc *BuildContext) BuildWithInterruptHandler() error { err := bc.vi.Cancel() if err != nil { - lg.Warn("Failed to cancel VM context", "error", err) + lg.Warn("Failed to cancel VM context", "error", err.Error()) } } } diff --git a/cmd/imgbuilder/main.go b/cmd/imgbuilder/main.go index 27a8c22..5000890 100644 --- a/cmd/imgbuilder/main.go +++ b/cmd/imgbuilder/main.go @@ -19,13 +19,13 @@ var rootCmd = &cobra.Command{ bc, err := builder.NewBuildContext(slog.With("caller", "build-context"), baseISOPath, outImagePath, vmDebugFlag) if err != nil { - slog.Error("Failed to create a new build context", "error", err) + slog.Error("Failed to create a new build context", "error", err.Error()) os.Exit(1) } err = bc.BuildWithInterruptHandler() if err != nil { - slog.Error("Failed to build an image", "error", err) + slog.Error("Failed to build an image", "error", err.Error()) os.Exit(1) } diff --git a/cmd/ls.go b/cmd/ls.go index 22728ed..3c8aa38 100644 --- a/cmd/ls.go +++ b/cmd/ls.go @@ -20,11 +20,16 @@ var lsCmd = &cobra.Command{ os.Exit(runVM(args[0], func(ctx context.Context, i *vm.VM, fm *vm.FileManager) int { lsblkOut, err := fm.Lsblk() if err != nil { - slog.Error("Failed to list block devices in the VM", "error", err) + slog.Error("Failed to list block devices in the VM", "error", err.Error()) return 1 } - fmt.Print(string(lsblkOut)) + if len(lsblkOut) == 0 { + fmt.Printf("\n") + } else { + fmt.Print(string(lsblkOut)) + } + return 0 }, nil, false)) }, diff --git a/cmd/root.go b/cmd/root.go index b3783a4..479c28a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,6 +26,9 @@ func Execute() { var vmDebugFlag bool var unrestrictedNetworkingFlag bool +var vmMemAllocFlag uint64 + +// TODO: Version command. func init() { slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil))) @@ -34,6 +37,7 @@ func init() { rootCmd.AddCommand(runCmd) rootCmd.AddCommand(shellCmd) - rootCmd.PersistentFlags().BoolVar(&vmDebugFlag, "vmdebug", false, "Enable VM debug mode. This will open an accessible VM monitor. You can log in with root user and no password.") - rootCmd.PersistentFlags().BoolVar(&unrestrictedNetworkingFlag, "unrestricted-networking", false, "Enable unrestricted networking. This will allow the VM to connect to the internet.") + rootCmd.PersistentFlags().BoolVar(&vmDebugFlag, "vmdebug", false, "Enables the VM debug mode. This will open an accessible VM monitor. You can log in with root user and no password.") + rootCmd.PersistentFlags().BoolVar(&unrestrictedNetworkingFlag, "unrestricted-networking", false, "Enables unrestricted networking. This will allow the VM to connect to the internet.") + rootCmd.PersistentFlags().Uint64Var(&vmMemAllocFlag, "vm-mem-alloc", 512, "Specifies the VM memory allocation in KiB") } diff --git a/cmd/run.go b/cmd/run.go index 2c2a7ad..d6dfc51 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -24,7 +24,7 @@ var runCmd = &cobra.Command{ networkSharePort, err := getClosestAvailPortWithSubsequent(9000, 10) if err != nil { - slog.Error("Failed to get closest available host port for network file share", "error", err) + slog.Error("Failed to get closest available host port for network file share", "error", err.Error()) os.Exit(1) } @@ -46,18 +46,20 @@ var runCmd = &cobra.Command{ // TODO: `slog` library prints entire stack traces for errors which makes reading errors challenging. os.Exit(runVM(args[0], func(ctx context.Context, i *vm.VM, fm *vm.FileManager) int { + slog.Info("Mounting the device", "dev", vmMountDevName, "fs", fsType, "luks", luksFlag) + err := fm.Mount(vmMountDevName, vm.MountOptions{ FSType: fsType, LUKS: luksFlag, }) if err != nil { - slog.Error("Failed to mount the disk inside the VM", "error", err) + slog.Error("Failed to mount the disk inside the VM", "error", err.Error()) return 1 } sharePWD, err := password.Generate(16, 10, 0, false, false) if err != nil { - slog.Error("Failed to generate ephemeral password for network file share", "error", err) + slog.Error("Failed to generate ephemeral password for network file share", "error", err.Error()) return 1 } @@ -67,7 +69,7 @@ var runCmd = &cobra.Command{ err = fm.StartFTP([]byte(sharePWD), networkSharePort+1, ftpPassivePortCount) if err != nil { - slog.Error("Failed to start FTP server", "error", err) + slog.Error("Failed to start FTP server", "error", err.Error()) return 1 } diff --git a/cmd/shell.go b/cmd/shell.go index 5ff054e..214d432 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -32,7 +32,7 @@ var shellCmd = &cobra.Command{ fpr, err := vm.ParsePortForwardString(fp) if err != nil { - slog.Error("Failed to parse port forward string", "index", i, "value", fp, "error", err) + slog.Error("Failed to parse port forward string", "index", i, "value", fp, "error", err.Error()) os.Exit(1) } @@ -42,7 +42,7 @@ var shellCmd = &cobra.Command{ os.Exit(runVM(passthroughArg, func(ctx context.Context, i *vm.VM, fm *vm.FileManager) int { sc, err := i.DialSSH() if err != nil { - slog.Error("Failed to dial VM SSH", "error", err) + slog.Error("Failed to dial VM SSH", "error", err.Error()) return 1 } @@ -50,7 +50,7 @@ var shellCmd = &cobra.Command{ sess, err := sc.NewSession() if err != nil { - slog.Error("Failed to create new VM SSH session", "error", err) + slog.Error("Failed to create new VM SSH session", "error", err.Error()) return 1 } @@ -59,14 +59,14 @@ var shellCmd = &cobra.Command{ termFD := int(os.Stdin.Fd()) termState, err := term.MakeRaw(termFD) if err != nil { - slog.Error("Failed to make raw terminal", "error", err) + 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) + slog.Error("Failed to restore terminal", "error", err.Error()) } }() @@ -78,7 +78,7 @@ var shellCmd = &cobra.Command{ termWidth, termHeight, err := term.GetSize(termFDGetSize) if err != nil { - slog.Error("Failed to get terminal size", "error", err) + slog.Error("Failed to get terminal size", "error", err.Error()) return 1 } @@ -95,7 +95,7 @@ var shellCmd = &cobra.Command{ err = sess.RequestPty(term, termHeight, termWidth, termModes) if err != nil { - slog.Error("Failed to request VM SSH pty", "error", err) + slog.Error("Failed to request VM SSH pty", "error", err.Error()) return 1 } @@ -105,7 +105,7 @@ var shellCmd = &cobra.Command{ err = sess.Shell() if err != nil { - slog.Error("Start VM SSH shell", "error", err) + slog.Error("Start VM SSH shell", "error", err.Error()) return 1 } @@ -114,7 +114,7 @@ var shellCmd = &cobra.Command{ go func() { err = sess.Wait() if err != nil { - slog.Error("Failed to wait for VM SSH session to finish", "error", err) + slog.Error("Failed to wait for VM SSH session to finish", "error", err.Error()) } doneCh <- struct{}{} diff --git a/cmd/utils.go b/cmd/utils.go index 91209f5..39d1203 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -40,7 +40,7 @@ func doUSBRootCheck() { ok, err := checkIfRoot() if err != nil { - slog.Error("Failed to check whether the command is ran by root", "error", err) + slog.Error("Failed to check whether the command is ran by root", "error", err.Error()) os.Exit(1) } @@ -64,6 +64,8 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag SnapshotMode: true, }}, + MemoryAlloc: vmMemAllocFlag, + USBDevices: passthroughConfig, ExtraPortForwardingRules: forwardPortsRules, @@ -74,7 +76,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag // TODO: Alpine image should be downloaded from somewhere. vi, err := vm.NewVM(slog.Default().With("caller", "vm"), vmCfg) if err != nil { - slog.Error("Failed to create vm instance", "error", err) + slog.Error("Failed to create vm instance", "error", err.Error()) os.Exit(1) } @@ -115,7 +117,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag err := vi.Cancel() if err != nil { - lg.Warn("Failed to cancel VM context", "error", err) + lg.Warn("Failed to cancel VM context", "error", err.Error()) } } } @@ -130,12 +132,12 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag err = fmt.Errorf("operation canceled by user") } - slog.Error("Failed to start the VM", "error", err) + slog.Error("Failed to start the VM", "error", err.Error()) os.Exit(1) case <-vi.SSHUpNotifyChan(): err := fm.Init() if err != nil { - slog.Error("Failed to initialize File Manager", "error", err) + slog.Error("Failed to initialize File Manager", "error", err.Error()) os.Exit(1) } @@ -143,7 +145,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag err = vi.Cancel() if err != nil { - slog.Error("Failed to cancel VM context", "error", err) + slog.Error("Failed to cancel VM context", "error", err.Error()) os.Exit(1) } @@ -152,7 +154,7 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag select { case err := <-runErrCh: if err != nil { - slog.Error("Failed to run the VM", "error", err) + slog.Error("Failed to run the VM", "error", err.Error()) os.Exit(1) } default: diff --git a/vm/filemanager.go b/vm/filemanager.go index f992928..75bda8b 100644 --- a/vm/filemanager.go +++ b/vm/filemanager.go @@ -40,11 +40,6 @@ func (fm *FileManager) Init() error { defer func() { _ = sc.Close() }() - _, err = runSSHCmd(sc, "apk add util-linux lvm2") - if err != nil { - return errors.Wrap(err, "install utilities") - } - _, err = runSSHCmd(sc, "vgchange -ay") if err != nil { return errors.Wrap(err, "run vgchange cmd") @@ -110,7 +105,7 @@ func (fm *FileManager) luksOpen(sc *ssh.Client, fullDevPath string) error { return errors.Wrap(err, "start cryptsetup luksopen cmd") } - lg.Info("Attempting to open LUKS device") + lg.Info("Attempting to open a LUKS device") _, err = os.Stderr.Write([]byte("Enter Password: ")) if err != nil { @@ -140,6 +135,10 @@ func (fm *FileManager) luksOpen(sc *ssh.Client, fullDevPath string) error { err = sess.Wait() if err != nil { + if strings.Contains(stderrBuf.String(), "Not enough available memory to open a keyslot.") { + fm.logger.Warn("Detected not enough memory to open a LUKS device, please allocate more memory using --vm-mem-alloc flag.") + } + return utils.WrapErrWithLog(err, "wait for cryptsetup luksopen cmd to finish", stderrBuf.String()) } @@ -268,11 +267,11 @@ pasv_address=127.0.0.1 go func() { _, err = stdinPipe.Write(pwd) if err != nil { - fm.vm.logger.Error("Failed to write FTP password to passwd stdin", "error", err) + fm.vm.logger.Error("Failed to write FTP password to passwd stdin", "error", err.Error()) } _, err = stdinPipe.Write(pwd) if err != nil { - fm.vm.logger.Error("Failed to write repeated FTP password to passwd stdin", "error", err) + fm.vm.logger.Error("Failed to write repeated FTP password to passwd stdin", "error", err.Error()) } }() diff --git a/vm/vm.go b/vm/vm.go index 0b67d45..4e2e0c2 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -61,6 +61,8 @@ type VMConfig struct { CdromImagePath string Drives []DriveConfig + MemoryAlloc uint64 // In KiB. + USBDevices []USBDevicePassthroughConfig ExtraPortForwardingRules []PortForwardingRule @@ -84,7 +86,7 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) { // TODO: Configurable memory allocation - cmdArgs := []string{"-serial", "stdio", "-m", "2048", "-smp", fmt.Sprint(runtime.NumCPU())} + cmdArgs := []string{"-serial", "stdio", "-m", fmt.Sprint(cfg.MemoryAlloc), "-smp", fmt.Sprint(runtime.NumCPU())} baseCmd := "qemu-system" @@ -319,14 +321,14 @@ func (vm *VM) Cancel() error { sc, err := vm.DialSSH() if err != nil { if !errors.Is(err, ErrSSHUnavailable) { - vm.logger.Warn("Failed to dial VM SSH to do graceful shutdown", "error", err) + vm.logger.Warn("Failed to dial VM SSH to do graceful shutdown", "error", err.Error()) } } else { vm.logger.Warn("Sending poweroff command to the VM") _, err = runSSHCmd(sc, "poweroff") _ = sc.Close() if err != nil { - vm.logger.Warn("Could not power off the VM safely", "error", err) + vm.logger.Warn("Could not power off the VM safely", "error", err.Error()) } else { vm.logger.Info("Shutting the VM down safely") }