diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go index 7bd796360d..2e5db60ecf 100644 --- a/cmd/crc/cmd/start.go +++ b/cmd/crc/cmd/start.go @@ -88,6 +88,8 @@ func runStart(ctx context.Context) (*types.StartResult, error) { PersistentVolumeSize: config.Get(crcConfig.PersistentVolumeSize).AsInt(), EnableBundleQuayFallback: config.Get(crcConfig.EnableBundleQuayFallback).AsBool(), + + EnableRosetta: config.Get(crcConfig.UseRosetta).AsBool(), } client := newMachine() diff --git a/pkg/crc/config/settings.go b/pkg/crc/config/settings.go index 3d8a96c291..2e261e87e4 100644 --- a/pkg/crc/config/settings.go +++ b/pkg/crc/config/settings.go @@ -38,6 +38,7 @@ const ( EmergencyLogin = "enable-emergency-login" PersistentVolumeSize = "persistent-volume-size" EnableBundleQuayFallback = "enable-bundle-quay-fallback" + UseRosetta = "use-rosetta" ) func RegisterSettings(cfg *Config) { @@ -127,6 +128,9 @@ func RegisterSettings(cfg *Config) { cfg.AddSetting(EnableBundleQuayFallback, false, ValidateBool, SuccessfullyApplied, "If bundle download from the default location fails, fallback to quay.io (true/false, default: false)") + cfg.AddSetting(UseRosetta, false, validateRosetta, RequiresDeleteAndSetupMsg, + "Use Rosetta to run x86_64/amd64 containers on Apple Silicon (true/false, default: false)") + if err := cfg.RegisterNotifier(Preset, presetChanged); err != nil { logging.Debugf("Failed to register notifier for Preset: %v", err) } diff --git a/pkg/crc/config/validations.go b/pkg/crc/config/validations.go index 5f938e4fe2..8d76bf418d 100644 --- a/pkg/crc/config/validations.go +++ b/pkg/crc/config/validations.go @@ -154,6 +154,14 @@ func validatePreset(value interface{}) (bool, string) { return true, "" } +// validateRosetta checks if Rosetta can be enabled on this platform +func validateRosetta(value interface{}) (bool, string) { + if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" { + return false, "Rosetta is only supported on Apple Silicon Macs" + } + return ValidateBool(value) +} + func validatePort(value interface{}) (bool, string) { port, err := cast.ToUintE(value) if err != nil { diff --git a/pkg/crc/machine/config/config.go b/pkg/crc/machine/config/config.go index 682b814e09..610e2ecdbb 100644 --- a/pkg/crc/machine/config/config.go +++ b/pkg/crc/machine/config/config.go @@ -26,4 +26,7 @@ type MachineConfig struct { // Experimental features NetworkMode network.Mode + + // Rosetta for x86_64 emulation on Apple Silicon + EnableRosetta bool } diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go index 226db0fc48..6f3c72bae1 100644 --- a/pkg/crc/machine/start.go +++ b/pkg/crc/machine/start.go @@ -267,6 +267,63 @@ func configureSharedDirs(vm *virtualMachine, sshRunner *crcssh.Runner) error { return nil } +func configureRosetta(sshRunner *crcssh.Runner) error { + logging.Infof("Configuring Rosetta for x86_64 emulation") + + // Create mount point and mount the Rosetta virtiofs share + if _, _, err := sshRunner.RunPrivileged("Creating Rosetta mount point", + "mkdir", "-p", "/media/rosetta"); err != nil { + return err + } + if _, _, err := sshRunner.RunPrivileged("Mounting Rosetta share", + "mount", "-t", "virtiofs", + "-o", `context="system_u:object_r:bin_t:s0"`, + "rosetta", "/media/rosetta"); err != nil { + return err + } + + // Bind mount the Rosetta binary to a local path with shared propagation. + // This avoids virtiofs dropping file connections across namespace boundaries + // (e.g. buildah unshare), while preserving the virtiofs transport Rosetta requires. + if _, _, err := sshRunner.RunPrivileged("Making Rosetta mount shared", + "mount", "--make-shared", "/media/rosetta"); err != nil { + return err + } + if _, _, err := sshRunner.RunPrivileged("Creating local Rosetta directory", + "mkdir", "-p", "/var/lib/rosetta"); err != nil { + return err + } + if _, _, err := sshRunner.RunPrivileged("Creating bind mount target", + "touch", "/var/lib/rosetta/rosetta"); err != nil { + return err + } + if _, _, err := sshRunner.RunPrivileged("Bind mounting Rosetta binary to local path", + "mount", "--bind", "/media/rosetta/rosetta", "/var/lib/rosetta/rosetta"); err != nil { + return err + } + + // Mask QEMU x86_64 binfmt config so systemd-binfmt won't register it + if _, _, err := sshRunner.RunPrivileged("Masking QEMU x86_64 binfmt config", + "ln", "-sf", "/dev/null", "/etc/binfmt.d/qemu-x86_64-static.conf"); err != nil { + return err + } + + // Write Rosetta binfmt config pointing to the bind mount path + rosettaBinfmt := `:rosetta:M::\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00:\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/var/lib/rosetta/rosetta:CFP` + if _, _, err := sshRunner.RunPrivileged("Writing Rosetta binfmt config", + fmt.Sprintf("tee /etc/binfmt.d/rosetta.conf <<< '%s'", rosettaBinfmt)); err != nil { + return err + } + + // Restart systemd-binfmt to apply the new handler + if _, _, err := sshRunner.RunPrivileged("Restarting systemd-binfmt", + "systemctl", "restart", "systemd-binfmt"); err != nil { + return err + } + + return nil +} + func (client *client) Start(ctx context.Context, startConfig types.StartConfig) (*types.StartResult, error) { telemetry.SetCPUs(ctx, startConfig.CPUs) telemetry.SetMemory(ctx, uint64(startConfig.Memory.ToBytes())) @@ -331,6 +388,7 @@ func (client *client) Start(ctx context.Context, startConfig types.StartConfig) SharedDirs: sharedDirs, SharedDirPassword: startConfig.SharedDirPassword, SharedDirUsername: startConfig.SharedDirUsername, + EnableRosetta: startConfig.EnableRosetta, } if crcBundleMetadata.IsOpenShift() { machineConfig.KubeConfig = crcBundleMetadata.GetKubeConfigPath() @@ -469,6 +527,11 @@ func (client *client) Start(ctx context.Context, startConfig types.StartConfig) return nil, err } } + if startConfig.EnableRosetta { + if err := configureRosetta(sshRunner); err != nil { + return nil, errors.Wrap(err, "Failed to configure Rosetta") + } + } if _, _, err := sshRunner.RunPrivileged("make root Podman socket accessible", "chmod 777 /run/podman/ /run/podman/podman.sock"); err != nil { return nil, errors.Wrap(err, "Failed to change permissions to root podman socket") diff --git a/pkg/crc/machine/types/types.go b/pkg/crc/machine/types/types.go index 08e67527d9..2240728728 100644 --- a/pkg/crc/machine/types/types.go +++ b/pkg/crc/machine/types/types.go @@ -49,6 +49,9 @@ type StartConfig struct { // Enable bundle quay fallback EnableBundleQuayFallback bool + + // Enable Rosetta for x86_64 emulation on Apple Silicon + EnableRosetta bool } type ClusterConfig struct { diff --git a/pkg/crc/machine/vfkit/driver_darwin.go b/pkg/crc/machine/vfkit/driver_darwin.go index 882f8ccaf1..6c256c4d4e 100644 --- a/pkg/crc/machine/vfkit/driver_darwin.go +++ b/pkg/crc/machine/vfkit/driver_darwin.go @@ -26,6 +26,8 @@ func CreateHost(machineConfig config.MachineConfig) *vfkit.Driver { vfDriver.SharedDirs = configureShareDirs(machineConfig) + vfDriver.Rosetta = machineConfig.EnableRosetta + return vfDriver } diff --git a/pkg/crc/preflight/preflight.go b/pkg/crc/preflight/preflight.go index fb729e9dd8..de020650fd 100644 --- a/pkg/crc/preflight/preflight.go +++ b/pkg/crc/preflight/preflight.go @@ -157,8 +157,9 @@ func getPreflightChecksHelper(config crcConfig.Storage) []Check { bundlePath := config.Get(crcConfig.Bundle).AsString() preset := crcConfig.GetPreset(config) enableBundleQuayFallback := config.Get(crcConfig.EnableBundleQuayFallback).AsBool() + enableRosetta := config.Get(crcConfig.UseRosetta).AsBool() logging.Infof("Using bundle path %s", bundlePath) - return getPreflightChecks(experimentalFeatures, mode, bundlePath, preset, enableBundleQuayFallback) + return getPreflightChecks(experimentalFeatures, mode, bundlePath, preset, enableBundleQuayFallback, enableRosetta) } // StartPreflightChecks performs the preflight checks before starting the cluster diff --git a/pkg/crc/preflight/preflight_checks_darwin.go b/pkg/crc/preflight/preflight_checks_darwin.go index 284b96d895..0af41316f5 100644 --- a/pkg/crc/preflight/preflight_checks_darwin.go +++ b/pkg/crc/preflight/preflight_checks_darwin.go @@ -226,6 +226,15 @@ func fixPlistFileExists(agentConfig launchd.AgentConfig) error { return waitForDaemonRunning() } +const rosettaPath = "/Library/Apple/usr/libexec/oah/libRosettaRuntime" + +func checkRosettaInstalled() error { + if _, err := os.Stat(rosettaPath); err != nil { + return fmt.Errorf("Rosetta is not installed, which is required for x86_64 emulation") + } + return nil +} + func deprecationNotice() error { supports, err := darwin.AtLeast("13.0.0") if err != nil { diff --git a/pkg/crc/preflight/preflight_darwin.go b/pkg/crc/preflight/preflight_darwin.go index efc3e63abc..372cbf1bb2 100644 --- a/pkg/crc/preflight/preflight_darwin.go +++ b/pkg/crc/preflight/preflight_darwin.go @@ -75,6 +75,16 @@ var trayLaunchdCleanupChecks = []Check{ labels: labels{Os: Darwin}, }, } +var rosettaPreflightCheck = Check{ + configKeySuffix: "check-rosetta-installed", + checkDescription: "Checking if Rosetta is installed", + check: checkRosettaInstalled, + fixDescription: "Rosetta is required for x86_64 emulation, install it with: softwareupdate --install-rosetta", + flags: NoFix, + + labels: labels{Os: Darwin}, +} + var resolverPreflightChecks = []Check{ { configKeySuffix: "check-resolver-file-permissions", @@ -109,10 +119,10 @@ var daemonLaunchdChecks = []Check{ // Passing 'SystemNetworkingMode' to getPreflightChecks currently achieves this // as there are no user networking specific checks func getAllPreflightChecks() []Check { - return getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(crcpreset.OpenShift), crcpreset.OpenShift, false) + return getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(crcpreset.OpenShift), crcpreset.OpenShift, false, false) } -func getChecks(_ network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool) []Check { +func getChecks(_ network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool, enableRosetta bool) []Check { checks := []Check{} checks = append(checks, deprecationWarning) @@ -121,6 +131,9 @@ func getChecks(_ network.Mode, bundlePath string, preset crcpreset.Preset, enabl checks = append(checks, memoryCheck(preset)) checks = append(checks, genericCleanupChecks...) checks = append(checks, vfkitPreflightChecks...) + if enableRosetta { + checks = append(checks, rosettaPreflightCheck) + } checks = append(checks, resolverPreflightChecks...) checks = append(checks, bundleCheck(bundlePath, preset, enableBundleQuayFallback)) checks = append(checks, trayLaunchdCleanupChecks...) @@ -130,9 +143,9 @@ func getChecks(_ network.Mode, bundlePath string, preset crcpreset.Preset, enabl return checks } -func getPreflightChecks(_ bool, mode network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool) []Check { +func getPreflightChecks(_ bool, mode network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool, enableRosetta bool) []Check { filter := newFilter() filter.SetNetworkMode(mode) - return filter.Apply(getChecks(mode, bundlePath, preset, enableBundleQuayFallback)) + return filter.Apply(getChecks(mode, bundlePath, preset, enableBundleQuayFallback, enableRosetta)) } diff --git a/pkg/crc/preflight/preflight_darwin_test.go b/pkg/crc/preflight/preflight_darwin_test.go index d696176469..e068d0f628 100644 --- a/pkg/crc/preflight/preflight_darwin_test.go +++ b/pkg/crc/preflight/preflight_darwin_test.go @@ -17,9 +17,13 @@ func TestCountConfigurationOptions(t *testing.T) { } func TestCountPreflights(t *testing.T) { - assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 20) - assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 20) + assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 20) + assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 20) - assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 19) - assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 19) + assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 19) + assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 19) + + // With Rosetta enabled, one additional preflight check is added + assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, true), 21) + assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, true), 20) } diff --git a/pkg/crc/preflight/preflight_linux.go b/pkg/crc/preflight/preflight_linux.go index fd89e6319a..02d2eef21a 100644 --- a/pkg/crc/preflight/preflight_linux.go +++ b/pkg/crc/preflight/preflight_linux.go @@ -345,7 +345,7 @@ func getAllPreflightChecks() []Check { return filter.Apply(getChecks(distro(), constants.GetDefaultBundlePath(crcpreset.OpenShift), crcpreset.OpenShift, false)) } -func getPreflightChecks(_ bool, networkMode network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool) []Check { +func getPreflightChecks(_ bool, networkMode network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool, _ bool) []Check { usingSystemdResolved := checkSystemdResolvedIsRunning() return getPreflightChecksForDistro(distro(), networkMode, usingSystemdResolved == nil, bundlePath, preset, enableBundleQuayFallback) diff --git a/pkg/crc/preflight/preflight_windows.go b/pkg/crc/preflight/preflight_windows.go index 50166169ec..e4a362cfa0 100644 --- a/pkg/crc/preflight/preflight_windows.go +++ b/pkg/crc/preflight/preflight_windows.go @@ -212,7 +212,7 @@ func checkVsock() error { // Passing 'UserNetworkingMode' to getPreflightChecks currently achieves this // as there are no system networking specific checks func getAllPreflightChecks() []Check { - return getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(crcpreset.OpenShift), crcpreset.OpenShift, false) + return getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(crcpreset.OpenShift), crcpreset.OpenShift, false, false) } func getChecks(bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool) []Check { @@ -231,7 +231,7 @@ func getChecks(bundlePath string, preset crcpreset.Preset, enableBundleQuayFallb return checks } -func getPreflightChecks(_ bool, networkMode network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool) []Check { +func getPreflightChecks(_ bool, networkMode network.Mode, bundlePath string, preset crcpreset.Preset, enableBundleQuayFallback bool, _ bool) []Check { filter := newFilter() filter.SetNetworkMode(networkMode) diff --git a/pkg/crc/preflight/preflight_windows_test.go b/pkg/crc/preflight/preflight_windows_test.go index b13e40e64b..9366e24c71 100644 --- a/pkg/crc/preflight/preflight_windows_test.go +++ b/pkg/crc/preflight/preflight_windows_test.go @@ -17,9 +17,9 @@ func TestCountConfigurationOptions(t *testing.T) { } func TestCountPreflights(t *testing.T) { - assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 22) - assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 22) + assert.Len(t, getPreflightChecks(false, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 22) + assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 22) - assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 23) - assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false), 23) + assert.Len(t, getPreflightChecks(false, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 23) + assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift, false, false), 23) } diff --git a/pkg/drivers/vfkit/driver_darwin.go b/pkg/drivers/vfkit/driver_darwin.go index c480fce0e2..05655103a8 100644 --- a/pkg/drivers/vfkit/driver_darwin.go +++ b/pkg/drivers/vfkit/driver_darwin.go @@ -43,6 +43,7 @@ type Driver struct { *drivers.VMDriver VfkitPath string VirtioNet bool + Rosetta bool VsockPath string DaemonVsockPort uint @@ -230,6 +231,17 @@ func (d *Driver) Start() error { } } + // rosetta + if d.Rosetta { + rosettaDev, err := config.RosettaShareNew("rosetta") + if err != nil { + return err + } + if err := vm.AddDevice(rosettaDev); err != nil { + return err + } + } + // entropy dev, err = config.VirtioRngNew() if err != nil {