diff --git a/cmd/launch.go b/cmd/launch.go index 5efdb9e..444ad18 100644 --- a/cmd/launch.go +++ b/cmd/launch.go @@ -22,29 +22,29 @@ var launchCmd = &cobra.Command{ Run: launch, } -var machineArch, machineVersion, machineCPU, machineMemory, machineDisk, machinePort, sshPort, machineName, machineMount string +var machineArch, imageVersion, machineCPU, machineMemory, machineDisk, machinePort, sshPort, machineName, machineMount string func init() { includeLaunchFlags(launchCmd) } func includeLaunchFlags(cmd *cobra.Command) { - cmd.Flags().StringVarP(&machineVersion, "version", "v", "3.16.0", "Alpine image version.") + cmd.Flags().StringVarP(&imageVersion, "image", "i", "alpine_3.16.0", "Image to be launched.") cmd.Flags().StringVarP(&machineArch, "arch", "a", "x86_64", "Machine architecture.") cmd.Flags().StringVarP(&machineCPU, "cpu", "c", "4", "Number of CPUs to allocate.") cmd.Flags().StringVarP(&machineMemory, "memory", "m", "2048", "Amount of memory to allocate. Positive integers, in kilobytes.") cmd.Flags().StringVarP(&machineDisk, "disk", "d", "10G", "Disk space to allocate. Positive integers, in bytes, or with K, M, G suffix.") - cmd.Flags().StringVarP(&machineMount, "mount", "", "", "Path to host directory to be exposed on guest. (default $HOME)") + cmd.Flags().StringVarP(&machineMount, "mount", "", "", "Path to host directory to be exposed on guest.") cmd.Flags().StringVarP(&sshPort, "ssh", "s", "22", "Forward VM SSH port to host.") cmd.Flags().StringVarP(&machinePort, "port", "p", "", "Forward VM ports to host. Multiple ports can be separated by `,`.") cmd.Flags().StringVarP(&machineName, "name", "n", "", "Name for the instance") } -func correctArguments(machineVersion string, machineArch string, machineCPU string, +func correctArguments(imageVersion string, machineArch string, machineCPU string, machineMemory string, machineDisk string, sshPort string, machinePort string) error { - if machineVersion != "3.16.0" { - return errors.New("unsupported version. only -v 3.16.0 is currently available") + if !utils.StringSliceContains([]string{"alpine_3.16.0", "debian_11.3.0"}, imageVersion) { + return errors.New("unsupported image. only -i alpine_3.16.0 | debian_11.3.0 are currently available") } if machineArch != "aarch64" && machineArch != "x86_64" { @@ -107,7 +107,7 @@ func correctArguments(machineVersion string, machineArch string, machineCPU stri func launch(cmd *cobra.Command, args []string) { - err := correctArguments(machineVersion, machineArch, machineCPU, machineMemory, machineDisk, sshPort, machinePort) + err := correctArguments(imageVersion, machineArch, machineCPU, machineMemory, machineDisk, sshPort, machinePort) if err != nil { log.Fatal("parameter format: " + err.Error()) } @@ -131,21 +131,18 @@ func launch(cmd *cobra.Command, args []string) { log.Fatal("machine " + machineName + " exists") } - if machineMount == "" { - machineMount = userHomeDir - } - machineConfig := qemu.MachineConfig{ - Alias: machineName, - Image: "alpine_" + machineVersion + "-" + machineArch + ".qcow2", - Arch: machineArch, - Version: machineVersion, - CPU: machineCPU, - Memory: machineMemory, - Disk: machineDisk, - Mount: machineMount, - Port: machinePort, - SSHPort: sshPort, + Alias: machineName, + Image: imageVersion + "-" + machineArch + ".qcow2", + Arch: machineArch, + CPU: machineCPU, + Memory: machineMemory, + Disk: machineDisk, + Mount: machineMount, + Port: machinePort, + SSHPort: sshPort, + SSHUser: "root", + SSHPassword: "root", } machineConfig.Location = filepath.Join(userHomeDir, ".macpine", machineConfig.Alias) diff --git a/cmd/list.go b/cmd/list.go index e9ff834..c6e5d9c 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -6,6 +6,7 @@ import ( "log" "os" "path/filepath" + "strings" "text/tabwriter" "github.com/beringresearch/macpine/host" @@ -58,9 +59,9 @@ func list(cmd *cobra.Command, args []string) { } w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) - fmt.Fprintln(w, "NAME\tSTATUS\tSSH\tPORTS\tARCH\tPID\t") + fmt.Fprintln(w, "NAME\tOS\tSTATUS\tSSH\tPORTS\tARCH\tPID\t") for i, machine := range config { - fmt.Fprintln(w, machine.Alias+" \t"+status[i]+" \t"+machine.SSHPort+" \t"+machine.Port+" \t"+machine.Arch+" \t"+fmt.Sprint(pid[i])+" \t") + fmt.Fprintln(w, machine.Alias+" \t"+strings.Split(machine.Image, "_")[0]+" \t"+status[i]+" \t"+machine.SSHPort+" \t"+machine.Port+" \t"+machine.Arch+" \t"+fmt.Sprint(pid[i])+" \t") } w.Flush() diff --git a/qemu/ops.go b/qemu/ops.go index d047d2c..8ef5240 100644 --- a/qemu/ops.go +++ b/qemu/ops.go @@ -24,25 +24,26 @@ import ( ) type MachineConfig struct { - Alias string `yaml:"alias"` - Image string `yaml:"image"` - Arch string `yaml:"arch"` - Version string `yaml:"version"` - CPU string `yaml:"cpu"` - Memory string `yaml:"memory"` - Disk string `yaml:"disk"` - Mount string `yaml:"mount"` - Port string `yaml:"port"` - SSHPort string `yaml:"sshport"` - Location string `yaml:"location"` + Alias string `yaml:"alias"` + Image string `yaml:"image"` + Arch string `yaml:"arch"` + CPU string `yaml:"cpu"` + Memory string `yaml:"memory"` + Disk string `yaml:"disk"` + Mount string `yaml:"mount"` + Port string `yaml:"port"` + SSHPort string `yaml:"sshport"` + SSHUser string `yaml:"sshuser"` + SSHPassword string `yaml:"sshpassword"` + Location string `yaml:"location"` } //ExecShell starts an interactive shell terminal in VM func (c *MachineConfig) ExecShell() error { host := "localhost:" + c.SSHPort - user := "root" - pwd := "root" + user := c.SSHUser + pwd := c.SSHPassword var err error @@ -102,10 +103,10 @@ func (c *MachineConfig) ExecShell() error { func (c *MachineConfig) Exec(cmd string) error { config := &ssh.ClientConfig{ - User: "root", + User: c.SSHUser, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Auth: []ssh.AuthMethod{ - ssh.Password("root"), + ssh.Password(c.SSHPassword), }, } @@ -189,15 +190,6 @@ func (c *MachineConfig) Stop() error { // Start starts up an Alpine VM func (c *MachineConfig) Start() error { - userHomeDir, err := os.UserHomeDir() - if err != nil { - log.Fatal(err) - } - - if c.Mount == "" { - c.Mount = userHomeDir - } - exposedPorts := "user,id=net0,hostfwd=tcp::" + c.SSHPort + "-:22" if c.Port != "" { @@ -213,7 +205,7 @@ func (c *MachineConfig) Start() error { accelAarch64 := "hvf" - _, err = syscall.Sysctl("sysctl.proc_translated") + _, err := syscall.Sysctl("sysctl.proc_translated") appleSilicon := true if err != nil && err.Error() == "no such file or directory" { accelAarch64 = "tcg" @@ -241,7 +233,10 @@ func (c *MachineConfig) Start() error { x86Args := []string{"-accel", accelx86} - commonArgs := []string{"-m", c.Memory, "-global", "ICH9-LPC.disable_s3=1", + mountArgs := []string{"-fsdev", "local,path=" + c.Mount + ",security_model=none,id=host0", + "-device", "virtio-9p-pci,fsdev=host0,mount_tag=host0"} + + commonArgs := []string{"-m", c.Memory, "-smp", c.CPU + ",sockets=1,cores=" + c.CPU + ",threads=1", "-hda", filepath.Join(c.Location, c.Image), "-nographic", @@ -255,9 +250,7 @@ func (c *MachineConfig) Start() error { "-qmp", "chardev:char-qmp", "-parallel", "none", //"-virtfs", "local,path=" + c.Mount + ",security_model=none,mount_tag=Home", - "-fsdev", "local,path=" + c.Mount + ",security_model=none,id=host0", - "-device", "virtio-9p-pci,fsdev=host0,mount_tag=host0", - "-name", "alpine"} + "-name", c.Alias} if c.Arch == "aarch64" { qemuArgs = append(aarch64Args, commonArgs...) @@ -266,6 +259,10 @@ func (c *MachineConfig) Start() error { qemuArgs = append(x86Args, commonArgs...) } + if c.Mount != "" { + qemuArgs = append(qemuArgs, mountArgs...) + } + cmd := exec.Command(qemuCmd, qemuArgs..., ) @@ -282,20 +279,22 @@ func (c *MachineConfig) Start() error { time.Sleep(1 * time.Second) - err = utils.Retry(10, 2*time.Second, func() error { - err := c.Exec("mkdir -p /root/mnt/; mount -t 9p -o trans=virtio host0 /root/mnt/") + if c.Mount != "" { + err = utils.Retry(10, 2*time.Second, func() error { + err := c.Exec("mkdir -p /root/mnt/; mount -t 9p -o trans=virtio host0 /root/mnt/") + if err != nil { + return err + } + return nil + }) + if err != nil { - return err + return errors.New("unable to mount: " + err.Error()) } - return nil - }) - if err != nil { - return errors.New("unable to mount: " + err.Error()) + log.Printf("Mounted " + c.Mount + ": /root/mnt/") } - log.Printf("Mounted " + c.Mount + ": /root/mnt/") - status, _ := c.Status() if status == "Stopped" { @@ -319,12 +318,12 @@ func (c *MachineConfig) Launch() error { return err } - imageName, alpineURL := utils.GetAlpineURL(c.Version, c.Arch) + imageURL := utils.GetImageURL(c.Image) - if _, err := os.Stat(filepath.Join(cacheDir, imageName)); errors.Is(err, os.ErrNotExist) { - err = utils.DownloadFile(filepath.Join(cacheDir, imageName), alpineURL) + if _, err := os.Stat(filepath.Join(cacheDir, c.Image)); errors.Is(err, os.ErrNotExist) { + err = utils.DownloadFile(filepath.Join(cacheDir, c.Image), imageURL) if err != nil { - return errors.New("unable to download Alpine " + c.Version + " for " + c.Arch + ": " + err.Error()) + return errors.New("unable to download " + c.Image + " for " + c.Arch + ": " + err.Error()) } } @@ -344,7 +343,7 @@ func (c *MachineConfig) Launch() error { return err } - _, err = utils.CopyFile(filepath.Join(cacheDir, imageName), filepath.Join(targetDir, imageName)) + _, err = utils.CopyFile(filepath.Join(cacheDir, c.Image), filepath.Join(targetDir, c.Image)) if err != nil { os.RemoveAll(targetDir) return err diff --git a/utils/utils.go b/utils/utils.go index 830d403..bd62b88 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -13,6 +13,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "time" ) @@ -206,6 +207,9 @@ func DownloadFile(filepath string, url string) error { } resp, err := http.Get(url) + if resp.StatusCode != 200 { + return errors.New("requested image download is not supported: StatusCode " + strconv.Itoa(resp.StatusCode)) + } if err != nil { out.Close() return err @@ -227,10 +231,9 @@ func DownloadFile(filepath string, url string) error { return nil } -func GetAlpineURL(version string, arch string) (string, string) { - imageFile := "alpine_" + version + "-" + arch + ".qcow2" - url := "https://github.com/beringresearch/macpine/releases/download/v.01/" + imageFile - return imageFile, url +func GetImageURL(version string) string { + url := "https://github.com/beringresearch/macpine/releases/download/v.01/" + version + return url } func DirExists(path string) (bool, error) {