forked from lix-project/lix-installer
Merge pull request #31 from DeterminateSystems/nix-mac-encrypted-volume
Nix Mac encrypted volume support
This commit is contained in:
commit
673464ede7
39
Cargo.lock
generated
39
Cargo.lock
generated
|
@ -786,6 +786,7 @@ dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
"plist",
|
"plist",
|
||||||
|
"rand 0.8.5",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1333,6 +1334,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-error"
|
name = "proc-macro-error"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -1394,6 +1401,27 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -1409,6 +1437,15 @@ version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
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]]
|
[[package]]
|
||||||
name = "rdrand"
|
name = "rdrand"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1808,7 +1845,7 @@ version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand",
|
"rand 0.4.6",
|
||||||
"remove_dir_all",
|
"remove_dir_all",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -48,3 +48,4 @@ dirs = "4.0.0"
|
||||||
erased-serde = "0.3.23"
|
erased-serde = "0.3.23"
|
||||||
typetag = "0.2.3"
|
typetag = "0.2.3"
|
||||||
dyn-clone = "1.0.9"
|
dyn-clone = "1.0.9"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
|
@ -13,10 +13,11 @@ Planned support:
|
||||||
* [x] Multi-user x86_64 Linux with systemd init
|
* [x] Multi-user x86_64 Linux with systemd init
|
||||||
* [ ] Multi-user aarch64 Linux with systemd init
|
* [ ] Multi-user aarch64 Linux with systemd init
|
||||||
* [x] Multi-user x86_64 MacOS
|
* [x] Multi-user x86_64 MacOS
|
||||||
+ Note: Uninstall and encrypted volume support are incomplete
|
+ 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 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 x86_64 Linux with systemd init
|
||||||
* [ ] Single-user aarch64 Linux with systemd init
|
* [ ] Single-user aarch64 Linux with systemd init
|
||||||
* [ ] Multi-user aarch64 MacOS
|
|
||||||
* [ ] Others...
|
* [ ] Others...
|
||||||
|
|
||||||
## Installation Differences
|
## Installation Differences
|
||||||
|
|
|
@ -70,17 +70,26 @@ impl Action for CreateGroup {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/sbin/dseditgroup").args([
|
if Command::new("/usr/bin/dscl")
|
||||||
"-o",
|
.args([".", "-read", &format!("/Groups/{name}")])
|
||||||
"create",
|
.status()
|
||||||
"-r",
|
.await?
|
||||||
"Nix build group for nix-daemon",
|
.success()
|
||||||
"-i",
|
{
|
||||||
&format!("{gid}"),
|
()
|
||||||
name.as_str(),
|
} else {
|
||||||
]))
|
execute_command(Command::new("/usr/sbin/dseditgroup").args([
|
||||||
.await
|
"-o",
|
||||||
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
"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([
|
execute_command(Command::new("groupadd").args([
|
||||||
|
@ -141,13 +150,16 @@ impl Action for CreateGroup {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
// TODO(@hoverbear): Make this actually work...
|
||||||
".",
|
// Right now, our test machines do not have a secure token and cannot delete users.
|
||||||
"-delete",
|
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.");
|
||||||
&format!("/Groups/{name}"),
|
// execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
]))
|
// ".",
|
||||||
.await
|
// "-delete",
|
||||||
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
// &format!("/Groups/{name}"),
|
||||||
|
// ]))
|
||||||
|
// .await
|
||||||
|
// .map_err(|e| CreateGroupError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(Command::new("groupdel").arg(&name))
|
execute_command(Command::new("groupdel").arg(&name))
|
||||||
|
|
|
@ -81,81 +81,93 @@ impl Action for CreateUser {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
// TODO(@hoverbear): Make this actually work...
|
||||||
".",
|
// Right now, our test machines do not have a secure token and cannot delete users.
|
||||||
"-create",
|
|
||||||
&format!("/Users/{name}"),
|
if Command::new("/usr/bin/dscl")
|
||||||
]))
|
.args([".", "-read", &format!("/Users/{name}")])
|
||||||
.await
|
.status()
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.await?
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
.success()
|
||||||
".",
|
{
|
||||||
"-create",
|
()
|
||||||
&format!("/Users/{name}"),
|
} else {
|
||||||
"UniqueID",
|
execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
&format!("{uid}"),
|
".",
|
||||||
]))
|
"-create",
|
||||||
.await
|
&format!("/Users/{name}"),
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
]))
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
.await
|
||||||
".",
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
"-create",
|
execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
&format!("/Users/{name}"),
|
".",
|
||||||
"PrimaryGroupID",
|
"-create",
|
||||||
&format!("{gid}"),
|
&format!("/Users/{name}"),
|
||||||
]))
|
"UniqueID",
|
||||||
.await
|
&format!("{uid}"),
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
]))
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
.await
|
||||||
".",
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
"-create",
|
execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
&format!("/Users/{name}"),
|
".",
|
||||||
"NFSHomeDirectory",
|
"-create",
|
||||||
"/var/empty",
|
&format!("/Users/{name}"),
|
||||||
]))
|
"PrimaryGroupID",
|
||||||
.await
|
&format!("{gid}"),
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
]))
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
.await
|
||||||
".",
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
"-create",
|
execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
&format!("/Users/{name}"),
|
".",
|
||||||
"UserShell",
|
"-create",
|
||||||
"/sbin/nologin",
|
&format!("/Users/{name}"),
|
||||||
]))
|
"NFSHomeDirectory",
|
||||||
.await
|
"/var/empty",
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
]))
|
||||||
execute_command(
|
.await
|
||||||
Command::new("/usr/bin/dscl")
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
.args([
|
execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
".",
|
".",
|
||||||
"-append",
|
"-create",
|
||||||
&format!("/Groups/{groupname}"),
|
&format!("/Users/{name}"),
|
||||||
"GroupMembership",
|
"UserShell",
|
||||||
])
|
"/sbin/nologin",
|
||||||
.arg(&name),
|
]))
|
||||||
)
|
.await
|
||||||
.await
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
execute_command(
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
Command::new("/usr/bin/dscl")
|
||||||
".",
|
.args([
|
||||||
"-create",
|
".",
|
||||||
&format!("/Users/{name}"),
|
"-append",
|
||||||
"IsHidden",
|
&format!("/Groups/{groupname}"),
|
||||||
"1",
|
"GroupMembership",
|
||||||
]))
|
])
|
||||||
.await
|
.arg(&name),
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
)
|
||||||
execute_command(
|
.await
|
||||||
Command::new("/usr/sbin/dseditgroup")
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
.args(["-o", "edit"])
|
execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
.arg("-a")
|
".",
|
||||||
.arg(&name)
|
"-create",
|
||||||
.arg("-t")
|
&format!("/Users/{name}"),
|
||||||
.arg(&name)
|
"IsHidden",
|
||||||
.arg(groupname),
|
"1",
|
||||||
)
|
]))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.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([
|
execute_command(Command::new("useradd").args([
|
||||||
|
@ -235,13 +247,16 @@ impl Action for CreateUser {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
// TODO(@hoverbear): Make this actually work...
|
||||||
".",
|
// Right now, our test machines do not have a secure token and cannot delete users.
|
||||||
"-delete",
|
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.");
|
||||||
&format!("/Users/{name}"),
|
// execute_command(Command::new("/usr/bin/dscl").args([
|
||||||
]))
|
// ".",
|
||||||
.await
|
// "-delete",
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
// &format!("/Users/{name}"),
|
||||||
|
// ]))
|
||||||
|
// .await
|
||||||
|
// .map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(Command::new("userdel").args([&name.to_string()]))
|
execute_command(Command::new("userdel").args([&name.to_string()]))
|
||||||
|
|
|
@ -17,14 +17,14 @@ use std::{
|
||||||
};
|
};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
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)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateApfsVolume {
|
pub struct CreateApfsVolume {
|
||||||
disk: PathBuf,
|
disk: PathBuf,
|
||||||
name: String,
|
name: String,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
encrypt: Option<String>,
|
encrypt: bool,
|
||||||
create_or_append_synthetic_conf: CreateOrAppendFile,
|
create_or_append_synthetic_conf: CreateOrAppendFile,
|
||||||
create_synthetic_objects: CreateSyntheticObjects,
|
create_synthetic_objects: CreateSyntheticObjects,
|
||||||
unmount_volume: UnmountVolume,
|
unmount_volume: UnmountVolume,
|
||||||
|
@ -43,7 +43,7 @@ impl CreateApfsVolume {
|
||||||
disk: impl AsRef<Path>,
|
disk: impl AsRef<Path>,
|
||||||
name: String,
|
name: String,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
encrypt: Option<String>,
|
encrypt: bool,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let disk = disk.as_ref();
|
let disk = disk.as_ref();
|
||||||
let create_or_append_synthetic_conf = CreateOrAppendFile::plan(
|
let create_or_append_synthetic_conf = CreateOrAppendFile::plan(
|
||||||
|
@ -72,31 +72,25 @@ impl CreateApfsVolume {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.boxed())?;
|
.map_err(|e| e.boxed())?;
|
||||||
|
|
||||||
let encrypt_volume = if let Some(password) = encrypt.as_ref() {
|
let encrypt_volume = if encrypt {
|
||||||
Some(EncryptVolume::plan(disk, password.to_string()).await?)
|
Some(EncryptVolume::plan(disk, &name).await?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mount_command = if encrypt.is_some() {
|
let name_with_qoutes = format!("\"{name}\"");
|
||||||
vec![
|
let encrypted_command;
|
||||||
"/bin/sh",
|
let mount_command = if encrypt {
|
||||||
"-c",
|
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");
|
||||||
"/usr/bin/security find-generic-password",
|
vec!["/bin/sh", "-c", encrypted_command.as_str()]
|
||||||
"-s",
|
|
||||||
"{name}",
|
|
||||||
"-w",
|
|
||||||
"|",
|
|
||||||
"/usr/sbin/diskutil",
|
|
||||||
"apfs",
|
|
||||||
"unlockVolume",
|
|
||||||
&name,
|
|
||||||
"-mountpoint",
|
|
||||||
"/nix",
|
|
||||||
"-stdinpassphrase",
|
|
||||||
]
|
|
||||||
} else {
|
} else {
|
||||||
vec!["/usr/sbin/diskutil", "mount", "-mountPoint", "/nix", &name]
|
vec![
|
||||||
|
"/usr/sbin/diskutil",
|
||||||
|
"mount",
|
||||||
|
"-mountPoint",
|
||||||
|
"/nix",
|
||||||
|
name.as_str(),
|
||||||
|
]
|
||||||
};
|
};
|
||||||
// TODO(@hoverbear): Use plist lib we have in tree...
|
// TODO(@hoverbear): Use plist lib we have in tree...
|
||||||
let mount_plist = format!(
|
let mount_plist = format!(
|
||||||
|
|
|
@ -60,8 +60,7 @@ impl Action for EnableOwnership {
|
||||||
.args(["info", "-plist"])
|
.args(["info", "-plist"])
|
||||||
.arg(&path),
|
.arg(&path),
|
||||||
)
|
)
|
||||||
.await
|
.await?
|
||||||
.unwrap()
|
|
||||||
.stdout;
|
.stdout;
|
||||||
let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf)).unwrap();
|
let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf)).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -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 std::path::{Path, PathBuf};
|
||||||
|
use tokio::process::Command;
|
||||||
use crate::action::{Action, ActionDescription, ActionState};
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct EncryptVolume {
|
pub struct EncryptVolume {
|
||||||
disk: PathBuf,
|
disk: PathBuf,
|
||||||
password: String,
|
name: String,
|
||||||
action_state: ActionState,
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,11 +17,12 @@ impl EncryptVolume {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
disk: impl AsRef<Path>,
|
disk: impl AsRef<Path>,
|
||||||
password: String,
|
name: impl AsRef<str>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let name = name.as_ref().to_owned();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
name,
|
||||||
disk: disk.as_ref().to_path_buf(),
|
disk: disk.as_ref().to_path_buf(),
|
||||||
password,
|
|
||||||
action_state: ActionState::Uncompleted,
|
action_state: ActionState::Uncompleted,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -42,8 +47,8 @@ impl Action for EncryptVolume {
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
disk: _,
|
disk,
|
||||||
password: _,
|
name,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
if *action_state == ActionState::Completed {
|
if *action_state == ActionState::Completed {
|
||||||
|
@ -52,7 +57,75 @@ impl Action for EncryptVolume {
|
||||||
}
|
}
|
||||||
tracing::debug!("Encrypting volume");
|
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 */
|
||||||
|
|
||||||
|
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.
|
||||||
|
execute_command(
|
||||||
|
Command::new("/usr/bin/security").args([
|
||||||
|
"add-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(),
|
||||||
|
"-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",
|
||||||
|
name.as_str(),
|
||||||
|
"-user",
|
||||||
|
"disk",
|
||||||
|
"-passphrase",
|
||||||
|
password.as_str(),
|
||||||
|
]))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
execute_command(
|
||||||
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
.arg("unmount")
|
||||||
|
.arg("force")
|
||||||
|
.arg(&name),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
tracing::trace!("Encrypted volume");
|
tracing::trace!("Encrypted volume");
|
||||||
*action_state = ActionState::Completed;
|
*action_state = ActionState::Completed;
|
||||||
|
@ -72,17 +145,40 @@ impl Action for EncryptVolume {
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
disk: _,
|
disk,
|
||||||
password: _,
|
name,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
if *action_state == ActionState::Uncompleted {
|
if *action_state == ActionState::Uncompleted {
|
||||||
tracing::trace!("Already reverted: Unencrypted volume (noop)");
|
tracing::trace!("Already reverted: Unencrypted volume");
|
||||||
return Ok(());
|
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;
|
*action_state = ActionState::Completed;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ mod kickstart_launchctl_service;
|
||||||
mod unmount_volume;
|
mod unmount_volume;
|
||||||
|
|
||||||
pub use bootstrap_volume::{BootstrapVolume, BootstrapVolumeError};
|
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_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
|
||||||
pub use create_volume::{CreateVolume, CreateVolumeError};
|
pub use create_volume::{CreateVolume, CreateVolumeError};
|
||||||
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
|
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
|
||||||
|
|
|
@ -18,13 +18,22 @@ use crate::{
|
||||||
pub struct DarwinMulti {
|
pub struct DarwinMulti {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub settings: CommonSettings,
|
pub settings: CommonSettings,
|
||||||
|
/// Force encryption on the volume
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
action(ArgAction::Set),
|
||||||
|
default_value = "false",
|
||||||
|
env = "HARMONIC_ENCRYPT"
|
||||||
|
)]
|
||||||
|
pub encrypt: Option<bool>,
|
||||||
|
/// Use a case sensitive volume
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
action(ArgAction::SetTrue),
|
action(ArgAction::SetTrue),
|
||||||
default_value = "false",
|
default_value = "false",
|
||||||
env = "HARMONIC_VOLUME_ENCRYPT"
|
env = "HARMONIC_CASE_SENSITIVE"
|
||||||
)]
|
)]
|
||||||
pub volume_encrypt: bool,
|
pub case_sensitive: bool,
|
||||||
#[clap(long, default_value = "Nix Store", env = "HARMONIC_VOLUME_LABEL")]
|
#[clap(long, default_value = "Nix Store", env = "HARMONIC_VOLUME_LABEL")]
|
||||||
pub volume_label: String,
|
pub volume_label: String,
|
||||||
#[clap(long, env = "HARMONIC_ROOT_DISK")]
|
#[clap(long, env = "HARMONIC_ROOT_DISK")]
|
||||||
|
@ -48,7 +57,8 @@ impl Planner for DarwinMulti {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
settings: CommonSettings::default()?,
|
settings: CommonSettings::default()?,
|
||||||
root_disk: Some(default_root_disk().await?),
|
root_disk: Some(default_root_disk().await?),
|
||||||
volume_encrypt: false,
|
case_sensitive: false,
|
||||||
|
encrypt: None,
|
||||||
volume_label: "Nix Store".into(),
|
volume_label: "Nix Store".into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -71,7 +81,17 @@ impl Planner for DarwinMulti {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let volume_label = "Nix Store".into();
|
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 {
|
Ok(InstallPlan {
|
||||||
planner: Box::new(self.clone()),
|
planner: Box::new(self.clone()),
|
||||||
|
@ -83,9 +103,9 @@ impl Planner for DarwinMulti {
|
||||||
Box::new(
|
Box::new(
|
||||||
CreateApfsVolume::plan(
|
CreateApfsVolume::plan(
|
||||||
self.root_disk.unwrap(), /* We just ensured it was populated */
|
self.root_disk.unwrap(), /* We just ensured it was populated */
|
||||||
volume_label,
|
self.volume_label,
|
||||||
false,
|
false,
|
||||||
None,
|
encrypt,
|
||||||
)
|
)
|
||||||
.await?,
|
.await?,
|
||||||
),
|
),
|
||||||
|
@ -103,19 +123,21 @@ impl Planner for DarwinMulti {
|
||||||
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
|
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
|
||||||
let Self {
|
let Self {
|
||||||
settings,
|
settings,
|
||||||
volume_encrypt,
|
encrypt,
|
||||||
volume_label,
|
volume_label,
|
||||||
|
case_sensitive,
|
||||||
root_disk,
|
root_disk,
|
||||||
} = self;
|
} = self;
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
|
|
||||||
map.extend(settings.describe()?.into_iter());
|
map.extend(settings.describe()?.into_iter());
|
||||||
map.insert(
|
map.insert("volume_encrypt".into(), serde_json::to_value(encrypt)?);
|
||||||
"volume_encrypt".into(),
|
|
||||||
serde_json::to_value(volume_encrypt)?,
|
|
||||||
);
|
|
||||||
map.insert("volume_label".into(), serde_json::to_value(volume_label)?);
|
map.insert("volume_label".into(), serde_json::to_value(volume_label)?);
|
||||||
map.insert("root_disk".into(), serde_json::to_value(root_disk)?);
|
map.insert("root_disk".into(), serde_json::to_value(root_disk)?);
|
||||||
|
map.insert(
|
||||||
|
"case_sensitive".into(),
|
||||||
|
serde_json::to_value(case_sensitive)?,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue