package main import ( "fmt" "io" "mkvm/volumes" "mkvm/volumes/pools" "net/http" "strings" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) var ( debianRelease string debianPackages []string debianFilenameSuffix = map[pools.ImageFormat]string{ pools.ImageFormatRaw: "-generic-amd64-daily.raw", pools.ImageFormatQcow2: "-generic-amd64-daily.qcow2", } ) var debianCmd = cobra.Command{ Use: "debian vm-name", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { name := args[0] conn, pool, err := getLibvirtAndPool() if err != nil { logrus.WithError(err).Fatal("error connecting to libvirt") return } defer conn.Close() diskImageURL, err := debianGetImageURL(debianRelease, pool.ImageFormat()) if err != nil { logrus.WithError(err).Fatal("error getting disk image URL") } // download disk image err = volumes.Create(conn, pool, argDiskSizeGB, diskImageURL, name) if err != nil { logrus.WithError(err).Fatal("error creating VM disk") } // prepare cloudconfig and start the http server bind, err := getServerListenAddress(conn) if err != nil { logrus.WithError(err).Fatal("error getting server bind address") } serverURL := fmt.Sprintf("http://%s", bind) err = buildCloudConfig(name, fmt.Sprintf("%s/phone-home", serverURL)) if err != nil { logrus.WithError(err).Fatal("error building cloud config") } go runHTTPServer(bind) smbios := map[int]map[string]string{1: {"serial": fmt.Sprintf("ds=nocloud-net;s=%s/", serverURL)}} // create domain err = createDomain(conn, pool, name, setSMBIOS(smbios)) if err != nil { logrus.WithError(err).Fatal("error creating domain") } wg.Add(1) logrus.Info("waiting for VM to finish provisioning") wg.Wait() }, } func init() { debianCmd.Flags().StringVarP(&debianRelease, "release", "r", "bookworm", "debian release to install. Options: bookworm (default), trixie, sid") debianCmd.Flags().StringArrayVarP(&debianPackages, "packages", "p", nil, "apt packages to install") rootCmd.AddCommand(&debianCmd) } func debianGetImageURL(release string, format pools.ImageFormat) (string, error) { imageSuffix, ok := debianFilenameSuffix[format] if !ok { return "", fmt.Errorf("unexpected image format %s from storage pool", format) } diskImageURLPrefix := fmt.Sprintf("https://cloud.debian.org/images/cloud/%s/daily/latest", release) // find image URL + hash shaURL := fmt.Sprintf("%s/SHA512SUMS", diskImageURLPrefix) shaResp, err := http.Get(shaURL) if err != nil { return "", err } defer shaResp.Body.Close() shas, err := io.ReadAll(shaResp.Body) if err != nil { return "", err } if shaResp.StatusCode != http.StatusOK { logrus.WithFields(logrus.Fields{ "status": shaResp.Status, "url": shaURL, "resp": string(shas), }).Fatal("failed to get image hash") } for _, line := range strings.Split(string(shas), "\n") { hash, filename, ok := strings.Cut(strings.TrimSpace(line), " ") if !ok { continue } if !strings.HasSuffix(filename, imageSuffix) { continue } return fmt.Sprintf("%s/%s#hash=sha512:%s", diskImageURLPrefix, filename, hash), nil } return "", fmt.Errorf("unable to find hash of image in %s", shaURL) }