Handle signals and user stdin more gracefully
This commit is contained in:
parent
b123bbf285
commit
942c652fc2
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1906,6 +1906,7 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2",
|
"socket2",
|
||||||
|
|
|
@ -22,7 +22,7 @@ crossterm = { version = "0.25.0", features = ["event-stream"] }
|
||||||
eyre = "0.6.8"
|
eyre = "0.6.8"
|
||||||
futures = "0.3.24"
|
futures = "0.3.24"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
nix = { version = "0.25.0", features = ["user", "fs"], default-features = false }
|
nix = { version = "0.25.0", features = ["user", "fs", "process"], default-features = false }
|
||||||
owo-colors = { version = "3.5.0", features = [ "supports-colors" ] }
|
owo-colors = { version = "3.5.0", features = [ "supports-colors" ] }
|
||||||
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "stream"] }
|
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "stream"] }
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
|
@ -32,7 +32,7 @@ tar = "0.4.38"
|
||||||
target-lexicon = "0.12.4"
|
target-lexicon = "0.12.4"
|
||||||
tempdir = { version = "0.3.7"}
|
tempdir = { version = "0.3.7"}
|
||||||
thiserror = "1.0.33"
|
thiserror = "1.0.33"
|
||||||
tokio = { version = "1.21.0", features = ["time", "io-std", "process", "fs", "tracing", "rt-multi-thread", "macros", "io-util"] }
|
tokio = { version = "1.21.0", features = ["time", "io-std", "process", "fs", "signal", "tracing", "rt-multi-thread", "macros", "io-util", "parking_lot"] }
|
||||||
tokio-util = { version = "0.7", features = ["io"] }
|
tokio-util = { version = "0.7", features = ["io"] }
|
||||||
tracing = { version = "0.1.36", features = [ "valuable" ] }
|
tracing = { version = "0.1.36", features = [ "valuable" ] }
|
||||||
tracing-error = "0.2.0"
|
tracing-error = "0.2.0"
|
||||||
|
|
|
@ -95,7 +95,8 @@ impl Action for ConfigureNixDaemonService {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
.arg("load")
|
.arg("load")
|
||||||
.arg(DARWIN_NIX_DAEMON_DEST),
|
.arg(DARWIN_NIX_DAEMON_DEST)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
@ -116,20 +117,35 @@ impl Action for ConfigureNixDaemonService {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemd-tmpfiles")
|
Command::new("systemd-tmpfiles")
|
||||||
.arg("--create")
|
.arg("--create")
|
||||||
.arg("--prefix=/nix/var/nix"),
|
.arg("--prefix=/nix/var/nix")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("link").arg(SERVICE_SRC))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("link")
|
||||||
|
.arg(SERVICE_SRC)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("link").arg(SOCKET_SRC))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("link")
|
||||||
|
.arg(SOCKET_SRC)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("daemon-reload"))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("daemon-reload")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
|
@ -181,18 +197,27 @@ impl Action for ConfigureNixDaemonService {
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(Command::new("systemctl").args(["disable", SOCKET_SRC]))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.args(["disable", SOCKET_SRC])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").args(["disable", SERVICE_SRC]))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.args(["disable", SERVICE_SRC])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemd-tmpfiles")
|
Command::new("systemd-tmpfiles")
|
||||||
.arg("--remove")
|
.arg("--remove")
|
||||||
.arg("--prefix=/nix/var/nix"),
|
.arg("--prefix=/nix/var/nix")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
|
@ -202,7 +227,11 @@ impl Action for ConfigureNixDaemonService {
|
||||||
.boxed()
|
.boxed()
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
execute_command(Command::new("systemctl").arg("daemon-reload"))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("daemon-reload")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -70,7 +70,9 @@ impl Action for CreateGroup {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/sbin/dseditgroup").args([
|
execute_command(
|
||||||
|
Command::new("/usr/sbin/dseditgroup")
|
||||||
|
.args([
|
||||||
"-o",
|
"-o",
|
||||||
"create",
|
"create",
|
||||||
"-r",
|
"-r",
|
||||||
|
@ -78,17 +80,18 @@ impl Action for CreateGroup {
|
||||||
"-i",
|
"-i",
|
||||||
&format!("{gid}"),
|
&format!("{gid}"),
|
||||||
name.as_str(),
|
name.as_str(),
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(Command::new("groupadd").args([
|
execute_command(
|
||||||
"-g",
|
Command::new("groupadd")
|
||||||
&gid.to_string(),
|
.args(["-g", &gid.to_string(), "--system", &name])
|
||||||
"--system",
|
.stdin(std::process::Stdio::null()),
|
||||||
&name,
|
)
|
||||||
]))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
|
@ -141,16 +144,20 @@ impl Action for CreateGroup {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
".",
|
Command::new("/usr/bin/dscl")
|
||||||
"-delete",
|
.args([".", "-delete", &format!("/Groups/{name}")])
|
||||||
&format!("/Groups/{name}"),
|
.stdin(std::process::Stdio::null()),
|
||||||
]))
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(Command::new("groupdel").arg(&name))
|
execute_command(
|
||||||
|
Command::new("groupdel")
|
||||||
|
.arg(&name)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
.map_err(|e| CreateGroupError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -81,47 +81,63 @@ impl Action for CreateUser {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
".",
|
Command::new("/usr/bin/dscl")
|
||||||
"-create",
|
.args([".", "-create", &format!("/Users/{name}")])
|
||||||
&format!("/Users/{name}"),
|
.stdin(std::process::Stdio::null()),
|
||||||
]))
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
|
Command::new("/usr/bin/dscl")
|
||||||
|
.args([
|
||||||
".",
|
".",
|
||||||
"-create",
|
"-create",
|
||||||
&format!("/Users/{name}"),
|
&format!("/Users/{name}"),
|
||||||
"UniqueID",
|
"UniqueID",
|
||||||
&format!("{uid}"),
|
&format!("{uid}"),
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
|
Command::new("/usr/bin/dscl")
|
||||||
|
.args([
|
||||||
".",
|
".",
|
||||||
"-create",
|
"-create",
|
||||||
&format!("/Users/{name}"),
|
&format!("/Users/{name}"),
|
||||||
"PrimaryGroupID",
|
"PrimaryGroupID",
|
||||||
&format!("{gid}"),
|
&format!("{gid}"),
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
|
Command::new("/usr/bin/dscl")
|
||||||
|
.args([
|
||||||
".",
|
".",
|
||||||
"-create",
|
"-create",
|
||||||
&format!("/Users/{name}"),
|
&format!("/Users/{name}"),
|
||||||
"NFSHomeDirectory",
|
"NFSHomeDirectory",
|
||||||
"/var/empty",
|
"/var/empty",
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
|
Command::new("/usr/bin/dscl")
|
||||||
|
.args([
|
||||||
".",
|
".",
|
||||||
"-create",
|
"-create",
|
||||||
&format!("/Users/{name}"),
|
&format!("/Users/{name}"),
|
||||||
"UserShell",
|
"UserShell",
|
||||||
"/sbin/nologin",
|
"/sbin/nologin",
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -132,17 +148,16 @@ impl Action for CreateUser {
|
||||||
&format!("/Groups/{groupname}"),
|
&format!("/Groups/{groupname}"),
|
||||||
"GroupMembership",
|
"GroupMembership",
|
||||||
])
|
])
|
||||||
.arg(&name),
|
.arg(&name)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
".",
|
Command::new("/usr/bin/dscl")
|
||||||
"-create",
|
.args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"])
|
||||||
&format!("/Users/{name}"),
|
.stdin(std::process::Stdio::null()),
|
||||||
"IsHidden",
|
)
|
||||||
"1",
|
|
||||||
]))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -152,13 +167,16 @@ impl Action for CreateUser {
|
||||||
.arg(&name)
|
.arg(&name)
|
||||||
.arg("-t")
|
.arg("-t")
|
||||||
.arg(&name)
|
.arg(&name)
|
||||||
.arg(groupname),
|
.arg(groupname)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(Command::new("useradd").args([
|
execute_command(
|
||||||
|
Command::new("useradd")
|
||||||
|
.args([
|
||||||
"--home-dir",
|
"--home-dir",
|
||||||
"/var/empty",
|
"/var/empty",
|
||||||
"--comment",
|
"--comment",
|
||||||
|
@ -176,7 +194,9 @@ impl Action for CreateUser {
|
||||||
"--password",
|
"--password",
|
||||||
"\"!\"",
|
"\"!\"",
|
||||||
&name.to_string(),
|
&name.to_string(),
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
|
@ -235,16 +255,20 @@ impl Action for CreateUser {
|
||||||
patch: _,
|
patch: _,
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
execute_command(Command::new("/usr/bin/dscl").args([
|
execute_command(
|
||||||
".",
|
Command::new("/usr/bin/dscl")
|
||||||
"-delete",
|
.args([".", "-delete", &format!("/Users/{name}")])
|
||||||
&format!("/Users/{name}"),
|
.stdin(std::process::Stdio::null()),
|
||||||
]))
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.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()])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
.map_err(|e| CreateUserError::Command(e).boxed())?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -99,6 +99,7 @@ impl Action for SetupDefaultProfile {
|
||||||
.arg(&nix_pkg)
|
.arg(&nix_pkg)
|
||||||
.arg("-i")
|
.arg("-i")
|
||||||
.arg(&nss_ca_cert_pkg)
|
.arg(&nss_ca_cert_pkg)
|
||||||
|
.stdin(std::process::Stdio::null())
|
||||||
.env(
|
.env(
|
||||||
"HOME",
|
"HOME",
|
||||||
dirs::home_dir().ok_or_else(|| SetupDefaultProfileError::NoRootHome.boxed())?,
|
dirs::home_dir().ok_or_else(|| SetupDefaultProfileError::NoRootHome.boxed())?,
|
||||||
|
@ -139,6 +140,7 @@ impl Action for SetupDefaultProfile {
|
||||||
"NIX_SSL_CERT_FILE",
|
"NIX_SSL_CERT_FILE",
|
||||||
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
||||||
);
|
);
|
||||||
|
command.stdin(std::process::Stdio::null());
|
||||||
|
|
||||||
execute_command(&mut command)
|
execute_command(&mut command)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -55,15 +55,16 @@ impl Action for BootstrapVolume {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
.args(["bootstrap", "system"])
|
.args(["bootstrap", "system"])
|
||||||
.arg(path),
|
.arg(path)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
|
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
|
||||||
execute_command(Command::new("launchctl").args([
|
execute_command(
|
||||||
"kickstart",
|
Command::new("launchctl")
|
||||||
"-k",
|
.args(["kickstart", "-k", "system/org.nixos.darwin-store"])
|
||||||
"system/org.nixos.darwin-store",
|
.stdin(std::process::Stdio::null()),
|
||||||
]))
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
|
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
|
||||||
|
|
||||||
|
@ -97,7 +98,8 @@ impl Action for BootstrapVolume {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
.args(["bootout", "system"])
|
.args(["bootout", "system"])
|
||||||
.arg(path),
|
.arg(path)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
|
.map_err(|e| BootstrapVolumeError::Command(e).boxed())?;
|
||||||
|
|
|
@ -44,13 +44,15 @@ impl Action for CreateSyntheticObjects {
|
||||||
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
|
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
||||||
.arg("-t"),
|
.arg("-t")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok(); // Deliberate
|
.ok(); // Deliberate
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
||||||
.arg("-B"),
|
.arg("-B")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok(); // Deliberate
|
.ok(); // Deliberate
|
||||||
|
@ -83,13 +85,15 @@ impl Action for CreateSyntheticObjects {
|
||||||
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
|
// Yup we literally call both and ignore the error! Reasoning: https://github.com/NixOS/nix/blob/95331cb9c99151cbd790ceb6ddaf49fc1c0da4b3/scripts/create-darwin-volume.sh#L261
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
||||||
.arg("-t"),
|
.arg("-t")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok(); // Deliberate
|
.ok(); // Deliberate
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
Command::new("/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util")
|
||||||
.arg("-B"),
|
.arg("-B")
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok(); // Deliberate
|
.ok(); // Deliberate
|
||||||
|
|
|
@ -69,7 +69,9 @@ impl Action for CreateVolume {
|
||||||
}
|
}
|
||||||
tracing::debug!("Creating volume");
|
tracing::debug!("Creating volume");
|
||||||
|
|
||||||
execute_command(Command::new("/usr/sbin/diskutil").args([
|
execute_command(
|
||||||
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
.args([
|
||||||
"apfs",
|
"apfs",
|
||||||
"addVolume",
|
"addVolume",
|
||||||
&format!("{}", disk.display()),
|
&format!("{}", disk.display()),
|
||||||
|
@ -80,7 +82,9 @@ impl Action for CreateVolume {
|
||||||
},
|
},
|
||||||
name,
|
name,
|
||||||
"-nomount",
|
"-nomount",
|
||||||
]))
|
])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
||||||
|
|
||||||
|
@ -122,7 +126,11 @@ impl Action for CreateVolume {
|
||||||
}
|
}
|
||||||
tracing::debug!("Deleting volume");
|
tracing::debug!("Deleting volume");
|
||||||
|
|
||||||
execute_command(Command::new("/usr/sbin/diskutil").args(["apfs", "deleteVolume", name]))
|
execute_command(
|
||||||
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
.args(["apfs", "deleteVolume", name])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,8 @@ impl Action for EnableOwnership {
|
||||||
let buf = execute_command(
|
let buf = execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
.args(["info", "-plist"])
|
.args(["info", "-plist"])
|
||||||
.arg(&path),
|
.arg(&path)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -72,7 +73,8 @@ impl Action for EnableOwnership {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
.arg("enableOwnership")
|
.arg("enableOwnership")
|
||||||
.arg(path),
|
.arg(path)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| EnableOwnershipError::Command(e).boxed())?;
|
.map_err(|e| EnableOwnershipError::Command(e).boxed())?;
|
||||||
|
|
|
@ -55,7 +55,8 @@ impl Action for KickstartLaunchctlService {
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
.arg("kickstart")
|
.arg("kickstart")
|
||||||
.arg("-k")
|
.arg("-k")
|
||||||
.arg(unit),
|
.arg(unit)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| KickstartLaunchctlServiceError::Command(e).boxed())?;
|
.map_err(|e| KickstartLaunchctlServiceError::Command(e).boxed())?;
|
||||||
|
|
|
@ -66,7 +66,8 @@ impl Action for UnmountVolume {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
.args(["unmount", "force"])
|
.args(["unmount", "force"])
|
||||||
.arg(name),
|
.arg(name)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| UnmountVolumeError::Command(e).boxed())?;
|
.map_err(|e| UnmountVolumeError::Command(e).boxed())?;
|
||||||
|
@ -108,7 +109,8 @@ impl Action for UnmountVolume {
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new(" /usr/sbin/diskutil")
|
Command::new(" /usr/sbin/diskutil")
|
||||||
.args(["unmount", "force"])
|
.args(["unmount", "force"])
|
||||||
.arg(name),
|
.arg(name)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| UnmountVolumeError::Command(e).boxed())?;
|
.map_err(|e| UnmountVolumeError::Command(e).boxed())?;
|
||||||
|
|
|
@ -55,7 +55,8 @@ impl Action for StartSystemdUnit {
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
.arg("enable")
|
.arg("enable")
|
||||||
.arg("--now")
|
.arg("--now")
|
||||||
.arg(format!("{unit}")),
|
.arg(format!("{unit}"))
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
|
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
|
||||||
|
@ -90,7 +91,12 @@ impl Action for StartSystemdUnit {
|
||||||
tracing::debug!("Stopping systemd unit");
|
tracing::debug!("Stopping systemd unit");
|
||||||
|
|
||||||
// TODO(@Hoverbear): Handle proxy vars
|
// TODO(@Hoverbear): Handle proxy vars
|
||||||
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
|
execute_command(
|
||||||
|
Command::new("systemctl")
|
||||||
|
.arg("stop")
|
||||||
|
.arg(format!("{unit}"))
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
|
.map_err(|e| StartSystemdUnitError::Command(e).boxed())?;
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,12 @@ impl Action for SystemdSysextMerge {
|
||||||
}
|
}
|
||||||
tracing::debug!("Merging systemd-sysext");
|
tracing::debug!("Merging systemd-sysext");
|
||||||
|
|
||||||
execute_command(Command::new("systemd-sysext").arg("merge").arg(device))
|
execute_command(
|
||||||
|
Command::new("systemd-sysext")
|
||||||
|
.arg("merge")
|
||||||
|
.arg(device)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| SystemdSysextMergeError::Command(e).boxed())?;
|
.map_err(|e| SystemdSysextMergeError::Command(e).boxed())?;
|
||||||
|
|
||||||
|
@ -94,7 +99,12 @@ impl Action for SystemdSysextMerge {
|
||||||
tracing::debug!("Unmrging systemd-sysext");
|
tracing::debug!("Unmrging systemd-sysext");
|
||||||
|
|
||||||
// TODO(@Hoverbear): Handle proxy vars
|
// TODO(@Hoverbear): Handle proxy vars
|
||||||
execute_command(Command::new("systemd-sysext").arg("unmerge").arg(device))
|
execute_command(
|
||||||
|
Command::new("systemd-sysext")
|
||||||
|
.arg("unmerge")
|
||||||
|
.arg(device)
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| SystemdSysextMergeError::Command(e).boxed())?;
|
.map_err(|e| SystemdSysextMergeError::Command(e).boxed())?;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ pub(crate) mod subcommand;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
use tokio::sync::broadcast::{Receiver, Sender};
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use self::subcommand::HarmonicSubcommand;
|
use self::subcommand::HarmonicSubcommand;
|
||||||
|
|
||||||
|
@ -40,3 +42,32 @@ impl CommandExecute for HarmonicCli {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn signal_channel() -> eyre::Result<(Sender<()>, Receiver<()>)> {
|
||||||
|
let (sender, reciever) = tokio::sync::broadcast::channel(100);
|
||||||
|
|
||||||
|
let sender_cloned = sender.clone();
|
||||||
|
let _guard = tokio::spawn(async move {
|
||||||
|
let mut ctrl_c = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt())
|
||||||
|
.expect("failed to install signal handler");
|
||||||
|
|
||||||
|
let mut terminate =
|
||||||
|
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
||||||
|
.expect("failed to install signal handler");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
Some(()) = ctrl_c.recv() => {
|
||||||
|
tracing::warn!("Got SIGINT signal");
|
||||||
|
sender_cloned.send(()).ok();
|
||||||
|
},
|
||||||
|
Some(()) = terminate.recv() => {
|
||||||
|
tracing::warn!("Got SIGTERM signal");
|
||||||
|
sender_cloned.send(()).ok();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok((sender, reciever))
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
use crate::BuiltinPlanner;
|
use crate::{cli::signal_channel, BuiltinPlanner, HarmonicError};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::{eyre, WrapErr};
|
use eyre::{eyre, WrapErr};
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{cli::CommandExecute, interaction};
|
use crate::{cli::CommandExecute, interaction};
|
||||||
|
|
||||||
|
@ -59,12 +60,21 @@ impl CommandExecute for Install {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = plan.install().await {
|
let (tx, rx1) = signal_channel().await?;
|
||||||
|
|
||||||
|
if let Err(err) = plan.install(rx1).await {
|
||||||
|
match err {
|
||||||
|
HarmonicError::Cancelled => {},
|
||||||
|
err => {
|
||||||
tracing::error!("{:?}", eyre!(err));
|
tracing::error!("{:?}", eyre!(err));
|
||||||
if !interaction::confirm(plan.describe_revert(explain)).await? {
|
if !interaction::confirm(plan.describe_revert(explain)).await? {
|
||||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!")
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
let rx2 = tx.subscribe();
|
||||||
|
plan.revert(rx2).await?
|
||||||
|
},
|
||||||
}
|
}
|
||||||
plan.revert().await?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
use crate::InstallPlan;
|
use crate::{cli::signal_channel, InstallPlan};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::WrapErr;
|
use eyre::WrapErr;
|
||||||
|
|
||||||
|
@ -48,7 +48,9 @@ impl CommandExecute for Uninstall {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plan.revert().await?;
|
let (tx, rx) = signal_channel().await?;
|
||||||
|
|
||||||
|
plan.revert(rx).await?;
|
||||||
// TODO(@hoverbear): It would be so nice to catch errors and offer the user a way to keep going...
|
// TODO(@hoverbear): It would be so nice to catch errors and offer the user a way to keep going...
|
||||||
// However that will require being able to link error -> step and manually setting that step as `Uncompleted`.
|
// However that will require being able to link error -> step and manually setting that step as `Uncompleted`.
|
||||||
|
|
||||||
|
|
|
@ -12,4 +12,6 @@ pub enum HarmonicError {
|
||||||
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
SerializingReceipt(serde_json::Error),
|
SerializingReceipt(serde_json::Error),
|
||||||
|
#[error("Cancelled by user")]
|
||||||
|
Cancelled,
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ use tokio::process::Command;
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
||||||
async fn execute_command(command: &mut Command) -> Result<Output, std::io::Error> {
|
async fn execute_command(command: &mut Command) -> Result<Output, std::io::Error> {
|
||||||
|
// TODO(@hoverbear): When tokio releases past 1.21.2, add a process group https://github.com/DeterminateSystems/harmonic/issues/41#issuecomment-1309513073
|
||||||
|
|
||||||
tracing::trace!("Executing");
|
tracing::trace!("Executing");
|
||||||
let command_str = format!("{:?}", command.as_std());
|
let command_str = format!("{:?}", command.as_std());
|
||||||
let output = command.output().await?;
|
let output = command.output().await?;
|
||||||
|
|
36
src/plan.rs
36
src/plan.rs
|
@ -1,6 +1,8 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription},
|
action::{Action, ActionDescription},
|
||||||
|
@ -74,16 +76,31 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn install(&mut self) -> Result<(), HarmonicError> {
|
pub async fn install(
|
||||||
|
&mut self,
|
||||||
|
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||||
|
) -> Result<(), HarmonicError> {
|
||||||
let Self {
|
let Self {
|
||||||
actions,
|
actions,
|
||||||
planner: _,
|
planner: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
let mut cancel_channel = cancel_channel.into();
|
||||||
|
|
||||||
// This is **deliberately sequential**.
|
// This is **deliberately sequential**.
|
||||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||||
// The plan itself represents the concept of the sequence of stages.
|
// The plan itself represents the concept of the sequence of stages.
|
||||||
for action in actions {
|
for action in actions {
|
||||||
|
if let Some(ref mut cancel_channel) = cancel_channel {
|
||||||
|
if cancel_channel.try_recv()
|
||||||
|
!= Err(tokio::sync::broadcast::error::TryRecvError::Empty)
|
||||||
|
{
|
||||||
|
if let Err(err) = write_receipt(self.clone()).await {
|
||||||
|
tracing::error!("Error saving receipt: {:?}", err);
|
||||||
|
}
|
||||||
|
return Err(HarmonicError::Cancelled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(err) = action.execute().await {
|
if let Err(err) = action.execute().await {
|
||||||
if let Err(err) = write_receipt(self.clone()).await {
|
if let Err(err) = write_receipt(self.clone()).await {
|
||||||
tracing::error!("Error saving receipt: {:?}", err);
|
tracing::error!("Error saving receipt: {:?}", err);
|
||||||
|
@ -140,16 +157,31 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn revert(&mut self) -> Result<(), HarmonicError> {
|
pub async fn revert(
|
||||||
|
&mut self,
|
||||||
|
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||||
|
) -> Result<(), HarmonicError> {
|
||||||
let Self {
|
let Self {
|
||||||
actions,
|
actions,
|
||||||
planner: _,
|
planner: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
let mut cancel_channel = cancel_channel.into();
|
||||||
|
|
||||||
// This is **deliberately sequential**.
|
// This is **deliberately sequential**.
|
||||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||||
// The plan itself represents the concept of the sequence of stages.
|
// The plan itself represents the concept of the sequence of stages.
|
||||||
for action in actions.iter_mut().rev() {
|
for action in actions.iter_mut().rev() {
|
||||||
|
if let Some(ref mut cancel_channel) = cancel_channel {
|
||||||
|
if cancel_channel.try_recv()
|
||||||
|
!= Err(tokio::sync::broadcast::error::TryRecvError::Empty)
|
||||||
|
{
|
||||||
|
if let Err(err) = write_receipt(self.clone()).await {
|
||||||
|
tracing::error!("Error saving receipt: {:?}", err);
|
||||||
|
}
|
||||||
|
return Err(HarmonicError::Cancelled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(err) = action.revert().await {
|
if let Err(err) = action.revert().await {
|
||||||
if let Err(err) = write_receipt(self.clone()).await {
|
if let Err(err) = write_receipt(self.clone()).await {
|
||||||
tracing::error!("Error saving receipt: {:?}", err);
|
tracing::error!("Error saving receipt: {:?}", err);
|
||||||
|
|
|
@ -32,7 +32,11 @@ pub struct DarwinMulti {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn default_root_disk() -> Result<String, BuiltinPlannerError> {
|
async fn default_root_disk() -> Result<String, BuiltinPlannerError> {
|
||||||
let buf = execute_command(Command::new("/usr/sbin/diskutil").args(["info", "-plist", "/"]))
|
let buf = execute_command(
|
||||||
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
.args(["info", "-plist", "/"])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.stdout;
|
.stdout;
|
||||||
|
@ -60,7 +64,9 @@ impl Planner for DarwinMulti {
|
||||||
root_disk @ Some(_) => root_disk,
|
root_disk @ Some(_) => root_disk,
|
||||||
None => {
|
None => {
|
||||||
let buf = execute_command(
|
let buf = execute_command(
|
||||||
Command::new("/usr/sbin/diskutil").args(["info", "-plist", "/"]),
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
.args(["info", "-plist", "/"])
|
||||||
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
Loading…
Reference in a new issue