Skip to content
Open
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
16 changes: 9 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,13 +49,16 @@ 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)
}
} else {
logrus.Infof("The autostart entry for instance %q is not registered", 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 @@ -155,9 +156,14 @@ func editAction(cmd *cobra.Command, args []string) error {
if !startNow {
return nil
}
err = networks.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 = networks.Reconcile(ctx, inst.Name)
if err != nil {
return err
}
}

// 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 = networks.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 = networks.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 {
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 = networks.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 = networks.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.5.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
3 changes: 3 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.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
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 Expand Up @@ -104,6 +106,7 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
Expand Down
150 changes: 50 additions & 100 deletions pkg/autostart/autostart.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,118 +6,68 @@ package autostart

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

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

//go:embed [email protected]
var systemdTemplate 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)
}

//go:embed io.lima-vm.autostart.INSTANCE.plist
var launchdTemplate string
// RegisterToStartAtLogin creates a start-at-login entry for the instance.
func RegisterToStartAtLogin(ctx context.Context, inst *limatype.Instance) error {
return manager().RegisterToStartAtLogin(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))
// UnregisterFromStartAtLogin deletes the start-at-login entry for the instance.
func UnregisterFromStartAtLogin(ctx context.Context, inst *limatype.Instance) error {
return manager().UnregisterFromStartAtLogin(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
// AutoStartedIdentifier returns the identifier if the current process was started by the autostart manager.
func AutoStartedIdentifier() string {
return manager().AutoStartedIdentifier()
}

// 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
// RequestStart requests to start the instance by identifier.
func RequestStart(ctx context.Context, inst *limatype.Instance) error {
return manager().RequestStart(ctx, inst)
}

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)))
// RequestStop requests to stop the instance by identifier.
func RequestStop(ctx context.Context, inst *limatype.Instance) (bool, error) {
return manager().RequestStop(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()
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)
}

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
var manager = sync.OnceValue(func() autoStartManager {
switch runtime.GOOS {
case "darwin":
return Launchd
case "linux":
if systemd.IsRunningSystemd() {
return Systemd
}
// TODO: support other init systems
}
return textutil.ExecuteTemplate(
tmpToExecute,
map[string]string{
"Binary": selfExeAbs,
"Instance": instName,
"WorkDir": workDir,
})
}
return &notSupportedManager{}
})
Loading
Loading