Merge pull request #46 from DeterminateSystems/hoverbear/ds-431-ctrlc-should-be-handled-and-terminate-us

Handle signals and user stdin more gracefully
This commit is contained in:
Ana Hobden 2022-11-14 13:37:52 -08:00 committed by GitHub
commit 2dcda0a801
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 338 additions and 161 deletions

1
Cargo.lock generated
View file

@ -1943,6 +1943,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",

View file

@ -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"

View file

@ -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())?;
@ -137,7 +153,8 @@ impl Action for ConfigureNixDaemonService {
Command::new("systemctl") Command::new("systemctl")
.arg("enable") .arg("enable")
.arg("--now") .arg("--now")
.arg("nix-daemon.socket"), .arg("nix-daemon.socket")
.stdin(std::process::Stdio::null()),
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
@ -190,18 +207,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, "--now"])) execute_command(
Command::new("systemctl")
.args(["disable", SOCKET_SRC, "--now"])
.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, "--now"])) execute_command(
Command::new("systemctl")
.args(["disable", SERVICE_SRC, "--now"])
.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())?;
@ -211,7 +237,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())?;
}, },

View file

@ -72,13 +72,16 @@ impl Action for CreateGroup {
| OperatingSystem::Darwin => { | OperatingSystem::Darwin => {
if Command::new("/usr/bin/dscl") if Command::new("/usr/bin/dscl")
.args([".", "-read", &format!("/Groups/{name}")]) .args([".", "-read", &format!("/Groups/{name}")])
.stdin(std::process::Stdio::null())
.status() .status()
.await? .await?
.success() .success()
{ {
() ()
} else { } else {
execute_command(Command::new("/usr/sbin/dseditgroup").args([ execute_command(
Command::new("/usr/sbin/dseditgroup")
.args([
"-o", "-o",
"create", "create",
"-r", "-r",
@ -86,18 +89,19 @@ 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())?;
}, },
@ -157,12 +161,16 @@ impl Action for CreateGroup {
// ".", // ".",
// "-delete", // "-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())?;
}, },

View file

@ -86,53 +86,70 @@ impl Action for CreateUser {
if Command::new("/usr/bin/dscl") if Command::new("/usr/bin/dscl")
.args([".", "-read", &format!("/Users/{name}")]) .args([".", "-read", &format!("/Users/{name}")])
.stdin(std::process::Stdio::null())
.status() .status()
.await? .await?
.success() .success()
{ {
() ()
} else { } else {
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(
@ -143,17 +160,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(
@ -163,14 +179,17 @@ 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",
@ -188,7 +207,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())?;
}, },
@ -254,12 +275,16 @@ impl Action for CreateUser {
// ".", // ".",
// "-delete", // "-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())?;
}, },

View file

@ -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

View file

@ -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())?;

View file

@ -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

View file

@ -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())?;

View file

@ -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?
.stdout; .stdout;
@ -71,7 +72,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())?;

View file

@ -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())?;

View file

@ -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())?;

View file

@ -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())?;
@ -93,7 +94,8 @@ impl Action for StartSystemdUnit {
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
.arg("disable") .arg("disable")
.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())?;

View file

@ -54,7 +54,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())?;
@ -91,7 +96,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())?;

View file

@ -3,6 +3,7 @@ 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 self::subcommand::HarmonicSubcommand; use self::subcommand::HarmonicSubcommand;
@ -41,6 +42,35 @@ impl CommandExecute for HarmonicCli {
} }
} }
pub(crate) async fn signal_channel() -> eyre::Result<(Sender<()>, Receiver<()>)> {
let (sender, receiver) = 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, receiver))
}
pub fn is_root() -> bool { pub fn is_root() -> bool {
nix::unistd::getuid() == nix::unistd::Uid::from_raw(0) nix::unistd::getuid() == nix::unistd::Uid::from_raw(0)
} }

View file

@ -4,13 +4,12 @@ use std::{
}; };
use crate::{ use crate::{
action::ActionState, cli::is_root, plan::RECEIPT_LOCATION, BuiltinPlanner, InstallPlan, Planner, action::ActionState, cli::is_root, cli::signal_channel, cli::CommandExecute, interaction,
plan::RECEIPT_LOCATION, BuiltinPlanner, InstallPlan, Planner,
}; };
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
use eyre::{eyre, WrapErr}; use eyre::{eyre, WrapErr};
use crate::{cli::CommandExecute, interaction};
/// Execute an install (possibly using an existing plan) /// Execute an install (possibly using an existing plan)
/// ///
/// To pass custom options, select a planner, for example `harmonic install linux-multi --help` /// To pass custom options, select a planner, for example `harmonic install linux-multi --help`
@ -115,14 +114,17 @@ impl CommandExecute for Install {
} }
} }
if let Err(err) = install_plan.install().await { let (tx, rx1) = signal_channel().await?;
if let Err(err) = install_plan.install(rx1).await {
let error = eyre!(err).wrap_err("Install failure"); let error = eyre!(err).wrap_err("Install failure");
if !no_confirm { if !no_confirm {
tracing::error!("{:?}", error); tracing::error!("{:?}", error);
if !interaction::confirm(install_plan.describe_revert(explain)).await? { if !interaction::confirm(install_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;
} }
install_plan.revert().await? let rx2 = tx.subscribe();
install_plan.revert(rx2).await?
} else { } else {
return Err(error); return Err(error);
} }

View file

@ -1,6 +1,10 @@
use std::{path::PathBuf, process::ExitCode}; use std::{path::PathBuf, process::ExitCode};
use crate::{cli::is_root, plan::RECEIPT_LOCATION, InstallPlan}; use crate::{
cli::{is_root, signal_channel},
plan::RECEIPT_LOCATION,
InstallPlan,
};
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
use eyre::{eyre, WrapErr}; use eyre::{eyre, WrapErr};
@ -54,7 +58,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`.

View file

@ -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,
} }

View file

@ -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?;

View file

@ -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},
@ -76,16 +78,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);
@ -142,16 +159,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);

View file

@ -41,7 +41,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;
@ -70,7 +74,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()