diff --git a/flake.lock b/flake.lock index 9a0a6c012..49fc72764 100644 --- a/flake.lock +++ b/flake.lock @@ -17,12 +17,12 @@ ] }, "locked": { - "lastModified": 1727475782, - "narHash": "sha256-M9Z7OMrQHAmZQnuMYxdyqzV+7ApIXVbA2GXl62l1GTo=", - "rev": "cb916a7dd1b85d547edd6ba2f782a578ca4ef480", - "revCount": 110, + "lastModified": 1727728378, + "narHash": "sha256-sSGQJP7isahkRAzlOiLJjvoz/MijCsoFa6FgQIqbcFE=", + "rev": "ec5f982bd53acbece1c3a72a0dbf074ab5d79e10", + "revCount": 136, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/determinate/0.1.110%2Brev-cb916a7dd1b85d547edd6ba2f782a578ca4ef480/01923596-e372-7668-a456-5b32177e0dda/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/DeterminateSystems/determinate/0.1.136%2Brev-ec5f982bd53acbece1c3a72a0dbf074ab5d79e10/019244a6-0aa7-72b5-9d85-a7bb7885aad3/source.tar.gz" }, "original": { "type": "tarball", @@ -32,37 +32,37 @@ "determinate-nixd-aarch64-darwin": { "flake": false, "locked": { - "narHash": "sha256-tmW+Sqn9cautArLTych0mnKXD1abtaAuJGCUCrtUmeo=", + "narHash": "sha256-OhG8joS/uN3Kdw4h9w8F/6ZIVTFZ8J9Fb4NGn/KK5/s=", "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/rev/2c18a8f38492d35be64d4e497b720938f17cc9f5/macOS" + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/macOS" }, "original": { "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/rev/2c18a8f38492d35be64d4e497b720938f17cc9f5/macOS" + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/macOS" } }, "determinate-nixd-aarch64-linux": { "flake": false, "locked": { - "narHash": "sha256-z5dg+qwLOjA4pjiCLReESa9qNYOtMxlaPXQQWNhEymA=", + "narHash": "sha256-AGcHQSIdb+KEJlhJzMB4YyFxbjdLZEDDf6bv6Zi3wqM=", "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/rev/2c18a8f38492d35be64d4e497b720938f17cc9f5/aarch64-linux" + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/aarch64-linux" }, "original": { "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/rev/2c18a8f38492d35be64d4e497b720938f17cc9f5/aarch64-linux" + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/aarch64-linux" } }, "determinate-nixd-x86_64-linux": { "flake": false, "locked": { - "narHash": "sha256-8sENexNuv7gsVAeQx1xuJd8IQtociheylIeEjFRYbQI=", + "narHash": "sha256-kU4dqHoYe3sFf4LDAUj4fyl9uGV8IHtE22+DdMeRN0s=", "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/rev/2c18a8f38492d35be64d4e497b720938f17cc9f5/x86_64-linux" + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/x86_64-linux" }, "original": { "type": "file", - "url": "https://install.determinate.systems/determinate-nixd/rev/2c18a8f38492d35be64d4e497b720938f17cc9f5/x86_64-linux" + "url": "https://install.determinate.systems/determinate-nixd/rev/51ecec5a3148baef87c2015536aa12dd18e4c4ad/x86_64-linux" } }, "fenix": { @@ -314,12 +314,12 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1727348695, - "narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=", - "rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784", - "revCount": 685764, + "lastModified": 1727634051, + "narHash": "sha256-S5kVU7U82LfpEukbn/ihcyNt2+EvG7Z5unsKW9H/yFA=", + "rev": "06cf0e1da4208d3766d898b7fdab6513366d45b9", + "revCount": 687049, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.685764%2Brev-1925c603f17fc89f4c8f6bf6f631a802ad85d784/01923479-4bef-7480-a7b0-72f6d33a5318/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.687049%2Brev-06cf0e1da4208d3766d898b7fdab6513366d45b9/019243b7-0a9f-79f7-b57a-4e0cfd13a578/source.tar.gz" }, "original": { "type": "tarball", diff --git a/src/action/common/configure_init_service.rs b/src/action/common/configure_init_service.rs index 1924b2fd7..7daba3d07 100644 --- a/src/action/common/configure_init_service.rs +++ b/src/action/common/configure_init_service.rs @@ -275,7 +275,7 @@ impl Action for ConfigureInitService { })?; } - crate::action::macos::retry_bootstrap(&domain, &service, &service_dest) + crate::action::macos::retry_bootstrap(domain, service, service_dest) .await .map_err(Self::error)?; diff --git a/src/action/macos/bootstrap_launchctl_service.rs b/src/action/macos/bootstrap_launchctl_service.rs index 01602da4d..8882b3a21 100644 --- a/src/action/macos/bootstrap_launchctl_service.rs +++ b/src/action/macos/bootstrap_launchctl_service.rs @@ -118,7 +118,7 @@ impl Action for BootstrapLaunchctlService { } if !*is_present { - crate::action::macos::retry_bootstrap(DARWIN_LAUNCHD_DOMAIN, &service, &path) + crate::action::macos::retry_bootstrap(DARWIN_LAUNCHD_DOMAIN, service, path) .await .map_err(Self::error)?; } diff --git a/src/action/macos/create_determinate_nix_volume.rs b/src/action/macos/create_determinate_nix_volume.rs index f2db05dc7..6f20f39bb 100644 --- a/src/action/macos/create_determinate_nix_volume.rs +++ b/src/action/macos/create_determinate_nix_volume.rs @@ -31,6 +31,7 @@ pub struct CreateDeterminateNixVolume { disk: PathBuf, name: String, case_sensitive: bool, + use_ec2_instance_store: bool, create_directory: StatefulAction, create_or_append_synthetic_conf: StatefulAction, create_synthetic_objects: StatefulAction, @@ -51,6 +52,7 @@ impl CreateDeterminateNixVolume { name: String, case_sensitive: bool, force: bool, + use_ec2_instance_store: bool, ) -> Result, ActionError> { let disk = disk.as_ref(); let create_or_append_synthetic_conf = CreateOrInsertIntoFile::plan( @@ -87,6 +89,7 @@ impl CreateDeterminateNixVolume { let setup_volume_daemon = CreateDeterminateVolumeService::plan( VOLUME_MOUNT_SERVICE_DEST, VOLUME_MOUNT_SERVICE_NAME, + use_ec2_instance_store, ) .await .map_err(Self::error)?; @@ -106,6 +109,7 @@ impl CreateDeterminateNixVolume { disk: disk.to_path_buf(), name, case_sensitive, + use_ec2_instance_store, create_directory, create_or_append_synthetic_conf, create_synthetic_objects, @@ -219,7 +223,7 @@ impl Action for CreateDeterminateNixVolume { .map_err(Self::error)?; let mut command = Command::new("/usr/local/bin/determinate-nixd"); - command.args(["--stop-after", "mount", "daemon"]); + command.args(["init", "--stop-after", "mount"]); command.stderr(std::process::Stdio::piped()); command.stdout(std::process::Stdio::piped()); tracing::trace!(command = ?command.as_std(), "Mounting /nix"); diff --git a/src/action/macos/create_determinate_volume_service.rs b/src/action/macos/create_determinate_volume_service.rs index 7b4cf2648..15b76028f 100644 --- a/src/action/macos/create_determinate_volume_service.rs +++ b/src/action/macos/create_determinate_volume_service.rs @@ -22,6 +22,7 @@ pub struct CreateDeterminateVolumeService { path: PathBuf, mount_service_label: String, needs_bootout: bool, + use_ec2_instance_store: bool, } impl CreateDeterminateVolumeService { @@ -29,12 +30,14 @@ impl CreateDeterminateVolumeService { pub async fn plan( path: impl AsRef, mount_service_label: impl Into, + use_ec2_instance_store: bool, ) -> Result, ActionError> { let path = path.as_ref().to_path_buf(); let mount_service_label = mount_service_label.into(); let mut this = Self { path, mount_service_label, + use_ec2_instance_store, needs_bootout: false, }; @@ -67,9 +70,10 @@ impl CreateDeterminateVolumeService { let discovered_plist: LaunchctlMountPlist = plist::from_file(&this.path).map_err(Self::error)?; - let expected_plist = generate_mount_plist(&this.mount_service_label) - .await - .map_err(Self::error)?; + let expected_plist = + generate_mount_plist(&this.mount_service_label, use_ec2_instance_store) + .await + .map_err(Self::error)?; if discovered_plist != expected_plist { tracing::trace!( ?discovered_plist, @@ -131,15 +135,16 @@ impl Action for CreateDeterminateVolumeService { path, mount_service_label, needs_bootout, + use_ec2_instance_store, } = self; if *needs_bootout { - crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, &mount_service_label, &path) + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, mount_service_label, path) .await .map_err(Self::error)?; } - let generated_plist = generate_mount_plist(mount_service_label) + let generated_plist = generate_mount_plist(mount_service_label, *use_ec2_instance_store) .await .map_err(Self::error)?; @@ -180,18 +185,18 @@ impl Action for CreateDeterminateVolumeService { /// This function must be able to operate at both plan and execute time. async fn generate_mount_plist( mount_service_label: &str, + use_ec2_instance_store: bool, ) -> Result { + let mut arguments = vec!["/usr/local/bin/determinate-nixd".into(), "init".into()]; + if use_ec2_instance_store { + arguments.push("--keep-mounted".into()); + } let mount_plist = LaunchctlMountPlist { run_at_load: true, label: mount_service_label.into(), - program_arguments: vec![ - "/usr/local/bin/determinate-nixd".into(), - "--stop-after".into(), - "mount".into(), - "daemon".into(), - ], - standard_out_path: "/var/log/determinate-nixd-mount.log".into(), - standard_error_path: "/var/log/determinate-nixd-mount.log".into(), + program_arguments: arguments, + standard_out_path: "/var/log/determinate-nix-init.log".into(), + standard_error_path: "/var/log/determinate-nix-init.log".into(), }; Ok(mount_plist) diff --git a/src/action/macos/create_nix_hook_service.rs b/src/action/macos/create_nix_hook_service.rs index 46a472a0b..3a8a1a646 100644 --- a/src/action/macos/create_nix_hook_service.rs +++ b/src/action/macos/create_nix_hook_service.rs @@ -127,7 +127,7 @@ impl Action for CreateNixHookService { } = self; if *needs_bootout { - crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, &service_label, &path) + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, service_label, path) .await .map_err(Self::error)?; } diff --git a/src/action/macos/create_volume_service.rs b/src/action/macos/create_volume_service.rs index 37080abca..e4c97681f 100644 --- a/src/action/macos/create_volume_service.rs +++ b/src/action/macos/create_volume_service.rs @@ -186,7 +186,7 @@ impl Action for CreateVolumeService { } = self; if *needs_bootout { - crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, &mount_service_label, &path) + crate::action::macos::retry_bootout(DARWIN_LAUNCHD_DOMAIN, mount_service_label, path) .await .map_err(Self::error)?; } diff --git a/src/os/darwin/diskutil.rs b/src/os/darwin/diskutil.rs index e65a15c77..0d48e7383 100644 --- a/src/os/darwin/diskutil.rs +++ b/src/os/darwin/diskutil.rs @@ -36,3 +36,19 @@ pub struct DiskUtilApfsListVolume { pub name: Option, pub encryption: bool, } + +#[derive(serde::Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct DiskUtilList { + pub all_disks_and_partitions: Vec, +} + +#[derive(serde::Deserialize, Clone, Debug)] +#[serde(rename_all = "PascalCase")] +pub struct DiskUtilListDisk { + #[serde(rename = "OSInternal")] + pub os_internal: bool, + pub device_identifier: String, + #[serde(rename = "Size")] + pub size_bytes: u64, +} diff --git a/src/planner/macos/mod.rs b/src/planner/macos/mod.rs index dc3530e9c..788b8b503 100644 --- a/src/planner/macos/mod.rs +++ b/src/planner/macos/mod.rs @@ -11,6 +11,8 @@ use crate::planner::HasExpectedErrors; mod profile_queries; mod profiles; +use crate::action::common::ConfigureDeterminateNixdInitService; +use crate::os::darwin::diskutil::DiskUtilList; use crate::{ action::{ base::RemoveDirectory, @@ -32,8 +34,6 @@ use crate::{ Action, BuiltinPlanner, }; -use crate::action::common::ConfigureDeterminateNixdInitService; - /// A planner for MacOS (Darwin) systems #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "cli", derive(clap::Parser))] @@ -72,6 +72,24 @@ pub struct Macos { /// The root disk of the target #[cfg_attr(feature = "cli", clap(long, env = "NIX_INSTALLER_ROOT_DISK"))] pub root_disk: Option, + + /// On AWS, put the Nix Store volume on the EC2 instances' instance store volume. + /// + /// WARNING: Using the instance store volume means the machine must never be Stopped in AWS. + /// If the instance is Stopped, the instance store volume is erased, and the installation is broken. + /// The machine can be safely rebooted. + /// + /// Using the instance store volume bypasses the interactive "enable full disk access" step. + /// Without this flag, installations on macOS on EC2 will require manual, graphical intervention when first installed to grant Full Disk Access. + /// + /// Setting this option: + /// * Requires passing --determinate due to complications of AWS's deployment of macOS. + /// * Sets --root-disk to an auto-detected disk + #[cfg_attr( + feature = "cli", + clap(long, default_value = "false", requires = "determinate_nix") + )] + pub use_ec2_instance_store: bool, } async fn default_root_disk() -> Result { @@ -81,19 +99,42 @@ async fn default_root_disk() -> Result { .stdin(std::process::Stdio::null()), ) .await - .unwrap() + .map_err(|e| PlannerError::Custom(Box::new(e)))? .stdout; let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf))?; Ok(the_plist.parent_whole_disk) } +async fn default_internal_root_disk() -> Result, PlannerError> { + let buf = execute_command( + Command::new("/usr/sbin/diskutil") + .args(["list", "-plist", "internal", "virtual"]) + .stdin(std::process::Stdio::null()), + ) + .await + .map_err(|e| PlannerError::Custom(Box::new(e)))? + .stdout; + let the_plist: DiskUtilList = plist::from_reader(Cursor::new(buf))?; + + let mut disks = the_plist + .all_disks_and_partitions + .into_iter() + .filter(|disk| !disk.os_internal) + .collect::>(); + + disks.sort_by_key(|d| d.size_bytes); + + Ok(disks.pop().map(|d| d.device_identifier)) +} + #[async_trait::async_trait] #[typetag::serde(name = "macos")] impl Planner for Macos { async fn default() -> Result { Ok(Self { settings: CommonSettings::default().await?, + use_ec2_instance_store: false, root_disk: Some(default_root_disk().await?), case_sensitive: false, encrypt: None, @@ -102,20 +143,18 @@ impl Planner for Macos { } async fn plan(&self) -> Result>>, PlannerError> { + if self.use_ec2_instance_store && !self.settings.determinate_nix { + return Err(PlannerError::Ec2InstanceStoreRequiresDeterminateNix); + } + let root_disk = match &self.root_disk { root_disk @ Some(_) => root_disk.clone(), None => { - let buf = execute_command( - Command::new("/usr/sbin/diskutil") - .args(["info", "-plist", "/"]) - .stdin(std::process::Stdio::null()), - ) - .await - .unwrap() - .stdout; - let the_plist: DiskUtilInfoOutput = plist::from_reader(Cursor::new(buf)).unwrap(); - - Some(the_plist.parent_whole_disk) + if self.use_ec2_instance_store { + default_internal_root_disk().await? + } else { + Some(default_root_disk().await?) + } }, }; @@ -160,6 +199,7 @@ impl Planner for Macos { self.volume_label.clone(), self.case_sensitive, self.settings.force, + self.use_ec2_instance_store, ) .await .map_err(PlannerError::Action)? @@ -257,6 +297,7 @@ impl Planner for Macos { volume_label, case_sensitive, root_disk, + use_ec2_instance_store, } = self; let mut map = HashMap::default(); @@ -264,6 +305,10 @@ impl Planner for Macos { map.insert("volume_encrypt".into(), serde_json::to_value(encrypt)?); map.insert("volume_label".into(), serde_json::to_value(volume_label)?); map.insert("root_disk".into(), serde_json::to_value(root_disk)?); + map.insert( + "use_ec2_instance_store".into(), + serde_json::to_value(use_ec2_instance_store)?, + ); map.insert( "case_sensitive".into(), serde_json::to_value(case_sensitive)?, diff --git a/src/planner/mod.rs b/src/planner/mod.rs index fb9ac1b62..7b8f48697 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -397,6 +397,8 @@ pub enum PlannerError { RosettaDetected, #[error("Determinate Nix is not available. See: https://determinate.systems/enterprise")] DeterminateNixUnavailable, + #[error("Running Nix on the EC2 instance store requires Determinate Nix to be enabled")] + Ec2InstanceStoreRequiresDeterminateNix, /// A Linux SELinux related error #[error("Unable to install on an SELinux system without common SELinux tooling, the binaries `restorecon`, and `semodule` are required")] SelinuxRequirements, @@ -431,6 +433,7 @@ impl HasExpectedErrors for PlannerError { this @ PlannerError::IncompatibleOperatingSystem { .. } => Some(Box::new(this)), this @ PlannerError::RosettaDetected => Some(Box::new(this)), this @ PlannerError::DeterminateNixUnavailable => Some(Box::new(this)), + this @ PlannerError::Ec2InstanceStoreRequiresDeterminateNix => Some(Box::new(this)), PlannerError::OsRelease(_) => None, PlannerError::Utf8(_) => None, PlannerError::SelinuxRequirements => Some(Box::new(self)), diff --git a/tests/fixtures/macos/macos.json b/tests/fixtures/macos/macos.json index c106bf2ad..7d4591e8b 100644 --- a/tests/fixtures/macos/macos.json +++ b/tests/fixtures/macos/macos.json @@ -471,7 +471,8 @@ "encrypt": null, "case_sensitive": false, "volume_label": "Nix Store", - "root_disk": "disk3" + "root_disk": "disk3", + "use_ec2_instance_store": false }, "diagnostic_data": { "version": "0.19.0",