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
2 changes: 2 additions & 0 deletions cmd/crc/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
4 changes: 4 additions & 0 deletions pkg/crc/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/crc/config/validations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment on lines +159 to +162
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Allow explicit false for unsupported platforms.

Line 159 currently rejects the setting regardless of value on non-Apple-Silicon hosts. This prevents users from explicitly setting use-rosetta=false (useful for config portability/reset flows).

Suggested fix
 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)
+	enabled, err := cast.ToBoolE(value)
+	if err != nil {
+		return false, "must be true or false"
+	}
+	if !enabled {
+		return true, ""
+	}
+	if runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
+		return false, "Rosetta is only supported on Apple Silicon Macs"
+	}
+	return true, ""
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/config/validations.go` around lines 159 - 162, The current platform
check blocks any setting of the rosetta flag on non-Apple-Silicon hosts; instead
first validate the boolean using ValidateBool(value), and only block when the
validated value is true on unsupported platforms. Concretely: call
ValidateBool(value) (same symbol) to get the parsed boolean and message, if
parsing failed return that failure; if parsed value is false return success
(allow explicit false); if parsed value is true then check runtime.GOOS and
runtime.GOARCH and, if not darwin/arm64, return the existing error message
"Rosetta is only supported on Apple Silicon Macs", otherwise return success.

}

func validatePort(value interface{}) (bool, string) {
port, err := cast.ToUintE(value)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions pkg/crc/machine/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,7 @@ type MachineConfig struct {

// Experimental features
NetworkMode network.Mode

// Rosetta for x86_64 emulation on Apple Silicon
EnableRosetta bool
}
63 changes: 63 additions & 0 deletions pkg/crc/machine/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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")
Expand Down
3 changes: 3 additions & 0 deletions pkg/crc/machine/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions pkg/crc/machine/vfkit/driver_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func CreateHost(machineConfig config.MachineConfig) *vfkit.Driver {

vfDriver.SharedDirs = configureShareDirs(machineConfig)

vfDriver.Rosetta = machineConfig.EnableRosetta

return vfDriver
}

Expand Down
3 changes: 2 additions & 1 deletion pkg/crc/preflight/preflight.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions pkg/crc/preflight/preflight_checks_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,16 @@ 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")
}
Comment on lines +232 to +234
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle non-ENOENT errors separately in Rosetta preflight.

Line 232 currently treats every os.Stat failure as “not installed”. That can mask permission/I/O errors and mislead remediation.

Suggested fix
 func checkRosettaInstalled() error {
-	if _, err := os.Stat(rosettaPath); err != nil {
-		return fmt.Errorf("Rosetta is not installed, which is required for x86_64 emulation")
+	if _, err := os.Stat(rosettaPath); err != nil {
+		if os.IsNotExist(err) {
+			return fmt.Errorf("Rosetta is not installed, which is required for x86_64 emulation")
+		}
+		return fmt.Errorf("failed to verify Rosetta installation at %s: %w", rosettaPath, err)
 	}
 	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := os.Stat(rosettaPath); err != nil {
return fmt.Errorf("Rosetta is not installed, which is required for x86_64 emulation")
}
func checkRosettaInstalled() error {
if _, err := os.Stat(rosettaPath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("Rosetta is not installed, which is required for x86_64 emulation")
}
return fmt.Errorf("failed to verify Rosetta installation at %s: %w", rosettaPath, err)
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/crc/preflight/preflight_checks_darwin.go` around lines 232 - 234, The
os.Stat(rosettaPath) error is currently always reported as "Rosetta is not
installed", which hides other failures; change the handling so that if
os.IsNotExist(err) you return the existing "Rosetta is not installed" error,
otherwise return a distinct error that includes the underlying error (e.g.,
"failed to stat rosettaPath: <err>") so permission/I/O errors are preserved;
locate the os.Stat(rosettaPath) call in preflight_checks_darwin.go and update
the conditional to use os.IsNotExist(err) and return the wrapped/original error
for non-ENOENT cases.

return nil
}


func deprecationNotice() error {
supports, err := darwin.AtLeast("13.0.0")
if err != nil {
Expand Down
21 changes: 17 additions & 4 deletions pkg/crc/preflight/preflight_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand All @@ -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...)
Expand All @@ -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))
}
12 changes: 8 additions & 4 deletions pkg/crc/preflight/preflight_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion pkg/crc/preflight/preflight_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion pkg/crc/preflight/preflight_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 4 additions & 4 deletions pkg/crc/preflight/preflight_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
12 changes: 12 additions & 0 deletions pkg/drivers/vfkit/driver_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type Driver struct {
*drivers.VMDriver
VfkitPath string
VirtioNet bool
Rosetta bool

VsockPath string
DaemonVsockPort uint
Expand Down Expand Up @@ -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 {
Expand Down