diff --git a/cmd/imgbuilder/builder/build.go b/cmd/imgbuilder/builder/build.go index 3c659d3..b4f36aa 100644 --- a/cmd/imgbuilder/builder/build.go +++ b/cmd/imgbuilder/builder/build.go @@ -1,6 +1,7 @@ package builder import ( + "bytes" "context" "fmt" "os" @@ -13,6 +14,7 @@ import ( "log/slog" + "github.com/AlexSSD7/linsk/utils" "github.com/AlexSSD7/linsk/vm" "github.com/alessio/shellescape" "github.com/pkg/errors" @@ -176,6 +178,9 @@ func runAlpineSetupCmd(sc *ssh.Client, pkgs []string) error { // TODO: Timeout for this command. + stderr := bytes.NewBuffer(nil) + sess.Stderr = stderr + defer func() { _ = sess.Close() }() @@ -191,9 +196,11 @@ func runAlpineSetupCmd(sc *ssh.Client, pkgs []string) error { cmd += " && mount /dev/vda3 /mnt && chroot /mnt apk add " + strings.Join(pkgsQuoted, " ") } + cmd += `&& chroot /mnt ash -c 'echo "PasswordAuthentication no" >> /etc/ssh/sshd_config && addgroup -g 1000 linsk && adduser -G linsk linsk -S -u 1000'` + err = sess.Run(cmd) if err != nil { - return errors.Wrap(err, "run setup cmd") + return utils.WrapErrWithLog(err, "run setup cmd", stderr.String()) } return nil diff --git a/cmd/utils.go b/cmd/utils.go index 10b740c..2d5649e 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -47,7 +47,10 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag } vmCfg := vm.VMConfig{ - CdromImagePath: "alpine-img/alpine.qcow2", + Drives: []vm.DriveConfig{{ + Path: "alpine.qcow2", + SnapshotMode: true, + }}, USBDevices: passthroughConfig, ExtraPortForwardingRules: forwardPortsRules, @@ -111,6 +114,10 @@ func runVM(passthroughArg string, fn func(context.Context, *vm.VM, *vm.FileManag for { select { case err := <-runErrCh: + if err == nil { + err = fmt.Errorf("operation canceled by user") + } + slog.Error("Failed to start the VM", "error", err) os.Exit(1) case <-vi.SSHUpNotifyChan(): diff --git a/utils/errors.go b/utils/errors.go new file mode 100644 index 0000000..6e82ddf --- /dev/null +++ b/utils/errors.go @@ -0,0 +1,26 @@ +package utils + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" +) + +func WrapErrWithLog(err error, msg, log string) error { + return errors.Wrapf(err, "%v %v", msg, GetLogErrMsg(log)) +} + +func GetLogErrMsg(s string) string { + logToInclude := strings.ReplaceAll(s, "\n", "\\n") + logToInclude = strings.TrimSuffix(logToInclude, "\\n") + logToInclude = ClearUnprintableChars(logToInclude, false) + + origLogLen := len(logToInclude) + const maxLogLen = 256 + if origLogLen > maxLogLen { + logToInclude = fmt.Sprintf("[%v chars trimmed]", origLogLen) + logToInclude[len(logToInclude)-maxLogLen:] + } + + return fmt.Sprintf("(log: '%v')", logToInclude) +} diff --git a/vm/errors.go b/vm/errors.go index 0174084..4863db0 100644 --- a/vm/errors.go +++ b/vm/errors.go @@ -1,31 +1,9 @@ package vm import ( - "fmt" - "strings" - - "github.com/AlexSSD7/linsk/utils" "github.com/pkg/errors" ) var ( ErrSSHUnavailable = errors.New("ssh unavailable") ) - -func wrapErrWithLog(err error, msg, log string) error { - return errors.Wrapf(err, "%v %v", msg, getLogErrMsg(log)) -} - -func getLogErrMsg(s string) string { - logToInclude := strings.ReplaceAll(s, "\n", "\\n") - logToInclude = strings.TrimSuffix(logToInclude, "\\n") - logToInclude = utils.ClearUnprintableChars(logToInclude, false) - - origLogLen := len(logToInclude) - const maxLogLen = 256 - if origLogLen > maxLogLen { - logToInclude = fmt.Sprintf("[%v chars trimmed]", origLogLen) + logToInclude[len(logToInclude)-maxLogLen:] - } - - return fmt.Sprintf("(log: '%v')", logToInclude) -} diff --git a/vm/filemanager.go b/vm/filemanager.go index 00e0453..03335ab 100644 --- a/vm/filemanager.go +++ b/vm/filemanager.go @@ -141,7 +141,7 @@ func (fm *FileManager) luksOpen(sc *ssh.Client, fullDevPath string) error { err = sess.Wait() if err != nil { - return wrapErrWithLog(err, "wait for cryptsetup luksopen cmd to finish", stderrBuf.String()) + return utils.WrapErrWithLog(err, "wait for cryptsetup luksopen cmd to finish", stderrBuf.String()) } lg.Info("LUKS device opened successfully") @@ -279,7 +279,7 @@ create mask = 0664` err = sess.Wait() if err != nil { - return wrapErrWithLog(err, "wait for change samba password cmd", stderr.String()) + return utils.WrapErrWithLog(err, "wait for change samba password cmd", stderr.String()) } return nil diff --git a/vm/ssh.go b/vm/ssh.go index 202d5a4..ab39269 100644 --- a/vm/ssh.go +++ b/vm/ssh.go @@ -121,7 +121,7 @@ func (vm *VM) sshSetup() (ssh.Signer, error) { case <-vm.ctx.Done(): return nil, vm.ctx.Err() case <-time.After(time.Until(deadline)): - return nil, fmt.Errorf("setup command timed out %v", getLogErrMsg(stdOutErrBuf.String())) + return nil, fmt.Errorf("setup command timed out %v", utils.GetLogErrMsg(stdOutErrBuf.String())) case data := <-vm.serialStdoutCh: prefix := []byte("SERIAL STATUS: ") stdOutErrBuf.WriteString(utils.ClearUnprintableChars(string(data), true)) @@ -132,7 +132,7 @@ func (vm *VM) sshSetup() (ssh.Signer, error) { if data[len(prefix)] != '0' { fmt.Fprintf(os.Stderr, "SSH SETUP FAILURE:\n%v", stdOutErrBuf.String()) - return nil, fmt.Errorf("non-zero setup command status code: '%v' %v", string(data[len(prefix)]), getLogErrMsg(stdOutErrBuf.String())) + return nil, fmt.Errorf("non-zero setup command status code: '%v' %v", string(data[len(prefix)]), utils.GetLogErrMsg(stdOutErrBuf.String())) } return sshSigner, nil @@ -173,7 +173,7 @@ func runSSHCmd(c *ssh.Client, cmd string) ([]byte, error) { err = sess.Run(cmd) if err != nil { - return nil, wrapErrWithLog(err, "run cmd", stderr.String()) + return nil, utils.WrapErrWithLog(err, "run cmd", stderr.String()) } return stdout.Bytes(), nil diff --git a/vm/vm.go b/vm/vm.go index 4c36d81..3bafbf5 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -18,6 +18,7 @@ import ( "log/slog" + "github.com/AlexSSD7/linsk/utils" "github.com/alessio/shellescape" "github.com/bramvdbogaerde/go-scp" "github.com/phayes/freeport" @@ -59,10 +60,10 @@ type DriveConfig struct { type VMConfig struct { CdromImagePath string + Drives []DriveConfig USBDevices []USBDevicePassthroughConfig ExtraPortForwardingRules []PortForwardingRule - Drives []DriveConfig // Mostly debug-related options. UnrestrictedNetworking bool @@ -126,13 +127,15 @@ func NewVM(logger *slog.Logger, cfg VMConfig) (*VM, error) { driveArgs := "file=" + shellescape.Quote(extraDrive.Path) + ",format=qcow2,if=virtio" if extraDrive.SnapshotMode { - driveArgs += ",snapshot" + driveArgs += ",snapshot=on" } cmdArgs = append(cmdArgs, "-drive", driveArgs) } - if cdromImagePath != "" { + // We're not using clean `cdromImagePath` here because it is set to "." + // when the original string is empty. + if cfg.CdromImagePath != "" { cmdArgs = append(cmdArgs, "-boot", "d", "-cdrom", cdromImagePath) } @@ -264,14 +267,14 @@ func (vm *VM) Run() error { errors.Wrap(cancelErr, "cancel"), ) - return fmt.Errorf("%w %v", combinedErr, getLogErrMsg(vm.stderrBuf.String())) + return fmt.Errorf("%w %v", combinedErr, utils.GetLogErrMsg(vm.stderrBuf.String())) } combinedErr := multierr.Combine( append(globalErrs, errors.Wrap(cancelErr, "cancel on exit"))..., ) if combinedErr != nil { - return fmt.Errorf("%w %v", combinedErr, getLogErrMsg(vm.stderrBuf.String())) + return fmt.Errorf("%w %v", combinedErr, utils.GetLogErrMsg(vm.stderrBuf.String())) } return nil