From 7c4f3206f9b51d31c2d313975d720227d9b76921 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Tue, 1 Nov 2022 11:33:54 -0700 Subject: [PATCH 01/10] Scaffold --- Cargo.lock | 39 +++++++++++- Cargo.toml | 1 + src/action/darwin/create_apfs_volume.rs | 12 ++-- src/action/darwin/encrypt_volume.rs | 81 ++++++++++++++++++++++--- src/action/darwin/mod.rs | 2 +- src/planner/darwin/multi.rs | 28 ++++++++- 6 files changed, 143 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b1f2f05..5b94073 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -786,6 +786,7 @@ dependencies = [ "nix", "owo-colors", "plist", + "rand 0.8.5", "reqwest", "serde", "serde_json", @@ -1333,6 +1334,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1394,6 +1401,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -1409,6 +1437,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -1808,7 +1845,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" dependencies = [ - "rand", + "rand 0.4.6", "remove_dir_all", ] diff --git a/Cargo.toml b/Cargo.toml index 2f9e5fe..6486b30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,3 +48,4 @@ dirs = "4.0.0" erased-serde = "0.3.23" typetag = "0.2.3" dyn-clone = "1.0.9" +rand = "0.8.5" diff --git a/src/action/darwin/create_apfs_volume.rs b/src/action/darwin/create_apfs_volume.rs index 2d280fd..c3b1c15 100644 --- a/src/action/darwin/create_apfs_volume.rs +++ b/src/action/darwin/create_apfs_volume.rs @@ -18,14 +18,14 @@ use crate::{ BoxableError, }; -const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist"; +pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist"; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateApfsVolume { disk: PathBuf, name: String, case_sensitive: bool, - encrypt: Option, + encrypt: bool, create_or_append_synthetic_conf: CreateOrAppendFile, create_synthetic_objects: CreateSyntheticObjects, unmount_volume: UnmountVolume, @@ -44,7 +44,7 @@ impl CreateApfsVolume { disk: impl AsRef, name: String, case_sensitive: bool, - encrypt: Option, + encrypt: bool, ) -> Result> { let disk = disk.as_ref(); let create_or_append_synthetic_conf = CreateOrAppendFile::plan( @@ -73,13 +73,13 @@ impl CreateApfsVolume { .await .map_err(|e| e.boxed())?; - let encrypt_volume = if let Some(password) = encrypt.as_ref() { - Some(EncryptVolume::plan(disk, password.to_string()).await?) + let encrypt_volume = if encrypt { + Some(EncryptVolume::plan(disk, &name).await?) } else { None }; - let mount_command = if encrypt.is_some() { + let mount_command = if encrypt { vec![ "/bin/sh", "-c", diff --git a/src/action/darwin/encrypt_volume.rs b/src/action/darwin/encrypt_volume.rs index 8e8f5c3..c7ce6b1 100644 --- a/src/action/darwin/encrypt_volume.rs +++ b/src/action/darwin/encrypt_volume.rs @@ -1,11 +1,15 @@ +use crate::{ + action::{darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionState}, + execute_command, +}; +use rand::Rng; use std::path::{Path, PathBuf}; - -use crate::action::{Action, ActionDescription, ActionState}; +use tokio::process::Command; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct EncryptVolume { disk: PathBuf, - password: String, + name: String, action_state: ActionState, } @@ -13,11 +17,12 @@ impl EncryptVolume { #[tracing::instrument(skip_all)] pub async fn plan( disk: impl AsRef, - password: String, + name: impl AsRef, ) -> Result> { + let name = name.as_ref().to_owned(); Ok(Self { + name, disk: disk.as_ref().to_path_buf(), - password, action_state: ActionState::Uncompleted, }) } @@ -42,8 +47,8 @@ impl Action for EncryptVolume { ))] async fn execute(&mut self) -> Result<(), Box> { let Self { - disk: _, - password: _, + disk, + name, action_state, } = self; if *action_state == ActionState::Completed { @@ -52,7 +57,65 @@ impl Action for EncryptVolume { } tracing::debug!("Encrypting volume"); - todo!(); + // Generate a random password. + let password: String = { + const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789)(*&^%$#@!~"; + const PASSWORD_LEN: usize = 32; + let mut rng = rand::thread_rng(); + + (0..PASSWORD_LEN) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect() + }; + + let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ + + // Add the password to the user keychain so they can unlock it later. + let _password_output = execute_command( + Command::new("/usr/bin/security").args([ + "add-generic-password", + "-a", + disk_str, + "-s", + name.as_str(), + "-l", + format!("{} encryption password", disk_str).as_str(), + "-D", + "Encrypted volume password", + "-j", + format!( + "Added automatically by the Nix installer for use by {NIX_VOLUME_MOUNTD_DEST}" + ) + .as_str(), + "-w", + password.as_str(), + "-T", + "/System/Library/CoreServices/APFSUserAgent", + "-T", + "/System/Library/CoreServices/CSUserAgent", + "-T", + "/usr/bin/security", + "/Library/Keychains/System.keychain", + ]), + ) + .await?; + + // Encrypt the mounted volume + execute_command(Command::new("/usr/sbin/diskutil").args([ + "apfs", + "encryptVolume", + disk_str, + "-user", + "disk", + "-passphrase", + password.as_str(), + ])) + .await?; tracing::trace!("Encrypted volume"); *action_state = ActionState::Completed; @@ -73,7 +136,7 @@ impl Action for EncryptVolume { async fn revert(&mut self) -> Result<(), Box> { let Self { disk: _, - password: _, + name: _, action_state, } = self; if *action_state == ActionState::Uncompleted { diff --git a/src/action/darwin/mod.rs b/src/action/darwin/mod.rs index 0cf48c7..bc3a525 100644 --- a/src/action/darwin/mod.rs +++ b/src/action/darwin/mod.rs @@ -8,7 +8,7 @@ mod kickstart_launchctl_service; mod unmount_volume; pub use bootstrap_volume::{BootstrapVolume, BootstrapVolumeError}; -pub use create_apfs_volume::{CreateApfsVolume, CreateApfsVolumeError}; +pub use create_apfs_volume::{CreateApfsVolume, CreateApfsVolumeError, NIX_VOLUME_MOUNTD_DEST}; pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError}; pub use create_volume::{CreateVolume, CreateVolumeError}; pub use enable_ownership::{EnableOwnership, EnableOwnershipError}; diff --git a/src/planner/darwin/multi.rs b/src/planner/darwin/multi.rs index 08da5eb..200943a 100644 --- a/src/planner/darwin/multi.rs +++ b/src/planner/darwin/multi.rs @@ -18,6 +18,7 @@ use crate::{ pub struct DarwinMulti { #[clap(flatten)] pub settings: CommonSettings, + /// Force encryption on the volume #[clap( long, action(ArgAction::SetTrue), @@ -25,6 +26,14 @@ pub struct DarwinMulti { env = "HARMONIC_VOLUME_ENCRYPT" )] pub volume_encrypt: bool, + /// Use a case sensitive volume + #[clap( + long, + action(ArgAction::SetTrue), + default_value = "false", + env = "HARMONIC_CASE_SENSITIVE" + )] + pub case_sensitive: bool, #[clap(long, default_value = "Nix Store", env = "HARMONIC_VOLUME_LABEL")] pub volume_label: String, #[clap(long, env = "HARMONIC_ROOT_DISK")] @@ -48,6 +57,7 @@ impl Planner for DarwinMulti { Ok(Self { settings: CommonSettings::default()?, root_disk: Some(default_root_disk().await?), + case_sensitive: false, volume_encrypt: false, volume_label: "Nix Store".into(), }) @@ -71,7 +81,14 @@ impl Planner for DarwinMulti { }, }; - let volume_label = "Nix Store".into(); + if self.volume_encrypt == false { + self.volume_encrypt = execute_command(Command::new("/usr/bin/fdesetup").arg("isactive")) + .await? + .status + .code() + .map(|v| if v == 0 { false } else { true }) + .unwrap_or(false) + } Ok(InstallPlan { planner: Box::new(self.clone()), @@ -83,9 +100,9 @@ impl Planner for DarwinMulti { Box::new( CreateApfsVolume::plan( self.root_disk.unwrap(), /* We just ensured it was populated */ - volume_label, + self.volume_label, false, - None, + self.volume_encrypt, ) .await?, ), @@ -105,6 +122,7 @@ impl Planner for DarwinMulti { settings, volume_encrypt, volume_label, + case_sensitive, root_disk, } = self; let mut map = HashMap::default(); @@ -116,6 +134,10 @@ impl Planner for DarwinMulti { ); map.insert("volume_label".into(), serde_json::to_value(volume_label)?); map.insert("root_disk".into(), serde_json::to_value(root_disk)?); + map.insert( + "case_sensitive".into(), + serde_json::to_value(case_sensitive)?, + ); Ok(map) } From 997364ad413fa99c3bbb64068550c24025bf108c Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Tue, 1 Nov 2022 15:31:31 -0700 Subject: [PATCH 02/10] Get key provisioning working better --- src/action/darwin/create_apfs_volume.rs | 6 ++- src/action/darwin/enable_ownership.rs | 3 +- src/action/darwin/encrypt_volume.rs | 49 +++++++++++++++++++++---- 3 files changed, 46 insertions(+), 12 deletions(-) diff --git a/src/action/darwin/create_apfs_volume.rs b/src/action/darwin/create_apfs_volume.rs index c3b1c15..22f1040 100644 --- a/src/action/darwin/create_apfs_volume.rs +++ b/src/action/darwin/create_apfs_volume.rs @@ -79,13 +79,15 @@ impl CreateApfsVolume { None }; + let name_with_qoutes = format!("\"{name}\""); let mount_command = if encrypt { vec![ "/bin/sh", "-c", - "/usr/bin/security find-generic-password", + "/usr/bin/security", + "find-generic-password", "-s", - "{name}", + name_with_qoutes.as_str(), "-w", "|", "/usr/sbin/diskutil", diff --git a/src/action/darwin/enable_ownership.rs b/src/action/darwin/enable_ownership.rs index 3e627ea..e0cf1d6 100644 --- a/src/action/darwin/enable_ownership.rs +++ b/src/action/darwin/enable_ownership.rs @@ -60,8 +60,7 @@ impl Action for EnableOwnership { .args(["info", "-plist"]) .arg(&path), ) - .await - .unwrap() + .await? .stdout; let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf)).unwrap(); diff --git a/src/action/darwin/encrypt_volume.rs b/src/action/darwin/encrypt_volume.rs index c7ce6b1..ab70689 100644 --- a/src/action/darwin/encrypt_volume.rs +++ b/src/action/darwin/encrypt_volume.rs @@ -75,12 +75,14 @@ impl Action for EncryptVolume { let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ + execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name)).await?; + // Add the password to the user keychain so they can unlock it later. - let _password_output = execute_command( + execute_command( Command::new("/usr/bin/security").args([ "add-generic-password", "-a", - disk_str, + name.as_str(), "-s", name.as_str(), "-l", @@ -109,7 +111,7 @@ impl Action for EncryptVolume { execute_command(Command::new("/usr/sbin/diskutil").args([ "apfs", "encryptVolume", - disk_str, + name.as_str(), "-user", "disk", "-passphrase", @@ -117,6 +119,14 @@ impl Action for EncryptVolume { ])) .await?; + execute_command( + Command::new("/usr/sbin/diskutil") + .arg("unmount") + .arg("force") + .arg(&name), + ) + .await?; + tracing::trace!("Encrypted volume"); *action_state = ActionState::Completed; Ok(()) @@ -135,17 +145,40 @@ impl Action for EncryptVolume { ))] async fn revert(&mut self) -> Result<(), Box> { let Self { - disk: _, - name: _, + disk, + name, action_state, } = self; if *action_state == ActionState::Uncompleted { - tracing::trace!("Already reverted: Unencrypted volume (noop)"); + tracing::trace!("Already reverted: Unencrypted volume"); return Ok(()); } - tracing::debug!("Unencrypted volume (noop)"); + tracing::debug!("Unencrypted volume"); - tracing::trace!("Unencrypted volume (noop)"); + let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */ + + // TODO: This seems very rough and unsafe + execute_command( + Command::new("/usr/bin/security").args([ + "delete-generic-password", + "-a", + name.as_str(), + "-s", + name.as_str(), + "-l", + format!("{} encryption password", disk_str).as_str(), + "-D", + "Encrypted volume password", + "-j", + format!( + "Added automatically by the Nix installer for use by {NIX_VOLUME_MOUNTD_DEST}" + ) + .as_str(), + ]), + ) + .await?; + + tracing::trace!("Unencrypted volume"); *action_state = ActionState::Completed; Ok(()) } From e079f3ade0a6730f48ac41017748b62e6a55dc22 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Wed, 2 Nov 2022 09:10:44 -0700 Subject: [PATCH 03/10] Use the right magic sauce in the mount command --- src/action/darwin/create_apfs_volume.rs | 28 +++++++++---------------- src/planner/darwin/multi.rs | 5 +++-- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/action/darwin/create_apfs_volume.rs b/src/action/darwin/create_apfs_volume.rs index 22f1040..3f6cdd8 100644 --- a/src/action/darwin/create_apfs_volume.rs +++ b/src/action/darwin/create_apfs_volume.rs @@ -80,26 +80,18 @@ impl CreateApfsVolume { }; let name_with_qoutes = format!("\"{name}\""); + let encrypted_command; let mount_command = if encrypt { - vec![ - "/bin/sh", - "-c", - "/usr/bin/security", - "find-generic-password", - "-s", - name_with_qoutes.as_str(), - "-w", - "|", - "/usr/sbin/diskutil", - "apfs", - "unlockVolume", - &name, - "-mountpoint", - "/nix", - "-stdinpassphrase", - ] + encrypted_command = format!("/usr/bin/security find-generic-password -s {name_with_qoutes} -w | /usr/sbin/diskutil apfs unlockVolume {name_with_qoutes} -mountpoint /nix -stdinpassphrase"); + vec!["/bin/sh", "-c", encrypted_command.as_str()] } else { - vec!["/usr/sbin/diskutil", "mount", "-mountPoint", "/nix", &name] + vec![ + "/usr/sbin/diskutil", + "mount", + "-mountPoint", + "/nix", + name_with_qoutes.as_str(), + ] }; // TODO(@hoverbear): Use plist lib we have in tree... let mount_plist = format!( diff --git a/src/planner/darwin/multi.rs b/src/planner/darwin/multi.rs index 200943a..e8f1b19 100644 --- a/src/planner/darwin/multi.rs +++ b/src/planner/darwin/multi.rs @@ -82,9 +82,10 @@ impl Planner for DarwinMulti { }; if self.volume_encrypt == false { - self.volume_encrypt = execute_command(Command::new("/usr/bin/fdesetup").arg("isactive")) + self.volume_encrypt = Command::new("/usr/bin/fdesetup") + .arg("isactive") + .status() .await? - .status .code() .map(|v| if v == 0 { false } else { true }) .unwrap_or(false) From 5cf3ce93b9b0343508544a664a7f3fcef001c4ed Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Wed, 2 Nov 2022 09:16:21 -0700 Subject: [PATCH 04/10] Rename volume-encrypt to encrypt --- src/planner/darwin/multi.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/planner/darwin/multi.rs b/src/planner/darwin/multi.rs index e8f1b19..492007e 100644 --- a/src/planner/darwin/multi.rs +++ b/src/planner/darwin/multi.rs @@ -23,9 +23,9 @@ pub struct DarwinMulti { long, action(ArgAction::SetTrue), default_value = "false", - env = "HARMONIC_VOLUME_ENCRYPT" + env = "HARMONIC_ENCRYPT" )] - pub volume_encrypt: bool, + pub encrypt: bool, /// Use a case sensitive volume #[clap( long, @@ -58,7 +58,7 @@ impl Planner for DarwinMulti { settings: CommonSettings::default()?, root_disk: Some(default_root_disk().await?), case_sensitive: false, - volume_encrypt: false, + encrypt: false, volume_label: "Nix Store".into(), }) } @@ -81,8 +81,8 @@ impl Planner for DarwinMulti { }, }; - if self.volume_encrypt == false { - self.volume_encrypt = Command::new("/usr/bin/fdesetup") + if self.encrypt == false { + self.encrypt = Command::new("/usr/bin/fdesetup") .arg("isactive") .status() .await? @@ -103,7 +103,7 @@ impl Planner for DarwinMulti { self.root_disk.unwrap(), /* We just ensured it was populated */ self.volume_label, false, - self.volume_encrypt, + self.encrypt, ) .await?, ), @@ -121,7 +121,7 @@ impl Planner for DarwinMulti { ) -> Result, Box> { let Self { settings, - volume_encrypt, + encrypt, volume_label, case_sensitive, root_disk, @@ -129,10 +129,7 @@ impl Planner for DarwinMulti { let mut map = HashMap::default(); map.extend(settings.describe()?.into_iter()); - map.insert( - "volume_encrypt".into(), - serde_json::to_value(volume_encrypt)?, - ); + 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( From 67efbedae918e337b147e2ad9b92ca0e7188ca30 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Wed, 2 Nov 2022 09:29:37 -0700 Subject: [PATCH 05/10] Make it so --encrypt false works --- src/planner/darwin/multi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/planner/darwin/multi.rs b/src/planner/darwin/multi.rs index 492007e..f708383 100644 --- a/src/planner/darwin/multi.rs +++ b/src/planner/darwin/multi.rs @@ -21,7 +21,7 @@ pub struct DarwinMulti { /// Force encryption on the volume #[clap( long, - action(ArgAction::SetTrue), + action(ArgAction::Set), default_value = "false", env = "HARMONIC_ENCRYPT" )] From 7b8c62e4d56d8204c9967b1e909bb8e3e1c3fc40 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Wed, 2 Nov 2022 09:32:06 -0700 Subject: [PATCH 06/10] When user sets encrypted, detect it, else heuristic --- src/planner/darwin/multi.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/planner/darwin/multi.rs b/src/planner/darwin/multi.rs index f708383..e5cb3ba 100644 --- a/src/planner/darwin/multi.rs +++ b/src/planner/darwin/multi.rs @@ -25,7 +25,7 @@ pub struct DarwinMulti { default_value = "false", env = "HARMONIC_ENCRYPT" )] - pub encrypt: bool, + pub encrypt: Option, /// Use a case sensitive volume #[clap( long, @@ -58,7 +58,7 @@ impl Planner for DarwinMulti { settings: CommonSettings::default()?, root_disk: Some(default_root_disk().await?), case_sensitive: false, - encrypt: false, + encrypt: None, volume_label: "Nix Store".into(), }) } @@ -81,15 +81,17 @@ impl Planner for DarwinMulti { }, }; - if self.encrypt == false { - self.encrypt = Command::new("/usr/bin/fdesetup") + let encrypt = if self.encrypt == None { + Command::new("/usr/bin/fdesetup") .arg("isactive") .status() .await? .code() .map(|v| if v == 0 { false } else { true }) .unwrap_or(false) - } + } else { + false + }; Ok(InstallPlan { planner: Box::new(self.clone()), @@ -103,7 +105,7 @@ impl Planner for DarwinMulti { self.root_disk.unwrap(), /* We just ensured it was populated */ self.volume_label, false, - self.encrypt, + encrypt, ) .await?, ), From c230ea65f63ac8842f2a39d71c3a34f686270c38 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Wed, 2 Nov 2022 09:47:40 -0700 Subject: [PATCH 07/10] Revert accidental name_with_qoutes that broke unencrypted --- src/action/darwin/create_apfs_volume.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/action/darwin/create_apfs_volume.rs b/src/action/darwin/create_apfs_volume.rs index 3f6cdd8..7f37002 100644 --- a/src/action/darwin/create_apfs_volume.rs +++ b/src/action/darwin/create_apfs_volume.rs @@ -90,7 +90,7 @@ impl CreateApfsVolume { "mount", "-mountPoint", "/nix", - name_with_qoutes.as_str(), + name.as_str(), ] }; // TODO(@hoverbear): Use plist lib we have in tree... From 80b7c2c1fae624a0720bf1d79ebc0eb5b717c4b4 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Fri, 4 Nov 2022 12:24:38 -0700 Subject: [PATCH 08/10] Workaround user deletion issues Signed-off-by: Ana Hobden --- README.md | 5 +- src/action/common/create_group.rs | 51 ++++++--- src/action/common/create_user.rs | 179 ++++++++++++++++-------------- 3 files changed, 133 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index 9e0b6a2..40f61dc 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,11 @@ Planned support: * [x] Multi-user x86_64 Linux with systemd init * [ ] Multi-user aarch64 Linux with systemd init * [x] Multi-user x86_64 MacOS - + Note: Uninstall and encrypted volume support are incomplete + + Note: user deletion is still buggy +* [x] Multi-user aarch64 MacOS + + Note: user deletion is still buggy * [ ] Single-user x86_64 Linux with systemd init * [ ] Single-user aarch64 Linux with systemd init -* [ ] Multi-user aarch64 MacOS * [ ] Others... ## Installation Differences diff --git a/src/action/common/create_group.rs b/src/action/common/create_group.rs index 2ac3971..23108d2 100644 --- a/src/action/common/create_group.rs +++ b/src/action/common/create_group.rs @@ -70,17 +70,29 @@ impl Action for CreateGroup { patch: _, } | OperatingSystem::Darwin => { - execute_command(Command::new("/usr/sbin/dseditgroup").args([ - "-o", - "create", - "-r", - "Nix build group for nix-daemon", - "-i", - &format!("{gid}"), - name.as_str(), - ])) - .await - .map_err(|e| CreateGroupError::Command(e).boxed())?; + // TODO(@hoverbear): Make this actually work... + // Right now, our test machines do not have a secure token and cannot delete users. + + if Command::new("/usr/bin/dscl") + .args([".", "-read", &format!("/Groups/{name}")]) + .status() + .await? + .success() + { + () + } else { + execute_command(Command::new("/usr/sbin/dseditgroup").args([ + "-o", + "create", + "-r", + "Nix build group for nix-daemon", + "-i", + &format!("{gid}"), + name.as_str(), + ])) + .await + .map_err(|e| CreateGroupError::Command(e).boxed())?; + } }, _ => { execute_command(Command::new("groupadd").args([ @@ -141,13 +153,16 @@ impl Action for CreateGroup { patch: _, } | OperatingSystem::Darwin => { - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-delete", - &format!("/Groups/{name}"), - ])) - .await - .map_err(|e| CreateGroupError::Command(e).boxed())?; + // TODO(@hoverbear): Make this actually work... + // Right now, our test machines do not have a secure token and cannot delete users. + + // execute_command(Command::new("/usr/bin/dscl").args([ + // ".", + // "-delete", + // &format!("/Groups/{name}"), + // ])) + // .await + // .map_err(|e| CreateGroupError::Command(e).boxed())?; }, _ => { execute_command(Command::new("groupdel").arg(&name)) diff --git a/src/action/common/create_user.rs b/src/action/common/create_user.rs index 40a6086..c3f108f 100644 --- a/src/action/common/create_user.rs +++ b/src/action/common/create_user.rs @@ -81,81 +81,93 @@ impl Action for CreateUser { patch: _, } | OperatingSystem::Darwin => { - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-create", - &format!("/Users/{name}"), - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-create", - &format!("/Users/{name}"), - "UniqueID", - &format!("{uid}"), - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-create", - &format!("/Users/{name}"), - "PrimaryGroupID", - &format!("{gid}"), - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-create", - &format!("/Users/{name}"), - "NFSHomeDirectory", - "/var/empty", - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-create", - &format!("/Users/{name}"), - "UserShell", - "/sbin/nologin", - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command( - Command::new("/usr/bin/dscl") - .args([ - ".", - "-append", - &format!("/Groups/{groupname}"), - "GroupMembership", - ]) - .arg(&name), - ) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-create", - &format!("/Users/{name}"), - "IsHidden", - "1", - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; - execute_command( - Command::new("/usr/sbin/dseditgroup") - .args(["-o", "edit"]) - .arg("-a") - .arg(&name) - .arg("-t") - .arg(&name) - .arg(groupname), - ) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; + // TODO(@hoverbear): Make this actually work... + // Right now, our test machines do not have a secure token and cannot delete users. + + if Command::new("/usr/bin/dscl") + .args([".", "-read", &format!("/Users/{name}")]) + .status() + .await? + .success() + { + () + } else { + execute_command(Command::new("/usr/bin/dscl").args([ + ".", + "-create", + &format!("/Users/{name}"), + ])) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command(Command::new("/usr/bin/dscl").args([ + ".", + "-create", + &format!("/Users/{name}"), + "UniqueID", + &format!("{uid}"), + ])) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command(Command::new("/usr/bin/dscl").args([ + ".", + "-create", + &format!("/Users/{name}"), + "PrimaryGroupID", + &format!("{gid}"), + ])) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command(Command::new("/usr/bin/dscl").args([ + ".", + "-create", + &format!("/Users/{name}"), + "NFSHomeDirectory", + "/var/empty", + ])) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command(Command::new("/usr/bin/dscl").args([ + ".", + "-create", + &format!("/Users/{name}"), + "UserShell", + "/sbin/nologin", + ])) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command( + Command::new("/usr/bin/dscl") + .args([ + ".", + "-append", + &format!("/Groups/{groupname}"), + "GroupMembership", + ]) + .arg(&name), + ) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command(Command::new("/usr/bin/dscl").args([ + ".", + "-create", + &format!("/Users/{name}"), + "IsHidden", + "1", + ])) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + execute_command( + Command::new("/usr/sbin/dseditgroup") + .args(["-o", "edit"]) + .arg("-a") + .arg(&name) + .arg("-t") + .arg(&name) + .arg(groupname), + ) + .await + .map_err(|e| CreateUserError::Command(e).boxed())?; + } }, _ => { execute_command(Command::new("useradd").args([ @@ -235,13 +247,16 @@ impl Action for CreateUser { patch: _, } | OperatingSystem::Darwin => { - execute_command(Command::new("/usr/bin/dscl").args([ - ".", - "-delete", - &format!("/Users/{name}"), - ])) - .await - .map_err(|e| CreateUserError::Command(e).boxed())?; + // TODO(@hoverbear): Make this actually work... + // Right now, our test machines do not have a secure token and cannot delete users. + + // execute_command(Command::new("/usr/bin/dscl").args([ + // ".", + // "-delete", + // &format!("/Users/{name}"), + // ])) + // .await + // .map_err(|e| CreateUserError::Command(e).boxed())?; }, _ => { execute_command(Command::new("userdel").args([&name.to_string()])) From 4fbc9dbdf9dfe9c897d6b98f13e85fd6a75ef278 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Wed, 9 Nov 2022 08:50:50 -0800 Subject: [PATCH 09/10] Better note in readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40f61dc..fc37909 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ Planned support: * [x] Multi-user x86_64 Linux with systemd init * [ ] Multi-user aarch64 Linux with systemd init * [x] Multi-user x86_64 MacOS - + Note: user deletion is still buggy + + Note: User deletion is currently unimplemented, you need to use a user with a secure token and `dscl . -delete /Users/_nixbuild*` where `*` is each user number. * [x] Multi-user aarch64 MacOS - + Note: user deletion is still buggy + + Note: User deletion is currently unimplemented, you need to use a user with a secure token and `dscl . -delete /Users/_nixbuild*` where `*` is each user number. * [ ] Single-user x86_64 Linux with systemd init * [ ] Single-user aarch64 Linux with systemd init * [ ] Others... From b81ec5f11f2cb0bc7886b66a4c89e5d7c24f90e4 Mon Sep 17 00:00:00 2001 From: Ana Hobden Date: Thu, 10 Nov 2022 08:46:31 -0800 Subject: [PATCH 10/10] Include warning messages on noops for mac --- src/action/base/create_group.rs | 5 +---- src/action/base/create_user.rs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/action/base/create_group.rs b/src/action/base/create_group.rs index 8e60cf9..3e62f96 100644 --- a/src/action/base/create_group.rs +++ b/src/action/base/create_group.rs @@ -70,9 +70,6 @@ impl Action for CreateGroup { patch: _, } | OperatingSystem::Darwin => { - // TODO(@hoverbear): Make this actually work... - // Right now, our test machines do not have a secure token and cannot delete users. - if Command::new("/usr/bin/dscl") .args([".", "-read", &format!("/Groups/{name}")]) .status() @@ -155,7 +152,7 @@ impl Action for CreateGroup { | OperatingSystem::Darwin => { // TODO(@hoverbear): Make this actually work... // Right now, our test machines do not have a secure token and cannot delete users. - + tracing::warn!("Harmonic currently cannot delete groups on Mac due to https://github.com/DeterminateSystems/harmonic/issues/33. This is a no-op, installing with harmonic again will use the existing group."); // execute_command(Command::new("/usr/bin/dscl").args([ // ".", // "-delete", diff --git a/src/action/base/create_user.rs b/src/action/base/create_user.rs index cf1704e..cc4d068 100644 --- a/src/action/base/create_user.rs +++ b/src/action/base/create_user.rs @@ -249,7 +249,7 @@ impl Action for CreateUser { | OperatingSystem::Darwin => { // TODO(@hoverbear): Make this actually work... // Right now, our test machines do not have a secure token and cannot delete users. - + tracing::warn!("Harmonic currently cannot delete groups on Mac due to https://github.com/DeterminateSystems/harmonic/issues/33. This is a no-op, installing with harmonic again will use the existing user."); // execute_command(Command::new("/usr/bin/dscl").args([ // ".", // "-delete",