|
| 1 | +package limautil |
| 2 | + |
| 3 | +import ( |
| 4 | + "bytes" |
| 5 | + "fmt" |
| 6 | + "strings" |
| 7 | + |
| 8 | + "github.com/coreos/go-semver/semver" |
| 9 | + "github.com/sirupsen/logrus" |
| 10 | + |
| 11 | + "github.com/abiosoft/colima/config" |
| 12 | + "github.com/abiosoft/colima/config/configmanager" |
| 13 | +) |
| 14 | + |
| 15 | +// AutostartCondition is when an instance should be started automatically. |
| 16 | +type AutostartCondition string |
| 17 | + |
| 18 | +const ( |
| 19 | + // AutostartLogin starts the instance when the user logs in. |
| 20 | + AutostartLogin AutostartCondition = "login" |
| 21 | + // AutostartBoot starts the instance at system boot, before any user logs in. |
| 22 | + // macOS only, and requires privileges to write to /Library/LaunchDaemons. |
| 23 | + AutostartBoot AutostartCondition = "boot" |
| 24 | +) |
| 25 | + |
| 26 | +// minAutostartVersion is the minimum Lima version usable for autostart. |
| 27 | +// |
| 28 | +// A Colima instance lives under ~/.colima/_lima rather than the default ~/.lima, so the |
| 29 | +// generated launchd/systemd unit has to carry LIMA_HOME. That was added in Lima v2.3.0 |
| 30 | +// (lima-vm/lima#5489); before it, the unit resolved the default directory at boot and |
| 31 | +// never found the instance. |
| 32 | +var minAutostartVersion = *semver.New("2.3.0") |
| 33 | + |
| 34 | +// parseVersion extracts the version from the output of `limactl --version`, |
| 35 | +// which is of the form "limactl version 2.3.0". |
| 36 | +func parseVersion(output string) (*semver.Version, error) { |
| 37 | + fields := strings.Fields(output) |
| 38 | + if len(fields) == 0 { |
| 39 | + return nil, fmt.Errorf("unexpected output from `%s --version`: %q", LimactlCommand, output) |
| 40 | + } |
| 41 | + return semver.NewVersion(strings.TrimPrefix(fields[len(fields)-1], "v")) |
| 42 | +} |
| 43 | + |
| 44 | +// Version returns the version of the limactl binary. |
| 45 | +func Version() (*semver.Version, error) { |
| 46 | + var buf bytes.Buffer |
| 47 | + cmd := Limactl("--version") |
| 48 | + cmd.Stdout = &buf |
| 49 | + cmd.Stderr = nil |
| 50 | + |
| 51 | + if err := cmd.Run(); err != nil { |
| 52 | + return nil, fmt.Errorf("error retrieving lima version: %w", err) |
| 53 | + } |
| 54 | + return parseVersion(buf.String()) |
| 55 | +} |
| 56 | + |
| 57 | +// checkAutostartSupported reports whether the installed Lima can autostart a Colima |
| 58 | +// instance. A development build is rejected, as its version sorts below the release. |
| 59 | +func checkAutostartSupported() error { |
| 60 | + version, err := Version() |
| 61 | + if err != nil { |
| 62 | + return err |
| 63 | + } |
| 64 | + if version.LessThan(minAutostartVersion) { |
| 65 | + return fmt.Errorf("autostart requires Lima v%s or newer, found v%s", minAutostartVersion, version) |
| 66 | + } |
| 67 | + return nil |
| 68 | +} |
| 69 | + |
| 70 | +// warnColimaProvision warns when the instance has provision scripts that Colima runs |
| 71 | +// itself. The generated unit invokes `limactl start`, so only the scripts Colima passes |
| 72 | +// through to Lima run on an automatic start. |
| 73 | +func warnColimaProvision() { |
| 74 | + conf, err := configmanager.LoadInstance() |
| 75 | + if err != nil { |
| 76 | + return // not fatal, the instance may not have been started yet |
| 77 | + } |
| 78 | + for _, p := range conf.Provision { |
| 79 | + if p.IsColimaMode() { |
| 80 | + logrus.Warnf("provision scripts with mode %q or %q do not run on an automatic start, "+ |
| 81 | + "as the instance is started by Lima rather than by Colima", |
| 82 | + config.ProvisionModeAfterBoot, config.ProvisionModeReady) |
| 83 | + return |
| 84 | + } |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +// EnableAutostart registers the instance of the current profile to start |
| 89 | +// automatically, on the given condition. |
| 90 | +func EnableAutostart(condition AutostartCondition) error { |
| 91 | + if err := checkAutostartSupported(); err != nil { |
| 92 | + return err |
| 93 | + } |
| 94 | + warnColimaProvision() |
| 95 | + cmd := Limactl("autostart", "enable", config.CurrentProfile().ID, "--condition", string(condition)) |
| 96 | + if err := cmd.Run(); err != nil { |
| 97 | + return fmt.Errorf("error enabling autostart: %w", err) |
| 98 | + } |
| 99 | + return nil |
| 100 | +} |
| 101 | + |
| 102 | +// DisableAutostart unregisters the instance of the current profile from |
| 103 | +// automatic startup. |
| 104 | +func DisableAutostart() error { |
| 105 | + cmd := Limactl("autostart", "disable", config.CurrentProfile().ID) |
| 106 | + if err := cmd.Run(); err != nil { |
| 107 | + return fmt.Errorf("error disabling autostart: %w", err) |
| 108 | + } |
| 109 | + return nil |
| 110 | +} |
0 commit comments