Repository: lima-vm/lima

Issue:
Consider setting `GUESTAGENT_COMPRESS=y` by default
https://github.com/lima-vm/lima/blob/ddc0081a241b05d3d7379480613f2c2c79ab2136/Kconfig#L34-L38

- https://github.com/lima-vm/lima/pull/2438

This should be enabled by default unless there is a significant drawback?



Discussion:
@afbjorklund: It should be a little slower* (due to legacy gzip), but not more than that.

\* in the order of milliseconds, like a quarter of a second or something?@kairveeehh: would like to take up this issue , should I follow up with a PR or wait for assignment?@AkihiroSuda: You can open a PR at anytime without waiting for an assignment.

Things to consider:
- Confirm that the overhead isn't more than a few seconds on typical machines
- Print a warning when the both compressed agent and the uncompressed one are present. The one with the newer timestamp should be selected.@kairveeehh: > You can open a PR at anytime without waiting for an assignment.
> 
> Things to consider:
> - Confirm that the overhead isn't more than a few seconds on typical machines
> - Print a warning when the both compressed agent and the uncompressed one are present. The one with the newer timestamp should be selected.

Working on it.@AkihiroSuda: @kairveeehh 

> Working on it.

Are you still working on this?@kairveeehh: > @kairveeehh 
> 
> > Working on it.
> 
> Are you still working on this?

Yes , was a bit busy cause of uni exams but I'll continue to work on it.@AkihiroSuda: This issue will be fixable in a single-line PR, after finishing this refactoring:
- https://github.com/lima-vm/lima/pull/3504@AkihiroSuda: @kairveeehh Do you want to open this PR?

```diff
diff --git a/Kconfig b/Kconfig
index d52d6fb0..7e40d787 100644
--- a/Kconfig
+++ b/Kconfig
@@ -47,4 +47,4 @@ config GUESTAGENT_COMPRESS
        bool "guestagent compress"
        help
                Compress lima-guestagent
-       default n
+       default y
diff --git a/config.mk b/config.mk
index 9a02aae2..06ab7787 100644
--- a/config.mk
+++ b/config.mk
@@ -5,4 +5,4 @@ CONFIG_GUESTAGENT_ARCH_ARMV7L=y
 CONFIG_GUESTAGENT_ARCH_PPC64LE=y
 CONFIG_GUESTAGENT_ARCH_RISCV64=y
 CONFIG_GUESTAGENT_ARCH_S390X=y
-CONFIG_GUESTAGENT_COMPRESS=n
+CONFIG_GUESTAGENT_COMPRESS=y
```@kairveeehh: > [@kairveeehh](https://github.com/kairveeehh) Do you want to open this PR?
> 
> diff --git a/Kconfig b/Kconfig
> index d52d6fb0..7e40d787 100644
> --- a/Kconfig
> +++ b/Kconfig
> @@ -47,4 +47,4 @@ config GUESTAGENT_COMPRESS
>         bool "guestagent compress"
>         help
>                 Compress lima-guestagent
> -       default n
> +       default y
> diff --git a/config.mk b/config.mk
> index 9a02aae2..06ab7787 100644
> --- a/config.mk
> +++ b/config.mk
> @@ -5,4 +5,4 @@ CONFIG_GUESTAGENT_ARCH_ARMV7L=y
>  CONFIG_GUESTAGENT_ARCH_PPC64LE=y
>  CONFIG_GUESTAGENT_ARCH_RISCV64=y
>  CONFIG_GUESTAGENT_ARCH_S390X=y
> -CONFIG_GUESTAGENT_COMPRESS=n
> +CONFIG_GUESTAGENT_COMPRESS=y

I would have not mind but amidst the situations in India , I am travelling as of now ( the flights are cancelled) so would suggest moving ahead if its required early.
thanks.@AkihiroSuda: Sure, hope you are in safe!

- https://github.com/lima-vm/lima/pull/3529

Solution patch:
diff --git a/cmd/limactl/genschema.go b/cmd/limactl/genschema.go
index 296db8ee..d1be43ae 100644
--- a/cmd/limactl/genschema.go
+++ b/cmd/limactl/genschema.go
@@ -9,10 +9,9 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/goccy/go-yaml"
 	"github.com/invopop/jsonschema"
+	"github.com/lima-vm/lima/pkg/jsonschemautil"
 	"github.com/lima-vm/lima/pkg/limayaml"
-	jsonschema2 "github.com/santhosh-tekuri/jsonschema/v6"
 	"github.com/spf13/cobra"
 	orderedmap "github.com/wk8/go-ordered-map/v2"
 
@@ -93,22 +92,8 @@ func genschemaAction(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
-	compiler := jsonschema2.NewCompiler()
-	schema2, err := compiler.Compile(file)
-	if err != nil {
-		return err
-	}
 	for _, f := range args {
-		b, err := os.ReadFile(f)
-		if err != nil {
-			return err
-		}
-		var y any
-		err = yaml.Unmarshal(b, &y)
-		if err != nil {
-			return err
-		}
-		err = schema2.Validate(y)
+		err = jsonschemautil.Validate(file, f)
 		if err != nil {
 			return fmt.Errorf("%q: %w", f, err)
 		}
diff --git a/cmd/limactl/hostagent.go b/cmd/limactl/hostagent.go
index f7606ff0..ae02207d 100644
--- a/cmd/limactl/hostagent.go
+++ b/cmd/limactl/hostagent.go
@@ -32,6 +32,7 @@ func newHostagentCommand() *cobra.Command {
 	hostagentCommand.Flags().StringP("pidfile", "p", "", "Write PID to file")
 	hostagentCommand.Flags().String("socket", "", "Path of hostagent socket")
 	hostagentCommand.Flags().Bool("run-gui", false, "Run GUI synchronously within hostagent")
+	hostagentCommand.Flags().String("guestagent", "", "Local file path (not URL) of lima-guestagent.OS-ARCH[.gz]")
 	hostagentCommand.Flags().String("nerdctl-archive", "", "Local file path (not URL) of nerdctl-full-VERSION-GOOS-GOARCH.tar.gz")
 	return hostagentCommand
 }
@@ -78,6 +79,13 @@ func hostagentAction(cmd *cobra.Command, args []string) error {
 
 	initLogrus(stderr)
 	var opts []hostagent.Opt
+	guestagentBinary, err := cmd.Flags().GetString("guestagent")
+	if err != nil {
+		return err
+	}
+	if guestagentBinary != "" {
+		opts = append(opts, hostagent.WithGuestAgentBinary(guestagentBinary))
+	}
 	nerdctlArchive, err := cmd.Flags().GetString("nerdctl-archive")
 	if err != nil {
 		return err
diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go
index cab834b7..a0e4370f 100644
--- a/pkg/cidata/cidata.go
+++ b/pkg/cidata/cidata.go
@@ -30,7 +30,6 @@ import (
 	"github.com/lima-vm/lima/pkg/osutil"
 	"github.com/lima-vm/lima/pkg/sshutil"
 	"github.com/lima-vm/lima/pkg/store/filenames"
-	"github.com/lima-vm/lima/pkg/usrlocalsharelima"
 	"github.com/sirupsen/logrus"
 )
 
@@ -355,7 +354,7 @@ func GenerateCloudConfig(instDir, name string, instConfig *limayaml.LimaYAML) er
 	return os.WriteFile(filepath.Join(instDir, filenames.CloudConfig), config, 0o444)
 }
 
-func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, nerdctlArchive string, vsockPort int, virtioPort string) error {
+func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, vsockPort int, virtioPort string) error {
 	args, err := templateArgs(true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort)
 	if err != nil {
 		return err
@@ -391,31 +390,32 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS
 		}
 	}
 
-	guestAgentBinary, err := usrlocalsharelima.GuestAgentBinary(*instConfig.OS, *instConfig.Arch)
-	if err != nil {
-		return err
-	}
-	var guestAgent io.ReadCloser
-	guestAgent, err = os.Open(guestAgentBinary)
-	if err != nil {
-		if !errors.Is(err, os.ErrNotExist) {
-			return err
-		}
-		compressedGuestAgent, err := os.Open(guestAgentBinary + ".gz")
-		if err != nil {
-			return err
-		}
-		logrus.Debugf("Decompressing %s.gz", guestAgentBinary)
-		guestAgent, err = gzip.NewReader(compressedGuestAgent)
-		if err != nil {
-			return err
+	if guestAgentBinary != "" {
+		var guestAgent io.ReadCloser
+		if strings.HasSuffix(guestAgentBinary, ".gz") {
+			logrus.Debugf("Decompressing %s", guestAgentBinary)
+			guestAgentGz, err := os.Open(guestAgentBinary)
+			if err != nil {
+				return err
+			}
+			defer guestAgentGz.Close()
+			guestAgent, err = gzip.NewReader(guestAgentGz)
+			if err != nil {
+				return err
+			}
+		} else {
+			guestAgent, err = os.Open(guestAgentBinary)
+			if err != nil {
+				return err
+			}
 		}
+
+		defer guestAgent.Close()
+		layout = append(layout, iso9660util.Entry{
+			Path:   "lima-guestagent",
+			Reader: guestAgent,
+		})
 	}
-	defer guestAgent.Close()
-	layout = append(layout, iso9660util.Entry{
-		Path:   "lima-guestagent",
-		Reader: guestAgent,
-	})
 
 	if nerdctlArchive != "" {
 		nftgz := args.Containerd.Archive
diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go
index f48f8dd8..36e6d243 100644
--- a/pkg/hostagent/hostagent.go
+++ b/pkg/hostagent/hostagent.go
@@ -75,11 +75,19 @@ type HostAgent struct {
 }
 
 type options struct {
-	nerdctlArchive string // local path, not URL
+	guestAgentBinary string
+	nerdctlArchive   string // local path, not URL
 }
 
 type Opt func(*options) error
 
+func WithGuestAgentBinary(s string) Opt {
+	return func(o *options) error {
+		o.guestAgentBinary = s
+		return nil
+	}
+}
+
 func WithNerdctlArchive(s string) Opt {
 	return func(o *options) error {
 		o.nerdctlArchive = s
@@ -134,7 +142,7 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
 	if err := cidata.GenerateCloudConfig(inst.Dir, instName, inst.Config); err != nil {
 		return nil, err
 	}
-	if err := cidata.GenerateISO9660(inst.Dir, instName, inst.Config, udpDNSLocalPort, tcpDNSLocalPort, o.nerdctlArchive, vSockPort, virtioPort); err != nil {
+	if err := cidata.GenerateISO9660(inst.Dir, instName, inst.Config, udpDNSLocalPort, tcpDNSLocalPort, o.guestAgentBinary, o.nerdctlArchive, vSockPort, virtioPort); err != nil {
 		return nil, err
 	}
 
diff --git a/pkg/infoutil/infoutil.go b/pkg/infoutil/infoutil.go
index c3c2469b..82daac81 100644
--- a/pkg/infoutil/infoutil.go
+++ b/pkg/infoutil/infoutil.go
@@ -4,19 +4,29 @@
 package infoutil
 
 import (
+	"errors"
+	"io/fs"
+
 	"github.com/lima-vm/lima/pkg/driverutil"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/store/dirnames"
 	"github.com/lima-vm/lima/pkg/templatestore"
+	"github.com/lima-vm/lima/pkg/usrlocalsharelima"
 	"github.com/lima-vm/lima/pkg/version"
+	"github.com/sirupsen/logrus"
 )
 
 type Info struct {
-	Version         string                   `json:"version"`
-	Templates       []templatestore.Template `json:"templates"`
-	DefaultTemplate *limayaml.LimaYAML       `json:"defaultTemplate"`
-	LimaHome        string                   `json:"limaHome"`
-	VMTypes         []string                 `json:"vmTypes"` // since Lima v0.14.2
+	Version         string                       `json:"version"`
+	Templates       []templatestore.Template     `json:"templates"`
+	DefaultTemplate *limayaml.LimaYAML           `json:"defaultTemplate"`
+	LimaHome        string                       `json:"limaHome"`
+	VMTypes         []string                     `json:"vmTypes"`     // since Lima v0.14.2
+	GuestAgents     map[limayaml.Arch]GuestAgent `json:"guestAgents"` // since Lima v1.1.0
+}
+
+type GuestAgent struct {
+	Location string `json:"location"` // since Lima v1.1.0
 }
 
 func GetInfo() (*Info, error) {
@@ -32,6 +42,7 @@ func GetInfo() (*Info, error) {
 		Version:         version.Version,
 		DefaultTemplate: y,
 		VMTypes:         driverutil.Drivers(),
+		GuestAgents:     make(map[limayaml.Arch]GuestAgent),
 	}
 	info.Templates, err = templatestore.Templates()
 	if err != nil {
@@ -41,5 +52,19 @@ func GetInfo() (*Info, error) {
 	if err != nil {
 		return nil, err
 	}
+	for _, arch := range limayaml.ArchTypes {
+		bin, err := usrlocalsharelima.GuestAgentBinary(limayaml.LINUX, arch)
+		if err != nil {
+			if errors.Is(err, fs.ErrNotExist) {
+				logrus.WithError(err).Debugf("Failed to resolve the guest agent binary for %q", arch)
+			} else {
+				logrus.WithError(err).Warnf("Failed to resolve the guest agent binary for %q", arch)
+			}
+			continue
+		}
+		info.GuestAgents[arch] = GuestAgent{
+			Location: bin,
+		}
+	}
 	return info, nil
 }
diff --git a/pkg/instance/start.go b/pkg/instance/start.go
index c76a6957..79aa90f3 100644
--- a/pkg/instance/start.go
+++ b/pkg/instance/start.go
@@ -20,6 +20,7 @@ import (
 	"github.com/lima-vm/lima/pkg/driverutil"
 	"github.com/lima-vm/lima/pkg/executil"
 	"github.com/lima-vm/lima/pkg/osutil"
+	"github.com/lima-vm/lima/pkg/usrlocalsharelima"
 	"github.com/mattn/go-isatty"
 
 	"github.com/lima-vm/lima/pkg/downloader"
@@ -72,11 +73,20 @@ func ensureNerdctlArchiveCache(ctx context.Context, y *limayaml.LimaYAML, create
 
 type Prepared struct {
 	Driver              driver.Driver
+	GuestAgent          string
 	NerdctlArchiveCache string
 }
 
 // Prepare ensures the disk, the nerdctl archive, etc.
 func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) {
+	var guestAgent string
+	if !*inst.Config.Plain {
+		var err error
+		guestAgent, err = usrlocalsharelima.GuestAgentBinary(*inst.Config.OS, *inst.Config.Arch)
+		if err != nil {
+			return nil, err
+		}
+	}
 	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
 		Instance: inst,
 	})
@@ -104,6 +114,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) {
 
 	return &Prepared{
 		Driver:              limaDriver,
+		GuestAgent:          guestAgent,
 		NerdctlArchiveCache: nerdctlArchiveCache,
 	}, nil
 }
@@ -171,6 +182,9 @@ func Start(ctx context.Context, inst *store.Instance, limactl string, launchHost
 	if prepared.Driver.CanRunGUI() {
 		args = append(args, "--run-gui")
 	}
+	if prepared.GuestAgent != "" {
+		args = append(args, "--guestagent", prepared.GuestAgent)
+	}
 	if prepared.NerdctlArchiveCache != "" {
 		args = append(args, "--nerdctl-archive", prepared.NerdctlArchiveCache)
 	}
diff --git a/pkg/jsonschemautil/jsonschemautil.go b/pkg/jsonschemautil/jsonschemautil.go
new file mode 100644
index 00000000..7f7b16a0
--- /dev/null
+++ b/pkg/jsonschemautil/jsonschemautil.go
@@ -0,0 +1,29 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package jsonschemautil
+
+import (
+	"os"
+
+	"github.com/goccy/go-yaml"
+	"github.com/santhosh-tekuri/jsonschema/v6"
+)
+
+func Validate(schemafile, instancefile string) error {
+	compiler := jsonschema.NewCompiler()
+	schema, err := compiler.Compile(schemafile)
+	if err != nil {
+		return err
+	}
+	instance, err := os.ReadFile(instancefile)
+	if err != nil {
+		return err
+	}
+	var y any
+	err = yaml.Unmarshal(instance, &y)
+	if err != nil {
+		return err
+	}
+	return schema.Validate(y)
+}
diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go
index fcaca4ad..c96a698e 100644
--- a/pkg/usrlocalsharelima/usrlocalsharelima.go
+++ b/pkg/usrlocalsharelima/usrlocalsharelima.go
@@ -80,6 +80,7 @@ func Dir() (string, error) {
 		ostype, arch, self, gaCandidates)
 }
 
+// GuestAgentBinary returns the guest agent binary, possibly with ".gz" suffix.
 func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (string, error) {
 	if ostype == "" {
 		return "", errors.New("os must be set")
@@ -91,5 +92,35 @@ func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (string, error) {
 	if err != nil {
 		return "", err
 	}
-	return filepath.Join(dir, "lima-guestagent."+ostype+"-"+arch), nil
+	uncomp := filepath.Join(dir, "lima-guestagent."+ostype+"-"+arch)
+	comp := uncomp + ".gz"
+	res, err := chooseGABinary([]string{comp, uncomp})
+	if err != nil {
+		logrus.Debug(err)
+		return "", fmt.Errorf("guest agent binary could not be found for %s-%s (Hint: try installing `lima-additional-guestagents` package)", ostype, arch)
+	}
+	return res, nil
+}
+
+func chooseGABinary(candidates []string) (string, error) {
+	var entries []string
+	for _, f := range candidates {
+		if _, err := os.Stat(f); err != nil {
+			if !errors.Is(err, fs.ErrNotExist) {
+				logrus.WithError(err).Warnf("failed to stat %q", f)
+			}
+			continue
+		}
+		entries = append(entries, f)
+	}
+	switch len(entries) {
+	case 0:
+		return "", fmt.Errorf("%w: attempted %v", fs.ErrNotExist, candidates)
+	case 1:
+		return entries[0], nil
+	default:
+		logrus.Warnf("multiple files found, choosing %q from %v; consider removing the other ones",
+			entries[0], candidates)
+		return entries[0], nil
+	}
 }



Test patch:
diff --git a/pkg/jsonschemautil/jsonschemautil_test.go b/pkg/jsonschemautil/jsonschemautil_test.go
new file mode 100644
index 00000000..6301af7b
--- /dev/null
+++ b/pkg/jsonschemautil/jsonschemautil_test.go
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package jsonschemautil
+
+import (
+	"testing"
+
+	"gotest.tools/v3/assert"
+)
+
+func TestValidateValidInstance(t *testing.T) {
+	schema := "testdata/schema.json"
+	instance := "testdata/valid.yaml"
+	err := Validate(schema, instance)
+	assert.NilError(t, err)
+}
+
+func TestValidateInvalidInstance(t *testing.T) {
+	schema := "testdata/schema.json"
+	instance := "testdata/invalid.yaml"
+	err := Validate(schema, instance)
+	assert.ErrorContains(t, err, "jsonschema validation failed")
+}
diff --git a/pkg/jsonschemautil/testdata/invalid.yaml b/pkg/jsonschemautil/testdata/invalid.yaml
new file mode 100644
index 00000000..8538dd10
--- /dev/null
+++ b/pkg/jsonschemautil/testdata/invalid.yaml
@@ -0,0 +1,2 @@
+name: John Doe
+age: "30"
diff --git a/pkg/jsonschemautil/testdata/schema.json b/pkg/jsonschemautil/testdata/schema.json
new file mode 100644
index 00000000..33eb2d18
--- /dev/null
+++ b/pkg/jsonschemautil/testdata/schema.json
@@ -0,0 +1,11 @@
+{
+  "type": "object",
+  "properties": {
+    "name": {
+      "type": "string"
+    },
+    "age": {
+      "type": "integer"
+    }
+  }
+}
diff --git a/pkg/jsonschemautil/testdata/valid.yaml b/pkg/jsonschemautil/testdata/valid.yaml
new file mode 100644
index 00000000..be10e25b
--- /dev/null
+++ b/pkg/jsonschemautil/testdata/valid.yaml
@@ -0,0 +1,2 @@
+name: John Doe
+age: 30