Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions cmd/limactl/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"os"
"runtime"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -50,12 +49,13 @@ func deleteAction(cmd *cobra.Command, args []string) error {
if err := instance.Delete(cmd.Context(), inst, force); err != nil {
return fmt.Errorf("failed to delete instance %q: %w", instName, err)
}
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
deleted, err := autostart.DeleteStartAtLoginEntry(ctx, runtime.GOOS, instName)
if err != nil && !errors.Is(err, os.ErrNotExist) {
logrus.WithError(err).Warnf("The autostart file for instance %q does not exist", instName)
} else if deleted {
logrus.Infof("The autostart file %q has been deleted", autostart.GetFilePath(runtime.GOOS, instName))
if registered, err := autostart.IsRegistered(ctx, inst); err != nil && !errors.Is(err, autostart.ErrNotSupported) {
logrus.WithError(err).Warnf("Failed to check if the autostart entry for instance %q is registered", instName)
} else if registered {
if err := autostart.UnregisterFromStartAtLogin(ctx, inst); err != nil {
logrus.WithError(err).Warnf("Failed to unregister the autostart entry for instance %q", instName)
} else {
logrus.Infof("The autostart entry for instance %q has been unregistered", instName)
}
}
logrus.Infof("Deleted %q (%q)", instName, inst.Dir)
Expand Down
12 changes: 9 additions & 3 deletions cmd/limactl/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/cmd/limactl/editflags"
"github.com/lima-vm/lima/v2/pkg/autostart"
"github.com/lima-vm/lima/v2/pkg/driverutil"
"github.com/lima-vm/lima/v2/pkg/editutil"
"github.com/lima-vm/lima/v2/pkg/instance"
Expand Down Expand Up @@ -160,9 +161,14 @@ func editAction(cmd *cobra.Command, args []string) error {
if !start {
return nil
}
err = reconcile.Reconcile(ctx, inst.Name)
if err != nil {
return err
// Network reconciliation will be performed by the process launched by the autostart manager
if registered, err := autostart.IsRegistered(ctx, inst); err != nil && !errors.Is(err, autostart.ErrNotSupported) {
return fmt.Errorf("failed to check if the autostart entry for instance %q is registered: %w", inst.Name, err)
} else if !registered {
err = reconcile.Reconcile(ctx, inst.Name)
if err != nil {
return err
}
Comment on lines +164 to +171
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic seems to be repeated in several places. Should it be part of reconcile.Reconcile() instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several reconcile.Reconcile() calls that are not related to autostart.IsRegistered().
It is not good to put it in reconcile.Reconcile().

}

// store.Inspect() syncs values between inst.YAML and the store.
Expand Down
12 changes: 9 additions & 3 deletions cmd/limactl/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/lima-vm/lima/v2/pkg/autostart"
"github.com/lima-vm/lima/v2/pkg/envutil"
"github.com/lima-vm/lima/v2/pkg/instance"
"github.com/lima-vm/lima/v2/pkg/ioutilx"
Expand Down Expand Up @@ -101,9 +102,14 @@ func shellAction(cmd *cobra.Command, args []string) error {
return nil
}

err = reconcile.Reconcile(ctx, inst.Name)
if err != nil {
return err
// Network reconciliation will be performed by the process launched by the autostart manager
if registered, err := autostart.IsRegistered(ctx, inst); err != nil && !errors.Is(err, autostart.ErrNotSupported) {
return fmt.Errorf("failed to check if the autostart entry for instance %q is registered: %w", inst.Name, err)
} else if !registered {
err = reconcile.Reconcile(ctx, inst.Name)
if err != nil {
return err
}
}

err = instance.Start(ctx, inst, false, false)
Expand Down
28 changes: 17 additions & 11 deletions cmd/limactl/start-at-login_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ package main

import (
"errors"
"fmt"
"os"
"runtime"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -38,18 +38,24 @@ func startAtLoginAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
if startAtLogin {
if err := autostart.CreateStartAtLoginEntry(ctx, runtime.GOOS, inst.Name, inst.Dir); err != nil {
logrus.WithError(err).Warnf("Can't create an autostart file for instance %q", inst.Name)
} else {
logrus.Infof("The autostart file %q has been created or updated", autostart.GetFilePath(runtime.GOOS, inst.Name))
if registered, err := autostart.IsRegistered(ctx, inst); err != nil {
return fmt.Errorf("failed to check if the autostart entry for instance %q is registered: %w", inst.Name, err)
} else if startAtLogin {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use else when the if branch ends in a return. @alexandear I'm surprised we don't have a linter rule enabled for this.

Suggested change
} else if startAtLogin {
}
if startAtLogin {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter rule should be subject to return in the last else block.

verb := "create"
if registered {
verb = "update"
}
if err := autostart.RegisterToStartAtLogin(ctx, inst); err != nil {
return fmt.Errorf("failed to %s the autostart entry for instance %q: %w", verb, inst.Name, err)
}
logrus.Infof("The autostart entry for instance %q has been %sd", inst.Name, verb)
} else {
deleted, err := autostart.DeleteStartAtLoginEntry(ctx, runtime.GOOS, instName)
if err != nil {
logrus.WithError(err).Warnf("The autostart file %q could not be deleted", instName)
} else if deleted {
logrus.Infof("The autostart file %q has been deleted", autostart.GetFilePath(runtime.GOOS, instName))
if !registered {
logrus.Infof("The autostart entry for instance %q is not registered", inst.Name)
} else if err := autostart.UnregisterFromStartAtLogin(ctx, inst); err != nil {
return fmt.Errorf("failed to unregister the autostart entry for instance %q: %w", inst.Name, err)
} else {
logrus.Infof("The autostart entry for instance %q has been unregistered", inst.Name)
}
}

Expand Down
12 changes: 9 additions & 3 deletions cmd/limactl/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/spf13/pflag"

"github.com/lima-vm/lima/v2/cmd/limactl/editflags"
"github.com/lima-vm/lima/v2/pkg/autostart"
"github.com/lima-vm/lima/v2/pkg/driverutil"
"github.com/lima-vm/lima/v2/pkg/editutil"
"github.com/lima-vm/lima/v2/pkg/instance"
Expand Down Expand Up @@ -580,9 +581,14 @@ func startAction(cmd *cobra.Command, args []string) error {
logrus.Warnf("expected status %q, got %q", limatype.StatusStopped, inst.Status)
}
ctx := cmd.Context()
err = reconcile.Reconcile(ctx, inst.Name)
if err != nil {
return err
// Network reconciliation will be performed by the process launched by the autostart manager
if registered, err := autostart.IsRegistered(ctx, inst); err != nil && !errors.Is(err, autostart.ErrNotSupported) {
return fmt.Errorf("failed to check if the autostart entry for instance %q is registered: %w", inst.Name, err)
} else if !registered {
err = reconcile.Reconcile(ctx, inst.Name)
if err != nil {
return err
}
}

launchHostAgentForeground := false
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/containerd/continuity v0.4.5
github.com/containers/gvisor-tap-vsock v0.8.7 // gomodjail:unconfined
github.com/coreos/go-semver v0.3.1
github.com/coreos/go-systemd/v22 v22.6.0
github.com/cpuguy83/go-md2man/v2 v2.0.7
github.com/digitalocean/go-qemu v0.0.0-20221209210016-f035778c97f7
github.com/diskfs/go-diskfs v1.7.0 // gomodjail:unconfined
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ github.com/containers/gvisor-tap-vsock v0.8.7 h1:mFMMU5CIXO9sbtsgECc90loUHx15km3
github.com/containers/gvisor-tap-vsock v0.8.7/go.mod h1:Rf2gm4Lpac0IZbg8wwQDh7UuKCxHmnxar0hEZ08OXY8=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.6.0 h1:aGVa/v8B7hpb0TKl0MWoAavPDmHvobFe5R5zn0bCJWo=
github.com/coreos/go-systemd/v22 v22.6.0/go.mod h1:iG+pp635Fo7ZmV/j14KUcmEyWF+0X7Lua8rrTWzYgWU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
Expand Down
139 changes: 38 additions & 101 deletions pkg/autostart/autostart.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,118 +6,55 @@ package autostart

import (
"context"
_ "embed"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"sync"

"github.com/lima-vm/lima/v2/pkg/textutil"
"github.com/lima-vm/lima/v2/pkg/limatype"
)

//go:embed lima-vm@INSTANCE.service
var systemdTemplate string

//go:embed io.lima-vm.autostart.INSTANCE.plist
var launchdTemplate string
// IsRegistered checks if the instance is registered to start at login.
func IsRegistered(ctx context.Context, inst *limatype.Instance) (bool, error) {
return manager().IsRegistered(ctx, inst)
}

// CreateStartAtLoginEntry respect host OS arch and create unit file.
func CreateStartAtLoginEntry(ctx context.Context, hostOS, instName, workDir string) error {
unitPath := GetFilePath(hostOS, instName)
if _, err := os.Stat(unitPath); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
tmpl, err := renderTemplate(hostOS, instName, workDir, os.Executable)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(unitPath), os.ModePerm); err != nil {
return err
}
if err := os.WriteFile(unitPath, tmpl, 0o644); err != nil {
return err
}
return enableDisableService(ctx, "enable", hostOS, GetFilePath(hostOS, instName))
// RegisterToStartAtLogin creates a start-at-login entry for the instance.
func RegisterToStartAtLogin(ctx context.Context, inst *limatype.Instance) error {
return manager().RegisterToStartAtLogin(ctx, inst)
}

// DeleteStartAtLoginEntry respect host OS arch and delete unit file.
// Return true, nil if unit file has been deleted.
func DeleteStartAtLoginEntry(ctx context.Context, hostOS, instName string) (bool, error) {
unitPath := GetFilePath(hostOS, instName)
if _, err := os.Stat(unitPath); err != nil {
return false, err
}
if err := enableDisableService(ctx, "disable", hostOS, GetFilePath(hostOS, instName)); err != nil {
return false, err
}
if err := os.Remove(unitPath); err != nil {
return false, err
}
return true, nil
// UnregisterFromStartAtLogin deletes the start-at-login entry for the instance.
func UnregisterFromStartAtLogin(ctx context.Context, inst *limatype.Instance) error {
return manager().UnregisterFromStartAtLogin(ctx, inst)
}

// GetFilePath returns the path to autostart file with respect of host.
func GetFilePath(hostOS, instName string) string {
var fileTmpl string
if hostOS == "darwin" { // launchd plist
fileTmpl = fmt.Sprintf("%s/Library/LaunchAgents/io.lima-vm.autostart.%s.plist", os.Getenv("HOME"), instName)
}
if hostOS == "linux" { // systemd service
// Use instance name as argument to systemd service
// Instance name available in unit file as %i
xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
if xdgConfigHome == "" {
xdgConfigHome = filepath.Join(os.Getenv("HOME"), ".config")
}
fileTmpl = fmt.Sprintf("%s/systemd/user/lima-vm@%s.service", xdgConfigHome, instName)
}
return fileTmpl
// AutoStartedIdentifier returns the identifier if the current process was started by the autostart manager.
func AutoStartedIdentifier() string {
return manager().AutoStartedIdentifier()
}

func enableDisableService(ctx context.Context, action, hostOS, serviceWithPath string) error {
// Get filename without extension
filename := strings.TrimSuffix(path.Base(serviceWithPath), filepath.Ext(path.Base(serviceWithPath)))
// RequestStart requests to start the instance by identifier.
func RequestStart(ctx context.Context, inst *limatype.Instance) error {
return manager().RequestStart(ctx, inst)
}

var args []string
if hostOS == "darwin" {
// man launchctl
args = append(args, []string{
"launchctl",
action,
fmt.Sprintf("gui/%s/%s", strconv.Itoa(os.Getuid()), filename),
}...)
} else {
args = append(args, []string{
"systemctl",
"--user",
action,
filename,
}...)
}
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
// RequestStop requests to stop the instance by identifier.
func RequestStop(ctx context.Context, inst *limatype.Instance) (bool, error) {
return manager().RequestStop(ctx, inst)
}

func renderTemplate(hostOS, instName, workDir string, getExecutable func() (string, error)) ([]byte, error) {
selfExeAbs, err := getExecutable()
if err != nil {
return nil, err
}
tmpToExecute := systemdTemplate
if hostOS == "darwin" {
tmpToExecute = launchdTemplate
}
return textutil.ExecuteTemplate(
tmpToExecute,
map[string]string{
"Binary": selfExeAbs,
"Instance": instName,
"WorkDir": workDir,
})
type autoStartManager interface {
// Registration
IsRegistered(ctx context.Context, inst *limatype.Instance) (bool, error)
RegisterToStartAtLogin(ctx context.Context, inst *limatype.Instance) error
UnregisterFromStartAtLogin(ctx context.Context, inst *limatype.Instance) error

// Status
AutoStartedIdentifier() string

// Operation
// RequestStart requests to start the instance by identifier.
RequestStart(ctx context.Context, inst *limatype.Instance) error
// RequestStop requests to stop the instance by identifier.
RequestStop(ctx context.Context, inst *limatype.Instance) (bool, error)
}

var manager = sync.OnceValue(Manager)
Loading
Loading