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
99 changes: 77 additions & 22 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/abiosoft/colima/environment/host"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/abiosoft/colima/environment/vm/native"
"github.com/abiosoft/colima/store"
"github.com/abiosoft/colima/util"
"github.com/docker/go-units"
Expand All @@ -42,11 +43,32 @@ type App interface {

var _ App = (*colimaApp)(nil)

// New creates a new app.
// New creates a new app using the saved instance's VM type.
func New() (App, error) {
guest := lima.New(host.New())
if err := host.IsInstalled(guest); err != nil {
return nil, fmt.Errorf("dependency check failed for VM: %w", err)
return NewWithVMType("")
}

// NewWithVMType creates a new app with the specified VM type.
// If vmType is empty, it loads from saved instance state or uses the default.
func NewWithVMType(vmType string) (App, error) {
h := host.New()

if vmType == "" {
if conf, err := configmanager.LoadInstance(); err == nil && conf.VMType != "" {
vmType = conf.VMType
} else {
vmType = environment.DefaultVMType()
}
}

var guest environment.VM
if vmType == "native" && util.Linux() {
guest = native.New(h)
} else {
guest = lima.New(h)
if err := host.IsInstalled(guest); err != nil {
return nil, fmt.Errorf("dependency check failed for VM: %w", err)
}
}

return &colimaApp{
Expand Down Expand Up @@ -281,9 +303,12 @@ func (c colimaApp) Delete(data, force bool) error {

// delete runtime disk if disk in use and data deletion is requested
if diskInUse && data {
log.Println("deleting container data")
if err := limautil.DeleteDisk(); err != nil {
return fmt.Errorf("error deleting container data: %w", err)
conf, _ := configmanager.LoadInstance()
if conf.VMType != "native" {
log.Println("deleting container data")
if err := limautil.DeleteDisk(); err != nil {
return fmt.Errorf("error deleting container data: %w", err)
}
}

if err := store.Reset(); err != nil {
Expand Down Expand Up @@ -342,8 +367,7 @@ func (c colimaApp) SSH(args ...string) error {
workDir = ""
}

guest := lima.New(host.New())
return guest.SSH(workDir, args...)
return c.guest.SSH(workDir, args...)
}

type statusInfo struct {
Expand Down Expand Up @@ -383,29 +407,49 @@ func (c colimaApp) getStatus() (status statusInfo, err error) {
status.Arch = string(c.guest.Arch())
status.Runtime = currentRuntime
status.MountType = conf.MountType
ipAddress := limautil.IPAddress(config.CurrentProfile().ID)
if ipAddress != "127.0.0.1" {
status.IPAddress = ipAddress
if conf.VMType == "native" {
status.IPAddress = native.HostIPAddress()
cpu, mem, disk := native.HostResources()
status.CPU = cpu
status.Memory = mem
status.Disk = disk
} else {
ipAddress := limautil.IPAddress(config.CurrentProfile().ID)
if ipAddress != "127.0.0.1" {
status.IPAddress = ipAddress
}
if inst, err := limautil.Instance(); err == nil {
status.CPU = inst.CPU
status.Memory = inst.Memory
status.Disk = inst.Disk
}
}
if currentRuntime == docker.Name {
status.DockerSocket = "unix://" + docker.HostSocketFile()
status.ContainerdSocket = "unix://" + containerd.HostSocketFiles().Containerd
if conf.VMType == "native" {
status.DockerSocket = "unix:///var/run/docker.sock"
} else {
status.DockerSocket = "unix://" + docker.HostSocketFile()
status.ContainerdSocket = "unix://" + containerd.HostSocketFiles().Containerd
}
}
if currentRuntime == containerd.Name {
status.ContainerdSocket = "unix://" + containerd.HostSocketFiles().Containerd
status.BuildkitdSocket = "unix://" + containerd.HostSocketFiles().Buildkitd
if conf.VMType == "native" {
status.ContainerdSocket = "unix:///run/containerd/containerd.sock"
} else {
status.ContainerdSocket = "unix://" + containerd.HostSocketFiles().Containerd
status.BuildkitdSocket = "unix://" + containerd.HostSocketFiles().Buildkitd
}
}
if currentRuntime == incus.Name {
status.IncusSocket = "unix://" + incus.HostSocketFile()
if conf.VMType == "native" {
status.IncusSocket = "unix:///var/lib/incus/unix.socket"
} else {
status.IncusSocket = "unix://" + incus.HostSocketFile()
}
}
if k, err := c.Kubernetes(); err == nil && k.Running(ctx) {
status.Kubernetes = true
}
if inst, err := limautil.Instance(); err == nil {
status.CPU = inst.CPU
status.Memory = inst.Memory
status.Disk = inst.Disk
}
return status, nil
}

Expand Down Expand Up @@ -507,6 +551,12 @@ func (c colimaApp) currentRuntime(ctx context.Context) (string, error) {

r := c.guest.Get(environment.ContainerRuntimeKey)
if r == "" {
// Fallback: read runtime from persisted config (needed for native mode
// where the Lima store mechanism is not available)
conf, err := configmanager.LoadInstance()
if err == nil && conf.Runtime != "" {
return conf.Runtime, nil
}
return "", fmt.Errorf("error retrieving current runtime: empty value")
}

Expand Down Expand Up @@ -625,6 +675,11 @@ func (c *colimaApp) Update() error {
}

func generateSSHConfig(modifySSHConfig bool) error {
// Skip SSH config generation for native mode (no VM to SSH into)
if conf, err := configmanager.LoadInstance(); err == nil && conf.VMType == "native" {
return nil
}

instances, err := limautil.Instances()
if err != nil {
return fmt.Errorf("error retrieving instances: %w", err)
Expand Down
21 changes: 17 additions & 4 deletions cmd/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/config/configmanager"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand All @@ -23,15 +24,22 @@ var cloneCmd = &cobra.Command{
to := config.ProfileFromName(args[1])

logrus.Infof("preparing to clone %s...", from.DisplayName)
{
// verify source profile exists

// Check if source is a native instance
isNative := false
if conf, err := configmanager.LoadFrom(from.StateFile()); err == nil && conf.VMType == "native" {
isNative = true
}

if !isNative {
// VM mode: verify source profile exists and clone VM files
if stat, err := os.Stat(from.LimaInstanceDir()); err != nil || !stat.IsDir() {
return fmt.Errorf("colima profile '%s' does not exist", from.ShortName)
}

// verify destination profile does not exists
// verify destination profile does not exist
if stat, err := os.Stat(to.LimaInstanceDir()); err == nil && stat.IsDir() {
return fmt.Errorf("colima profile '%s' already exists, delete with `colima delete %s` and try again", to.ShortName, to.ShortName)
return fmt.Errorf("colima profile '%s' already exists, delete with 'colima delete %s' and try again", to.ShortName, to.ShortName)
}

// copy source to destination
Expand All @@ -49,6 +57,11 @@ var cloneCmd = &cobra.Command{
).Run(); err != nil {
return fmt.Errorf("error copying VM: %w", err)
}
} else {
// Native mode: verify source config exists
if _, err := os.Stat(from.ConfigDir()); err != nil {
return fmt.Errorf("colima profile '%s' does not exist", from.ShortName)
}
}

{
Expand Down
3 changes: 2 additions & 1 deletion cmd/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ var pruneCmd = &cobra.Command{
}

if pruneCmdArgs.all {
// Lima prune is not applicable in native-only mode
cmd := limautil.Limactl("prune")
if err := cmd.Run(); err != nil {
return fmt.Errorf("error during Lima prune: %w", err)
logrus.Warnf("Lima prune skipped or failed: %v", err)
}
}

Expand Down
9 changes: 6 additions & 3 deletions cmd/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ The state of the VM is persisted at stop. A start afterwards
should return it back to its previous state.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// validate if the instance was previously created
if _, err := limautil.Instance(); err != nil {
return err
// validate if the instance was previously created (skip for native mode)
conf, _ := configmanager.LoadInstance()
if conf.VMType != "native" {
if _, err := limautil.Instance(); err != nil {
return err
}
}

app := newApp()
Expand Down
7 changes: 7 additions & 0 deletions cmd/ssh-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/abiosoft/colima/cmd/root"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/config/configmanager"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/spf13/cobra"
)
Expand All @@ -16,6 +17,12 @@ var sshConfigCmd = &cobra.Command{
Long: `Show configuration of the SSH connection to the VM.`,
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// SSH config is not applicable for native mode
if conf, err := configmanager.LoadInstance(); err == nil && conf.VMType == "native" {
fmt.Println("# SSH config not applicable for native mode (no VM)")
return nil
}

resp, err := limautil.ShowSSH(config.CurrentProfile().ID)
if err == nil {
fmt.Println(resp.Output)
Expand Down
18 changes: 14 additions & 4 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Run 'colima template' to set the default configurations or 'colima start --edit'
" colima start --kubernetes --k3s-arg='\"--disable=coredns,servicelb,traefik,local-storage,metrics-server\"'",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
app := newApp()
app := newAppWithVMType(startCmdArgs.VMType)
conf := startCmdArgs.Config

if !startCmdArgs.Flags.Edit {
Expand Down Expand Up @@ -87,9 +87,11 @@ Run 'colima template' to set the default configurations or 'colima start --edit'
return start(app, conf)
},
PreRunE: func(cmd *cobra.Command, args []string) error {
// validate Lima version
if err := core.LimaVersionSupported(); err != nil {
return fmt.Errorf("lima compatibility error: %w", err)
// validate Lima version (not needed for native mode)
if startCmdArgs.VMType != "native" {
if err := core.LimaVersionSupported(); err != nil {
return fmt.Errorf("lima compatibility error: %w", err)
}
}

// combine args and current config file(if any)
Expand Down Expand Up @@ -173,6 +175,9 @@ func init() {
if util.MacOS13OrNewerOnArm() {
vmTypes = append(vmTypes, "krunkit")
}
if util.Linux() {
vmTypes = append(vmTypes, "native")
}
types := strings.Join(vmTypes, ", ")

saveConfigDefault := true
Expand Down Expand Up @@ -229,6 +234,11 @@ func init() {
}
}

// vm type on Linux
if util.Linux() {
startCmd.Flags().StringVarP(&startCmdArgs.VMType, "vm-type", "t", defaultVMType, "virtual machine type ("+types+")")
}

// Gateway Address
startCmd.Flags().IPVar(&startCmdArgs.Network.GatewayAddress, "gateway-address", net.ParseIP("192.168.5.2"), "gateway address")

Expand Down
8 changes: 8 additions & 0 deletions cmd/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ func newApp() app.App {
return colimaApp
}

func newAppWithVMType(vmType string) app.App {
colimaApp, err := app.NewWithVMType(vmType)
if err != nil {
logrus.Fatal("Error: ", err)
}
return colimaApp
}

// waitForUserEdit launches a temporary file with content using editor,
// and waits for the user to close the editor.
// It returns the filename (if saved), empty file name (if aborted), and an error (if any).
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ func (c Config) AutoActivate() bool {
func (c Config) Empty() bool { return c.Runtime == "" } // this may be better but not really needed.

func (c Config) DriverLabel() string {
if c.VMType == "native" {
return "Native"
}
if util.MacOS13OrNewer() && c.VMType == "vz" {
return "macOS Virtualization.Framework"
} else if util.MacOS13OrNewerOnArm() && c.VMType == "krunkit" {
Expand Down
12 changes: 12 additions & 0 deletions config/configmanager/configmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ func LoadFrom(file string) (config.Config, error) {

// ValidateConfig validates config before we use it
func ValidateConfig(c config.Config) error {
// native mode: skip VM-specific validations entirely
if c.VMType == "native" {
if !util.Linux() {
return fmt.Errorf("vmType 'native' is only available on Linux")
}
// Native mode doesn't use mount types, disk images, or port forwarders
return nil
}

validMountTypes := map[string]bool{"9p": true, "sshfs": true}
validPortForwarders := map[string]bool{"grpc": true, "ssh": true, "none": true}

Expand All @@ -65,6 +74,9 @@ func ValidateConfig(c config.Config) error {
if util.MacOS13OrNewerOnArm() {
validVMTypes["krunkit"] = true
}
if util.Linux() {
validVMTypes["native"] = true
}
if c.VMType == "krunkit" && !util.MacOS13OrNewerOnArm() {
return fmt.Errorf("vmType 'krunkit' is only available on macOS with Apple Silicon")
}
Expand Down
9 changes: 8 additions & 1 deletion environment/container/docker/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"path/filepath"

"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/config/configmanager"
)

var configDir = func() string { return config.CurrentProfile().ConfigDir() }
Expand All @@ -25,9 +26,15 @@ func (d dockerRuntime) setupContext() error {

profile := config.CurrentProfile()

// In native mode, use the system Docker socket directly
socketPath := HostSocketFile()
if conf, err := configmanager.LoadInstance(); err == nil && conf.VMType == "native" {
socketPath = "/var/run/docker.sock"
}

return d.host.Run("docker", "context", "create", profile.ID,
"--description", profile.DisplayName,
"--docker", "host=unix://"+HostSocketFile(),
"--docker", "host=unix://"+socketPath,
)
}

Expand Down
6 changes: 5 additions & 1 deletion environment/container/docker/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ const hostGatewayIPKey = "host-gateway-ip"

func getHostGatewayIp(d dockerRuntime, conf map[string]any) (string, error) {
// get host-gateway ip from the guest
ip, err := d.guest.RunOutput("sh", "-c", "grep 'host.lima.internal' /etc/hosts | awk -F' ' '{print $1}'")
// Try Lima host entry first, fall back to default gateway for native mode
ip, err := d.guest.RunOutput("sh", "-c",
"grep 'host.lima.internal' /etc/hosts 2>/dev/null | awk -F' ' '{print $1}' || "+
"ip route show default 2>/dev/null | awk '{print $3}' || "+
"echo 127.0.0.1")
if err != nil {
return "", fmt.Errorf("error retrieving host gateway IP address: %w", err)
}
Expand Down
Loading