Add more failure context / Improve error structure (#296)
* wip: add more context to errors * Add a bunch fo context * Repair source handling * Add remaining contexts * Add some context, but some of it is not right... * Tidy up contexts properly * Get command errors working how I want * Remove some debug statements * Repair mac build * Move typetag to Action * newtypes! * Fix doctest
This commit is contained in:
parent
49154b9863
commit
903258942c
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -203,6 +203,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
"tracing-error",
|
"tracing-error",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -28,7 +28,7 @@ async-trait = { version = "0.1.57", default-features = false }
|
||||||
atty = { version = "0.2.14", default-features = false, optional = true }
|
atty = { version = "0.2.14", default-features = false, optional = true }
|
||||||
bytes = { version = "1.2.1", default-features = false, features = ["std", "serde"] }
|
bytes = { version = "1.2.1", default-features = false, features = ["std", "serde"] }
|
||||||
clap = { version = "4", features = ["std", "color", "usage", "help", "error-context", "suggestions", "derive", "env"], optional = true }
|
clap = { version = "4", features = ["std", "color", "usage", "help", "error-context", "suggestions", "derive", "env"], optional = true }
|
||||||
color-eyre = { version = "0.6.2", default-features = false, features = [ "track-caller", "tracing-error", "capture-spantrace", "color-spantrace" ], optional = true }
|
color-eyre = { version = "0.6.2", default-features = false, features = [ "track-caller", "issue-url", "tracing-error", "capture-spantrace", "color-spantrace" ], optional = true }
|
||||||
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ], optional = true }
|
eyre = { version = "0.6.8", default-features = false, features = [ "track-caller" ], optional = true }
|
||||||
glob = { version = "0.3.0", default-features = false }
|
glob = { version = "0.3.0", default-features = false }
|
||||||
nix = { version = "0.26.0", default-features = false, features = ["user", "fs", "process", "term"] }
|
nix = { version = "0.26.0", default-features = false, features = ["user", "fs", "process", "term"] }
|
||||||
|
|
|
@ -70,9 +70,11 @@ impl AddUserToGroup {
|
||||||
command.arg(&this.groupname);
|
command.arg(&this.groupname);
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
command.stderr(Stdio::piped());
|
command.stderr(Stdio::piped());
|
||||||
let command_str = format!("{:?}", command.as_std());
|
tracing::trace!("Executing `{:?}`", command.as_std());
|
||||||
tracing::trace!("Executing `{command_str}`");
|
let output = command
|
||||||
let output = command.output().await.map_err(ActionError::Command)?;
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::command(&command, e))?;
|
||||||
match output.status.code() {
|
match output.status.code() {
|
||||||
Some(0) => {
|
Some(0) => {
|
||||||
// yes {user} is a member of {groupname}
|
// yes {user} is a member of {groupname}
|
||||||
|
@ -96,18 +98,7 @@ impl AddUserToGroup {
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
// Some other issue
|
// Some other issue
|
||||||
return Err(ActionError::Command(std::io::Error::new(
|
return Err(ActionError::command_output(&command, output));
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"Command `{command_str}` failed{}, stderr:\n{}\n",
|
|
||||||
if let Some(code) = output.status.code() {
|
|
||||||
format!(" status {code}")
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(&output.stderr),
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -118,8 +109,7 @@ impl AddUserToGroup {
|
||||||
.arg(&this.name)
|
.arg(&this.name)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
let output_str = String::from_utf8(output.stdout)?;
|
let output_str = String::from_utf8(output.stdout)?;
|
||||||
let user_in_group = output_str.split(" ").any(|v| v == &this.groupname);
|
let user_in_group = output_str.split(" ").any(|v| v == &this.groupname);
|
||||||
|
|
||||||
|
@ -138,6 +128,9 @@ impl AddUserToGroup {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "add_user_to_group")]
|
#[typetag::serde(name = "add_user_to_group")]
|
||||||
impl Action for AddUserToGroup {
|
impl Action for AddUserToGroup {
|
||||||
|
fn action_tag() -> crate::action::ActionTag {
|
||||||
|
crate::action::ActionTag("add_user_to_group")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Add user `{}` (UID {}) to group `{}` (GID {})",
|
"Add user `{}` (UID {}) to group `{}` (GID {})",
|
||||||
|
@ -194,8 +187,7 @@ impl Action for AddUserToGroup {
|
||||||
.arg(&name)
|
.arg(&name)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/sbin/dseditgroup")
|
Command::new("/usr/sbin/dseditgroup")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
|
@ -207,8 +199,7 @@ impl Action for AddUserToGroup {
|
||||||
.arg(groupname)
|
.arg(groupname)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -218,8 +209,7 @@ impl Action for AddUserToGroup {
|
||||||
.args([&name.to_string(), &groupname.to_string()])
|
.args([&name.to_string(), &groupname.to_string()])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,8 +252,7 @@ impl Action for AddUserToGroup {
|
||||||
.arg(&name)
|
.arg(&name)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -273,8 +262,7 @@ impl Action for AddUserToGroup {
|
||||||
.args([&name.to_string(), &groupname.to_string()])
|
.args([&name.to_string(), &groupname.to_string()])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,9 @@ impl CreateDirectory {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_directory")]
|
#[typetag::serde(name = "create_directory")]
|
||||||
impl Action for CreateDirectory {
|
impl Action for CreateDirectory {
|
||||||
|
fn action_tag() -> crate::action::ActionTag {
|
||||||
|
crate::action::ActionTag("create_directory")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Create directory `{}`", self.path.display())
|
format!("Create directory `{}`", self.path.display())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
/** Create a file at the given location with the provided `buf`,
|
/** Create a file at the given location with the provided `buf`,
|
||||||
optionally with an owning user, group, and mode.
|
optionally with an owning user, group, and mode.
|
||||||
|
@ -134,6 +134,9 @@ impl CreateFile {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_file")]
|
#[typetag::serde(name = "create_file")]
|
||||||
impl Action for CreateFile {
|
impl Action for CreateFile {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_file")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Create or overwrite file `{}`", self.path.display())
|
format!("Create or overwrite file `{}`", self.path.display())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use nix::unistd::Group;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::ActionError;
|
use crate::action::{ActionError, ActionTag};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||||
|
@ -45,6 +45,9 @@ impl CreateGroup {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_group")]
|
#[typetag::serde(name = "create_group")]
|
||||||
impl Action for CreateGroup {
|
impl Action for CreateGroup {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_group")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Create group `{}` (GID {})", self.name, self.gid)
|
format!("Create group `{}` (GID {})", self.name, self.gid)
|
||||||
}
|
}
|
||||||
|
@ -93,8 +96,7 @@ impl Action for CreateGroup {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -103,8 +105,7 @@ impl Action for CreateGroup {
|
||||||
.args(["-g", &gid.to_string(), "--system", &name])
|
.args(["-g", &gid.to_string(), "--system", &name])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -138,8 +139,7 @@ impl Action for CreateGroup {
|
||||||
.args([".", "-delete", &format!("/Groups/{name}")])
|
.args([".", "-delete", &format!("/Groups/{name}")])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
if !output.status.success() {}
|
if !output.status.success() {}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -149,8 +149,7 @@ impl Action for CreateGroup {
|
||||||
.arg(&name)
|
.arg(&name)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use nix::unistd::{chown, Group, User};
|
use nix::unistd::{chown, Group, User};
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::{
|
use std::{
|
||||||
io::SeekFrom,
|
io::SeekFrom,
|
||||||
|
@ -140,6 +140,9 @@ impl CreateOrInsertIntoFile {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_or_insert_into_file")]
|
#[typetag::serde(name = "create_or_insert_into_file")]
|
||||||
impl Action for CreateOrInsertIntoFile {
|
impl Action for CreateOrInsertIntoFile {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_or_insert_into_file")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Create or insert file `{}`", self.path.display())
|
format!("Create or insert file `{}`", self.path.display())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use nix::unistd::User;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::ActionError;
|
use crate::action::{ActionError, ActionTag};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||||
|
@ -60,6 +60,9 @@ impl CreateUser {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_user")]
|
#[typetag::serde(name = "create_user")]
|
||||||
impl Action for CreateUser {
|
impl Action for CreateUser {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_user")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Create user `{}` (UID {}) in group `{}` (GID {})",
|
"Create user `{}` (UID {}) in group `{}` (GID {})",
|
||||||
|
@ -110,8 +113,7 @@ impl Action for CreateUser {
|
||||||
.args([".", "-create", &format!("/Users/{name}")])
|
.args([".", "-create", &format!("/Users/{name}")])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/bin/dscl")
|
Command::new("/usr/bin/dscl")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
|
@ -124,8 +126,7 @@ impl Action for CreateUser {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/bin/dscl")
|
Command::new("/usr/bin/dscl")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
|
@ -138,8 +139,7 @@ impl Action for CreateUser {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/bin/dscl")
|
Command::new("/usr/bin/dscl")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
|
@ -152,8 +152,7 @@ impl Action for CreateUser {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/bin/dscl")
|
Command::new("/usr/bin/dscl")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
|
@ -166,16 +165,14 @@ impl Action for CreateUser {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/bin/dscl")
|
Command::new("/usr/bin/dscl")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
.args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"])
|
.args([".", "-create", &format!("/Users/{name}"), "IsHidden", "1"])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -202,8 +199,7 @@ impl Action for CreateUser {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +247,7 @@ impl Action for CreateUser {
|
||||||
let output = command
|
let output = command
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
.map_err(|e| ActionError::command(&command, e))?;
|
||||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
match output.status.code() {
|
match output.status.code() {
|
||||||
Some(0) => (),
|
Some(0) => (),
|
||||||
|
@ -260,21 +256,9 @@ impl Action for CreateUser {
|
||||||
// These Macs cannot always delete users, as sometimes there is no graphical login
|
// These Macs cannot always delete users, as sometimes there is no graphical login
|
||||||
tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{name}");
|
tracing::warn!("Encountered an exit code 40 with -14120 error while removing user, this is likely because the initial executing user did not have a secure token, or that there was no graphical login session. To delete the user, log in graphically, then run `/usr/bin/dscl . -delete /Users/{name}");
|
||||||
},
|
},
|
||||||
status => {
|
_ => {
|
||||||
let command_str = format!("{:?}", command.as_std());
|
|
||||||
// Something went wrong
|
// Something went wrong
|
||||||
return Err(ActionError::Command(std::io::Error::new(
|
return Err(ActionError::command_output(&command, output));
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"Command `{command_str}` failed{}, stderr:\n{}\n",
|
|
||||||
if let Some(status) = status {
|
|
||||||
format!(" {status}")
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
},
|
|
||||||
stderr
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -285,8 +269,7 @@ impl Action for CreateUser {
|
||||||
.args([&name.to_string()])
|
.args([&name.to_string()])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use bytes::{Buf, Bytes};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Fetch a URL to the given path
|
Fetch a URL to the given path
|
||||||
|
@ -37,6 +37,9 @@ impl FetchAndUnpackNix {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "fetch_and_unpack_nix")]
|
#[typetag::serde(name = "fetch_and_unpack_nix")]
|
||||||
impl Action for FetchAndUnpackNix {
|
impl Action for FetchAndUnpackNix {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("fetch_and_unpack_nix")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Fetch `{}` to `{}`", self.url, self.dest.display())
|
format!("Fetch `{}` to `{}`", self.url, self.dest.display())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
pub(crate) const DEST: &str = "/nix/";
|
pub(crate) const DEST: &str = "/nix/";
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ impl MoveUnpackedNix {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "mount_unpacked_nix")]
|
#[typetag::serde(name = "mount_unpacked_nix")]
|
||||||
impl Action for MoveUnpackedNix {
|
impl Action for MoveUnpackedNix {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("move_unpacked_nix")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Move the downloaded Nix into `/nix`".to_string()
|
"Move the downloaded Nix into `/nix`".to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{ActionError, StatefulAction},
|
action::{ActionError, ActionTag, StatefulAction},
|
||||||
execute_command, set_env, ChannelValue,
|
execute_command, set_env, ChannelValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ impl SetupDefaultProfile {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "setup_default_profile")]
|
#[typetag::serde(name = "setup_default_profile")]
|
||||||
impl Action for SetupDefaultProfile {
|
impl Action for SetupDefaultProfile {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("setup_default_profile")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Setup the default Nix profile".to_string()
|
"Setup the default Nix profile".to_string()
|
||||||
}
|
}
|
||||||
|
@ -119,14 +122,14 @@ impl Action for SetupDefaultProfile {
|
||||||
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
ActionError::Custom(Box::new(SetupDefaultProfileError::NoRootHome))
|
||||||
})?,
|
})?,
|
||||||
);
|
);
|
||||||
let load_db_command_str = format!("{:?}", load_db_command.as_std());
|
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"Executing `{load_db_command_str}` with stdin from `{}`",
|
"Executing `{:?}` with stdin from `{}`",
|
||||||
|
load_db_command.as_std(),
|
||||||
reginfo_path.display()
|
reginfo_path.display()
|
||||||
);
|
);
|
||||||
let mut handle = load_db_command
|
let mut handle = load_db_command
|
||||||
.spawn()
|
.spawn()
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
.map_err(|e| ActionError::command(&load_db_command, e))?;
|
||||||
|
|
||||||
let mut stdin = handle.stdin.take().unwrap();
|
let mut stdin = handle.stdin.take().unwrap();
|
||||||
stdin
|
stdin
|
||||||
|
@ -146,20 +149,9 @@ impl Action for SetupDefaultProfile {
|
||||||
let output = handle
|
let output = handle
|
||||||
.wait_with_output()
|
.wait_with_output()
|
||||||
.await
|
.await
|
||||||
.map_err(ActionError::Command)?;
|
.map_err(|e| ActionError::command(&load_db_command, e))?;
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(ActionError::Command(std::io::Error::new(
|
return Err(ActionError::command_output(&load_db_command, output));
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"Command `{load_db_command_str}` failed{}, stderr:\n{}\n",
|
|
||||||
if let Some(code) = output.status.code() {
|
|
||||||
format!(" status {code}")
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
),
|
|
||||||
)));
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +173,7 @@ impl Action for SetupDefaultProfile {
|
||||||
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
||||||
), /* This is apparently load bearing... */
|
), /* This is apparently load bearing... */
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
// Install `nix` itself into the store
|
// Install `nix` itself into the store
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -202,8 +193,7 @@ impl Action for SetupDefaultProfile {
|
||||||
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
nss_ca_cert_pkg.join("etc/ssl/certs/ca-bundle.crt"),
|
||||||
), /* This is apparently load bearing... */
|
), /* This is apparently load bearing... */
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
set_env(
|
set_env(
|
||||||
"NIX_SSL_CERT_FILE",
|
"NIX_SSL_CERT_FILE",
|
||||||
|
@ -223,9 +213,7 @@ impl Action for SetupDefaultProfile {
|
||||||
);
|
);
|
||||||
command.stdin(std::process::Stdio::null());
|
command.stdin(std::process::Stdio::null());
|
||||||
|
|
||||||
execute_command(&mut command)
|
execute_command(&mut command).await?;
|
||||||
.await
|
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{ActionError, StatefulAction};
|
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
@ -42,8 +42,11 @@ impl ConfigureInitService {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "configure_nix_daemon")]
|
#[typetag::serde(name = "configure_init_service")]
|
||||||
impl Action for ConfigureInitService {
|
impl Action for ConfigureInitService {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("configure_init_service")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
match self.init {
|
match self.init {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -58,7 +61,7 @@ impl Action for ConfigureInitService {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tracing_span(&self) -> Span {
|
fn tracing_span(&self) -> Span {
|
||||||
span!(tracing::Level::DEBUG, "configure_nix_daemon",)
|
span!(tracing::Level::DEBUG, "configure_init_service",)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
|
@ -118,8 +121,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg(DARWIN_NIX_DAEMON_DEST)
|
.arg(DARWIN_NIX_DAEMON_DEST)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
if *start_daemon {
|
if *start_daemon {
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -130,8 +132,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("system/org.nixos.nix-daemon")
|
.arg("system/org.nixos.nix-daemon")
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
@ -154,8 +155,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("--prefix=/nix/var/nix")
|
.arg("--prefix=/nix/var/nix")
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
|
@ -164,8 +164,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg(SERVICE_SRC)
|
.arg(SERVICE_SRC)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
|
@ -174,8 +173,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg(SOCKET_SRC)
|
.arg(SOCKET_SRC)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
if *start_daemon {
|
if *start_daemon {
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -184,8 +182,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("daemon-reload")
|
.arg("daemon-reload")
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("systemctl")
|
Command::new("systemctl")
|
||||||
|
@ -194,8 +191,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("--now")
|
.arg("--now")
|
||||||
.arg(SOCKET_SRC),
|
.arg(SOCKET_SRC),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
@ -244,8 +240,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("unload")
|
.arg("unload")
|
||||||
.arg(DARWIN_NIX_DAEMON_DEST),
|
.arg(DARWIN_NIX_DAEMON_DEST),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
},
|
},
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
InitSystem::Systemd => {
|
InitSystem::Systemd => {
|
||||||
|
@ -263,8 +258,7 @@ impl Action for ConfigureInitService {
|
||||||
.args(["stop", "nix-daemon.socket"])
|
.args(["stop", "nix-daemon.socket"])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if socket_is_enabled {
|
if socket_is_enabled {
|
||||||
|
@ -274,8 +268,7 @@ impl Action for ConfigureInitService {
|
||||||
.args(["disable", "nix-daemon.socket"])
|
.args(["disable", "nix-daemon.socket"])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if service_is_active {
|
if service_is_active {
|
||||||
|
@ -285,8 +278,7 @@ impl Action for ConfigureInitService {
|
||||||
.args(["stop", "nix-daemon.service"])
|
.args(["stop", "nix-daemon.service"])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if service_is_enabled {
|
if service_is_enabled {
|
||||||
|
@ -296,8 +288,7 @@ impl Action for ConfigureInitService {
|
||||||
.args(["disable", "nix-daemon.service"])
|
.args(["disable", "nix-daemon.service"])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -307,8 +298,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("--prefix=/nix/var/nix")
|
.arg("--prefix=/nix/var/nix")
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
tokio::fs::remove_file(TMPFILES_DEST)
|
tokio::fs::remove_file(TMPFILES_DEST)
|
||||||
.await
|
.await
|
||||||
|
@ -320,8 +310,7 @@ impl Action for ConfigureInitService {
|
||||||
.arg("daemon-reload")
|
.arg("daemon-reload")
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
},
|
},
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
InitSystem::None => {
|
InitSystem::None => {
|
||||||
|
@ -342,12 +331,13 @@ pub enum ConfigureNixDaemonServiceError {
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
async fn is_active(unit: &str) -> Result<bool, ActionError> {
|
async fn is_active(unit: &str) -> Result<bool, ActionError> {
|
||||||
let output = Command::new("systemctl")
|
let mut command = Command::new("systemctl");
|
||||||
.arg("is-active")
|
command.arg("is-active");
|
||||||
.arg(unit)
|
command.arg(unit);
|
||||||
|
let output = command
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(ActionError::Command)?;
|
.map_err(|e| ActionError::command(&command, e))?;
|
||||||
if String::from_utf8(output.stdout)?.starts_with("active") {
|
if String::from_utf8(output.stdout)?.starts_with("active") {
|
||||||
tracing::trace!(%unit, "Is active");
|
tracing::trace!(%unit, "Is active");
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
@ -359,12 +349,13 @@ async fn is_active(unit: &str) -> Result<bool, ActionError> {
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
async fn is_enabled(unit: &str) -> Result<bool, ActionError> {
|
async fn is_enabled(unit: &str) -> Result<bool, ActionError> {
|
||||||
let output = Command::new("systemctl")
|
let mut command = Command::new("systemctl");
|
||||||
.arg("is-enabled")
|
command.arg("is-enabled");
|
||||||
.arg(unit)
|
command.arg(unit);
|
||||||
|
let output = command
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(ActionError::Command)?;
|
.map_err(|e| ActionError::command(&command, e))?;
|
||||||
let stdout = String::from_utf8(output.stdout)?;
|
let stdout = String::from_utf8(output.stdout)?;
|
||||||
if stdout.starts_with("enabled") || stdout.starts_with("linked") {
|
if stdout.starts_with("enabled") || stdout.starts_with("linked") {
|
||||||
tracing::trace!(%unit, "Is enabled");
|
tracing::trace!(%unit, "Is enabled");
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
action::{
|
action::{
|
||||||
base::SetupDefaultProfile,
|
base::SetupDefaultProfile,
|
||||||
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
|
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
|
||||||
Action, ActionDescription, ActionError, StatefulAction,
|
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||||
},
|
},
|
||||||
settings::CommonSettings,
|
settings::CommonSettings,
|
||||||
};
|
};
|
||||||
|
@ -23,21 +23,30 @@ pub struct ConfigureNix {
|
||||||
impl ConfigureNix {
|
impl ConfigureNix {
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
|
pub async fn plan(settings: &CommonSettings) -> Result<StatefulAction<Self>, ActionError> {
|
||||||
let setup_default_profile = SetupDefaultProfile::plan(settings.channels.clone()).await?;
|
let setup_default_profile = SetupDefaultProfile::plan(settings.channels.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(SetupDefaultProfile::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let configure_shell_profile = if settings.modify_profile {
|
let configure_shell_profile = if settings.modify_profile {
|
||||||
Some(ConfigureShellProfile::plan().await?)
|
Some(ConfigureShellProfile::plan().await.map_err(|e| {
|
||||||
|
ActionError::Child(ConfigureShellProfile::action_tag(), Box::new(e))
|
||||||
|
})?)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let place_channel_configuration =
|
let place_channel_configuration =
|
||||||
PlaceChannelConfiguration::plan(settings.channels.clone(), settings.force).await?;
|
PlaceChannelConfiguration::plan(settings.channels.clone(), settings.force)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(PlaceChannelConfiguration::action_tag(), Box::new(e))
|
||||||
|
})?;
|
||||||
let place_nix_configuration = PlaceNixConfiguration::plan(
|
let place_nix_configuration = PlaceNixConfiguration::plan(
|
||||||
settings.nix_build_group_name.clone(),
|
settings.nix_build_group_name.clone(),
|
||||||
settings.extra_conf.clone(),
|
settings.extra_conf.clone(),
|
||||||
settings.force,
|
settings.force,
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(PlaceNixConfiguration::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
|
@ -52,6 +61,9 @@ impl ConfigureNix {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "configure_nix")]
|
#[typetag::serde(name = "configure_nix")]
|
||||||
impl Action for ConfigureNix {
|
impl Action for ConfigureNix {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("configure_nix")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Configure Nix".to_string()
|
"Configure Nix".to_string()
|
||||||
}
|
}
|
||||||
|
@ -103,24 +115,39 @@ impl Action for ConfigureNix {
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(setup_default_profile_span)
|
.instrument(setup_default_profile_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(setup_default_profile.action_tag(), Box::new(e))
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async move {
|
async move {
|
||||||
place_nix_configuration
|
place_nix_configuration
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(place_nix_configuration_span)
|
.instrument(place_nix_configuration_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(place_nix_configuration.action_tag(), Box::new(e))
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async move {
|
async move {
|
||||||
place_channel_configuration
|
place_channel_configuration
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(place_channel_configuration_span)
|
.instrument(place_channel_configuration_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(
|
||||||
|
place_channel_configuration.action_tag(),
|
||||||
|
Box::new(e),
|
||||||
|
)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async move {
|
async move {
|
||||||
configure_shell_profile
|
configure_shell_profile
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(configure_shell_profile_span)
|
.instrument(configure_shell_profile_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(configure_shell_profile.action_tag(), Box::new(e))
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,18 +162,30 @@ impl Action for ConfigureNix {
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(setup_default_profile_span)
|
.instrument(setup_default_profile_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(setup_default_profile.action_tag(), Box::new(e))
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async move {
|
async move {
|
||||||
place_nix_configuration
|
place_nix_configuration
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(place_nix_configuration_span)
|
.instrument(place_nix_configuration_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(place_nix_configuration.action_tag(), Box::new(e))
|
||||||
|
})
|
||||||
},
|
},
|
||||||
async move {
|
async move {
|
||||||
place_channel_configuration
|
place_channel_configuration
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(place_channel_configuration_span)
|
.instrument(place_channel_configuration_span)
|
||||||
.await
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(
|
||||||
|
place_channel_configuration.action_tag(),
|
||||||
|
Box::new(e),
|
||||||
|
)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
};
|
};
|
||||||
|
@ -175,19 +214,26 @@ impl Action for ConfigureNix {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
if let Some(configure_shell_profile) = &mut self.configure_shell_profile {
|
||||||
setup_default_profile,
|
configure_shell_profile.try_revert().await.map_err(|e| {
|
||||||
place_nix_configuration,
|
ActionError::Child(configure_shell_profile.action_tag(), Box::new(e))
|
||||||
place_channel_configuration,
|
})?;
|
||||||
configure_shell_profile,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
if let Some(configure_shell_profile) = configure_shell_profile {
|
|
||||||
configure_shell_profile.try_revert().await?;
|
|
||||||
}
|
}
|
||||||
place_channel_configuration.try_revert().await?;
|
self.place_channel_configuration
|
||||||
place_nix_configuration.try_revert().await?;
|
.try_revert()
|
||||||
setup_default_profile.try_revert().await?;
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(self.place_channel_configuration.action_tag(), Box::new(e))
|
||||||
|
})?;
|
||||||
|
self.place_nix_configuration
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(self.place_nix_configuration.action_tag(), Box::new(e))
|
||||||
|
})?;
|
||||||
|
self.setup_default_profile.try_revert().await.map_err(|e| {
|
||||||
|
ActionError::Child(self.setup_default_profile.action_tag(), Box::new(e))
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::action::base::{create_or_insert_into_file, CreateDirectory, CreateOrInsertIntoFile};
|
use crate::action::base::{create_or_insert_into_file, CreateDirectory, CreateOrInsertIntoFile};
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
use nix::unistd::User;
|
use nix::unistd::User;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -166,6 +166,9 @@ impl ConfigureShellProfile {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "configure_shell_profile")]
|
#[typetag::serde(name = "configure_shell_profile")]
|
||||||
impl Action for ConfigureShellProfile {
|
impl Action for ConfigureShellProfile {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("configure_shell_profile")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Configure the shell profiles".to_string()
|
"Configure the shell profiles".to_string()
|
||||||
}
|
}
|
||||||
|
@ -183,26 +186,29 @@ impl Action for ConfigureShellProfile {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
for create_directory in &mut self.create_directories {
|
||||||
create_or_insert_into_files,
|
|
||||||
create_directories,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
for create_directory in create_directories {
|
|
||||||
create_directory.try_execute().await?;
|
create_directory.try_execute().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
let mut errors = Vec::default();
|
let mut errors = Vec::default();
|
||||||
|
|
||||||
for (idx, create_or_insert_into_file) in create_or_insert_into_files.iter().enumerate() {
|
for (idx, create_or_insert_into_file) in
|
||||||
|
self.create_or_insert_into_files.iter_mut().enumerate()
|
||||||
|
{
|
||||||
let span = tracing::Span::current().clone();
|
let span = tracing::Span::current().clone();
|
||||||
let mut create_or_insert_into_file_clone = create_or_insert_into_file.clone();
|
let mut create_or_insert_into_file_clone = create_or_insert_into_file.clone();
|
||||||
let _abort_handle = set.spawn(async move {
|
let _abort_handle = set.spawn(async move {
|
||||||
create_or_insert_into_file_clone
|
create_or_insert_into_file_clone
|
||||||
.try_execute()
|
.try_execute()
|
||||||
.instrument(span)
|
.instrument(span)
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(
|
||||||
|
create_or_insert_into_file_clone.action_tag(),
|
||||||
|
Box::new(e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
Result::<_, ActionError>::Ok((idx, create_or_insert_into_file_clone))
|
Result::<_, ActionError>::Ok((idx, create_or_insert_into_file_clone))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -210,18 +216,20 @@ impl Action for ConfigureShellProfile {
|
||||||
while let Some(result) = set.join_next().await {
|
while let Some(result) = set.join_next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok((idx, create_or_insert_into_file))) => {
|
Ok(Ok((idx, create_or_insert_into_file))) => {
|
||||||
create_or_insert_into_files[idx] = create_or_insert_into_file
|
self.create_or_insert_into_files[idx] = create_or_insert_into_file
|
||||||
},
|
},
|
||||||
Ok(Err(e)) => errors.push(Box::new(e)),
|
Ok(Err(e)) => errors.push(e),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e)?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap().into());
|
return Err(errors.into_iter().next().unwrap())?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ActionError::Children(errors));
|
return Err(ActionError::Children(
|
||||||
|
errors.into_iter().map(|v| Box::new(v)).collect(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,15 +245,12 @@ impl Action for ConfigureShellProfile {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
|
||||||
create_directories,
|
|
||||||
create_or_insert_into_files,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
let mut errors: Vec<Box<ActionError>> = Vec::default();
|
let mut errors: Vec<ActionError> = Vec::default();
|
||||||
|
|
||||||
for (idx, create_or_insert_into_file) in create_or_insert_into_files.iter().enumerate() {
|
for (idx, create_or_insert_into_file) in
|
||||||
|
self.create_or_insert_into_files.iter_mut().enumerate()
|
||||||
|
{
|
||||||
let mut create_or_insert_file_clone = create_or_insert_into_file.clone();
|
let mut create_or_insert_file_clone = create_or_insert_into_file.clone();
|
||||||
let _abort_handle = set.spawn(async move {
|
let _abort_handle = set.spawn(async move {
|
||||||
create_or_insert_file_clone.try_revert().await?;
|
create_or_insert_file_clone.try_revert().await?;
|
||||||
|
@ -256,22 +261,27 @@ impl Action for ConfigureShellProfile {
|
||||||
while let Some(result) = set.join_next().await {
|
while let Some(result) = set.join_next().await {
|
||||||
match result {
|
match result {
|
||||||
Ok(Ok((idx, create_or_insert_into_file))) => {
|
Ok(Ok((idx, create_or_insert_into_file))) => {
|
||||||
create_or_insert_into_files[idx] = create_or_insert_into_file
|
self.create_or_insert_into_files[idx] = create_or_insert_into_file
|
||||||
},
|
},
|
||||||
Ok(Err(e)) => errors.push(Box::new(e)),
|
Ok(Err(e)) => errors.push(e),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e)?,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for create_directory in create_directories {
|
for create_directory in self.create_directories.iter_mut() {
|
||||||
create_directory.try_revert().await?;
|
create_directory
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_directory.action_tag(), Box::new(e)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap().into());
|
return Err(errors.into_iter().next().unwrap())?;
|
||||||
} else {
|
} else {
|
||||||
return Err(ActionError::Children(errors));
|
return Err(ActionError::Children(
|
||||||
|
errors.into_iter().map(|v| Box::new(v)).collect(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::base::CreateDirectory;
|
use crate::action::base::CreateDirectory;
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
const PATHS: &[&str] = &[
|
const PATHS: &[&str] = &[
|
||||||
"/nix/var",
|
"/nix/var",
|
||||||
|
@ -33,7 +33,11 @@ impl CreateNixTree {
|
||||||
let mut create_directories = Vec::default();
|
let mut create_directories = Vec::default();
|
||||||
for path in PATHS {
|
for path in PATHS {
|
||||||
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
|
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
|
||||||
create_directories.push(CreateDirectory::plan(path, None, None, 0o0755, false).await?)
|
create_directories.push(
|
||||||
|
CreateDirectory::plan(path, None, None, 0o0755, false)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateDirectory::action_tag(), Box::new(e)))?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self { create_directories }.into())
|
Ok(Self { create_directories }.into())
|
||||||
|
@ -43,6 +47,9 @@ impl CreateNixTree {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_nix_tree")]
|
#[typetag::serde(name = "create_nix_tree")]
|
||||||
impl Action for CreateNixTree {
|
impl Action for CreateNixTree {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_nix_tree")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Create a directory tree in `/nix`".to_string()
|
"Create a directory tree in `/nix`".to_string()
|
||||||
}
|
}
|
||||||
|
@ -68,11 +75,12 @@ impl Action for CreateNixTree {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self { create_directories } = self;
|
|
||||||
|
|
||||||
// Just do sequential since parallelizing this will have little benefit
|
// Just do sequential since parallelizing this will have little benefit
|
||||||
for create_directory in create_directories {
|
for create_directory in self.create_directories.iter_mut() {
|
||||||
create_directory.try_execute().await?
|
create_directory
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_directory.action_tag(), Box::new(e)))?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -100,11 +108,12 @@ impl Action for CreateNixTree {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self { create_directories } = self;
|
|
||||||
|
|
||||||
// Just do sequential since parallelizing this will have little benefit
|
// Just do sequential since parallelizing this will have little benefit
|
||||||
for create_directory in create_directories.iter_mut().rev() {
|
for create_directory in self.create_directories.iter_mut().rev() {
|
||||||
create_directory.try_revert().await?
|
create_directory
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_directory.action_tag(), Box::new(e)))?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{
|
action::{
|
||||||
base::{AddUserToGroup, CreateGroup, CreateUser},
|
base::{AddUserToGroup, CreateGroup, CreateUser},
|
||||||
Action, ActionDescription, ActionError, StatefulAction,
|
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||||
},
|
},
|
||||||
settings::CommonSettings,
|
settings::CommonSettings,
|
||||||
};
|
};
|
||||||
|
@ -37,7 +37,8 @@ impl CreateUsersAndGroups {
|
||||||
settings.nix_build_group_name.clone(),
|
settings.nix_build_group_name.clone(),
|
||||||
settings.nix_build_group_id,
|
settings.nix_build_group_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateUser::action_tag(), Box::new(e)))?,
|
||||||
);
|
);
|
||||||
add_users_to_groups.push(
|
add_users_to_groups.push(
|
||||||
AddUserToGroup::plan(
|
AddUserToGroup::plan(
|
||||||
|
@ -46,7 +47,8 @@ impl CreateUsersAndGroups {
|
||||||
settings.nix_build_group_name.clone(),
|
settings.nix_build_group_name.clone(),
|
||||||
settings.nix_build_group_id,
|
settings.nix_build_group_id,
|
||||||
)
|
)
|
||||||
.await?,
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(AddUserToGroup::action_tag(), Box::new(e)))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -66,6 +68,9 @@ impl CreateUsersAndGroups {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_users_and_group")]
|
#[typetag::serde(name = "create_users_and_group")]
|
||||||
impl Action for CreateUsersAndGroups {
|
impl Action for CreateUsersAndGroups {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_users_and_group")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Create build users (UID {}-{}) and group (GID {})",
|
"Create build users (UID {}-{}) and group (GID {})",
|
||||||
|
@ -151,12 +156,18 @@ impl Action for CreateUsersAndGroups {
|
||||||
}
|
}
|
||||||
| OperatingSystem::Darwin => {
|
| OperatingSystem::Darwin => {
|
||||||
for create_user in create_users.iter_mut() {
|
for create_user in create_users.iter_mut() {
|
||||||
create_user.try_execute().await?;
|
create_user
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_user.action_tag(), Box::new(e)))?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
for create_user in create_users.iter_mut() {
|
for create_user in create_users.iter_mut() {
|
||||||
create_user.try_execute().await?;
|
create_user
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_user.action_tag(), Box::new(e)))?;
|
||||||
}
|
}
|
||||||
// While we may be tempted to do something like this, it can break on many older OSes like Ubuntu 18.04:
|
// While we may be tempted to do something like this, it can break on many older OSes like Ubuntu 18.04:
|
||||||
// ```
|
// ```
|
||||||
|
@ -194,7 +205,10 @@ impl Action for CreateUsersAndGroups {
|
||||||
};
|
};
|
||||||
|
|
||||||
for add_user_to_group in add_users_to_groups.iter_mut() {
|
for add_user_to_group in add_users_to_groups.iter_mut() {
|
||||||
add_user_to_group.try_execute().await?;
|
add_user_to_group
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(add_user_to_group.action_tag(), Box::new(e)))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -260,7 +274,11 @@ impl Action for CreateUsersAndGroups {
|
||||||
let span = tracing::Span::current().clone();
|
let span = tracing::Span::current().clone();
|
||||||
let mut create_user_clone = create_user.clone();
|
let mut create_user_clone = create_user.clone();
|
||||||
let _abort_handle = set.spawn(async move {
|
let _abort_handle = set.spawn(async move {
|
||||||
create_user_clone.try_revert().instrument(span).await?;
|
create_user_clone
|
||||||
|
.try_revert()
|
||||||
|
.instrument(span)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_user_clone.action_tag(), Box::new(e)))?;
|
||||||
Result::<_, ActionError>::Ok((idx, create_user_clone))
|
Result::<_, ActionError>::Ok((idx, create_user_clone))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -275,7 +293,7 @@ impl Action for CreateUsersAndGroups {
|
||||||
|
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap().into());
|
return Err(*errors.into_iter().next().unwrap());
|
||||||
} else {
|
} else {
|
||||||
return Err(ActionError::Children(errors));
|
return Err(ActionError::Children(errors));
|
||||||
}
|
}
|
||||||
|
@ -287,7 +305,10 @@ impl Action for CreateUsersAndGroups {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
create_group.try_revert().await?;
|
create_group
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(create_group.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::action::base::CreateFile;
|
use crate::action::base::CreateFile;
|
||||||
use crate::action::ActionError;
|
|
||||||
use crate::action::{Action, ActionDescription, StatefulAction};
|
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||||
|
use crate::action::{ActionError, ActionTag};
|
||||||
use crate::ChannelValue;
|
use crate::ChannelValue;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
|
@ -36,7 +36,8 @@ impl PlaceChannelConfiguration {
|
||||||
buf,
|
buf,
|
||||||
force,
|
force,
|
||||||
)
|
)
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateFile::action_tag(), Box::new(e)))?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels,
|
channels,
|
||||||
|
@ -48,6 +49,9 @@ impl PlaceChannelConfiguration {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "place_channel_configuration")]
|
#[typetag::serde(name = "place_channel_configuration")]
|
||||||
impl Action for PlaceChannelConfiguration {
|
impl Action for PlaceChannelConfiguration {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("place_channel_configuration")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Place channel configuration at `{}`",
|
"Place channel configuration at `{}`",
|
||||||
|
@ -74,12 +78,10 @@ impl Action for PlaceChannelConfiguration {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
self.create_file
|
||||||
create_file,
|
.try_execute()
|
||||||
channels: _,
|
.await
|
||||||
} = self;
|
.map_err(|e| ActionError::Child(self.create_file.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
create_file.try_execute().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -96,12 +98,10 @@ impl Action for PlaceChannelConfiguration {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
self.create_file
|
||||||
create_file,
|
.try_revert()
|
||||||
channels: _,
|
.await
|
||||||
} = self;
|
.map_err(|e| ActionError::Child(self.create_file.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
create_file.try_revert().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::base::{CreateDirectory, CreateFile};
|
use crate::action::base::{CreateDirectory, CreateFile};
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
const NIX_CONF_FOLDER: &str = "/etc/nix";
|
const NIX_CONF_FOLDER: &str = "/etc/nix";
|
||||||
const NIX_CONF: &str = "/etc/nix/nix.conf";
|
const NIX_CONF: &str = "/etc/nix/nix.conf";
|
||||||
|
@ -41,9 +41,12 @@ impl PlaceNixConfiguration {
|
||||||
extra_conf = extra_conf.join("\n"),
|
extra_conf = extra_conf.join("\n"),
|
||||||
version = env!("CARGO_PKG_VERSION"),
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
);
|
);
|
||||||
let create_directory =
|
let create_directory = CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force)
|
||||||
CreateDirectory::plan(NIX_CONF_FOLDER, None, None, 0o0755, force).await?;
|
.await
|
||||||
let create_file = CreateFile::plan(NIX_CONF, None, None, 0o0664, buf, force).await?;
|
.map_err(|e| ActionError::Child(CreateDirectory::action_tag(), Box::new(e)))?;
|
||||||
|
let create_file = CreateFile::plan(NIX_CONF, None, None, 0o0664, buf, force)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateFile::action_tag(), Box::new(e)))?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_directory,
|
create_directory,
|
||||||
create_file,
|
create_file,
|
||||||
|
@ -55,6 +58,9 @@ impl PlaceNixConfiguration {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "place_nix_configuration")]
|
#[typetag::serde(name = "place_nix_configuration")]
|
||||||
impl Action for PlaceNixConfiguration {
|
impl Action for PlaceNixConfiguration {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("place_nix_configuration")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Place the Nix configuration in `{NIX_CONF}`")
|
format!("Place the Nix configuration in `{NIX_CONF}`")
|
||||||
}
|
}
|
||||||
|
@ -75,13 +81,14 @@ impl Action for PlaceNixConfiguration {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
self.create_directory
|
||||||
create_file,
|
.try_execute()
|
||||||
create_directory,
|
.await
|
||||||
} = self;
|
.map_err(|e| ActionError::Child(self.create_directory.action_tag(), Box::new(e)))?;
|
||||||
|
self.create_file
|
||||||
create_directory.try_execute().await?;
|
.try_execute()
|
||||||
create_file.try_execute().await?;
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.create_file.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -98,13 +105,14 @@ impl Action for PlaceNixConfiguration {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
self.create_file
|
||||||
create_file,
|
.try_revert()
|
||||||
create_directory,
|
.await
|
||||||
} = self;
|
.map_err(|e| ActionError::Child(self.create_file.action_tag(), Box::new(e)))?;
|
||||||
|
self.create_directory
|
||||||
create_file.try_revert().await?;
|
.try_revert()
|
||||||
create_directory.try_revert().await?;
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.create_directory.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use super::{CreateNixTree, CreateUsersAndGroups};
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{
|
action::{
|
||||||
base::{FetchAndUnpackNix, MoveUnpackedNix},
|
base::{FetchAndUnpackNix, MoveUnpackedNix},
|
||||||
Action, ActionDescription, ActionError, StatefulAction,
|
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||||
},
|
},
|
||||||
settings::CommonSettings,
|
settings::CommonSettings,
|
||||||
};
|
};
|
||||||
|
@ -29,10 +29,15 @@ impl ProvisionNix {
|
||||||
PathBuf::from("/nix/temp-install-dir"),
|
PathBuf::from("/nix/temp-install-dir"),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone()).await?;
|
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone())
|
||||||
let create_nix_tree = CreateNixTree::plan().await?;
|
.await
|
||||||
let move_unpacked_nix =
|
.map_err(|e| ActionError::Child(CreateUsersAndGroups::action_tag(), Box::new(e)))?;
|
||||||
MoveUnpackedNix::plan(PathBuf::from("/nix/temp-install-dir")).await?;
|
let create_nix_tree = CreateNixTree::plan()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateNixTree::action_tag(), Box::new(e)))?;
|
||||||
|
let move_unpacked_nix = MoveUnpackedNix::plan(PathBuf::from("/nix/temp-install-dir"))
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(MoveUnpackedNix::action_tag(), Box::new(e)))?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
fetch_nix,
|
fetch_nix,
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
|
@ -46,6 +51,9 @@ impl ProvisionNix {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "provision_nix")]
|
#[typetag::serde(name = "provision_nix")]
|
||||||
impl Action for ProvisionNix {
|
impl Action for ProvisionNix {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("provision_nix")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Provision Nix".to_string()
|
"Provision Nix".to_string()
|
||||||
}
|
}
|
||||||
|
@ -73,25 +81,32 @@ impl Action for ProvisionNix {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
|
||||||
fetch_nix,
|
|
||||||
create_nix_tree,
|
|
||||||
create_users_and_group,
|
|
||||||
move_unpacked_nix,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
// We fetch nix while doing the rest, then move it over.
|
// We fetch nix while doing the rest, then move it over.
|
||||||
let mut fetch_nix_clone = fetch_nix.clone();
|
let mut fetch_nix_clone = self.fetch_nix.clone();
|
||||||
let fetch_nix_handle = tokio::task::spawn(async {
|
let fetch_nix_handle = tokio::task::spawn(async {
|
||||||
fetch_nix_clone.try_execute().await?;
|
fetch_nix_clone
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(fetch_nix_clone.action_tag(), Box::new(e)))?;
|
||||||
Result::<_, ActionError>::Ok(fetch_nix_clone)
|
Result::<_, ActionError>::Ok(fetch_nix_clone)
|
||||||
});
|
});
|
||||||
|
|
||||||
create_users_and_group.try_execute().await?;
|
self.create_users_and_group
|
||||||
create_nix_tree.try_execute().await?;
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(self.create_users_and_group.action_tag(), Box::new(e))
|
||||||
|
})?;
|
||||||
|
self.create_nix_tree
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.create_nix_tree.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
*fetch_nix = fetch_nix_handle.await.map_err(ActionError::Join)??;
|
self.fetch_nix = fetch_nix_handle.await.map_err(ActionError::Join)??;
|
||||||
move_unpacked_nix.try_execute().await?;
|
self.move_unpacked_nix
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.move_unpacked_nix.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -114,31 +129,33 @@ impl Action for ProvisionNix {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
|
||||||
fetch_nix,
|
|
||||||
create_nix_tree,
|
|
||||||
create_users_and_group,
|
|
||||||
move_unpacked_nix,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
// We fetch nix while doing the rest, then move it over.
|
// We fetch nix while doing the rest, then move it over.
|
||||||
let mut fetch_nix_clone = fetch_nix.clone();
|
let mut fetch_nix_clone = self.fetch_nix.clone();
|
||||||
let fetch_nix_handle = tokio::task::spawn(async {
|
let fetch_nix_handle = tokio::task::spawn(async {
|
||||||
fetch_nix_clone.try_revert().await?;
|
fetch_nix_clone
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(fetch_nix_clone.action_tag(), Box::new(e)))?;
|
||||||
Result::<_, ActionError>::Ok(fetch_nix_clone)
|
Result::<_, ActionError>::Ok(fetch_nix_clone)
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(err) = create_users_and_group.try_revert().await {
|
if let Err(err) = self.create_users_and_group.try_revert().await {
|
||||||
fetch_nix_handle.abort();
|
fetch_nix_handle.abort();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
if let Err(err) = create_nix_tree.try_revert().await {
|
if let Err(err) = self.create_nix_tree.try_revert().await {
|
||||||
fetch_nix_handle.abort();
|
fetch_nix_handle.abort();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
*fetch_nix = fetch_nix_handle.await.map_err(ActionError::Join)??;
|
self.fetch_nix = fetch_nix_handle
|
||||||
move_unpacked_nix.try_revert().await?;
|
.await
|
||||||
|
.map_err(ActionError::Join)?
|
||||||
|
.map_err(|e| ActionError::Child(self.fetch_nix.action_tag(), Box::new(e)))?;
|
||||||
|
self.move_unpacked_nix
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.move_unpacked_nix.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{ActionError, ActionState, StatefulAction};
|
use crate::action::{ActionError, ActionState, ActionTag, StatefulAction};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
@ -22,12 +22,13 @@ impl StartSystemdUnit {
|
||||||
enable: bool,
|
enable: bool,
|
||||||
) -> Result<StatefulAction<Self>, ActionError> {
|
) -> Result<StatefulAction<Self>, ActionError> {
|
||||||
let unit = unit.as_ref();
|
let unit = unit.as_ref();
|
||||||
let output = Command::new("systemctl")
|
let mut command = Command::new("systemctl");
|
||||||
.arg("is-active")
|
command.arg("is-active");
|
||||||
.arg(unit)
|
command.arg(unit);
|
||||||
|
let output = command
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(ActionError::Command)?;
|
.map_err(|e| ActionError::command(&command, e))?;
|
||||||
|
|
||||||
let state = if output.status.success() {
|
let state = if output.status.success() {
|
||||||
tracing::debug!("Starting systemd unit `{}` already complete", unit);
|
tracing::debug!("Starting systemd unit `{}` already complete", unit);
|
||||||
|
@ -49,6 +50,9 @@ impl StartSystemdUnit {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "start_systemd_unit")]
|
#[typetag::serde(name = "start_systemd_unit")]
|
||||||
impl Action for StartSystemdUnit {
|
impl Action for StartSystemdUnit {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("start_systemd_unit")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Enable (and start) the systemd unit {}", self.unit)
|
format!("Enable (and start) the systemd unit {}", self.unit)
|
||||||
}
|
}
|
||||||
|
@ -80,8 +84,7 @@ impl Action for StartSystemdUnit {
|
||||||
.arg(format!("{unit}"))
|
.arg(format!("{unit}"))
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
|
|
||||||
},
|
},
|
||||||
false => {
|
false => {
|
||||||
// TODO(@Hoverbear): Handle proxy vars
|
// TODO(@Hoverbear): Handle proxy vars
|
||||||
|
@ -92,8 +95,7 @@ impl Action for StartSystemdUnit {
|
||||||
.arg(format!("{unit}"))
|
.arg(format!("{unit}"))
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,8 +121,7 @@ impl Action for StartSystemdUnit {
|
||||||
.arg(format!("{unit}"))
|
.arg(format!("{unit}"))
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// We do both to avoid an error doing `disable --now` if the user did stop it already somehow.
|
// We do both to avoid an error doing `disable --now` if the user did stop it already somehow.
|
||||||
|
@ -131,8 +132,7 @@ impl Action for StartSystemdUnit {
|
||||||
.arg(format!("{unit}"))
|
.arg(format!("{unit}"))
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Custom(Box::new(StartSystemdUnitError::Command(e))))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{ActionError, StatefulAction};
|
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
@ -27,8 +27,11 @@ impl BootstrapApfsVolume {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "bootstrap_volume")]
|
#[typetag::serde(name = "bootstrap_apfs_volume")]
|
||||||
impl Action for BootstrapApfsVolume {
|
impl Action for BootstrapApfsVolume {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("bootstrap_apfs_volume")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Bootstrap and kickstart `{}`", self.path.display())
|
format!("Bootstrap and kickstart `{}`", self.path.display())
|
||||||
}
|
}
|
||||||
|
@ -36,7 +39,7 @@ impl Action for BootstrapApfsVolume {
|
||||||
fn tracing_span(&self) -> Span {
|
fn tracing_span(&self) -> Span {
|
||||||
span!(
|
span!(
|
||||||
tracing::Level::DEBUG,
|
tracing::Level::DEBUG,
|
||||||
"bootstrap_volume",
|
"bootstrap_apfs_volume",
|
||||||
path = %self.path.display(),
|
path = %self.path.display(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -56,16 +59,14 @@ impl Action for BootstrapApfsVolume {
|
||||||
.arg(path)
|
.arg(path)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
.process_group(0)
|
.process_group(0)
|
||||||
.args(["kickstart", "-k", "system/org.nixos.darwin-store"])
|
.args(["kickstart", "-k", "system/org.nixos.darwin-store"])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -88,8 +89,7 @@ impl Action for BootstrapApfsVolume {
|
||||||
.arg(path)
|
.arg(path)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{ActionError, StatefulAction};
|
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -25,8 +25,7 @@ impl CreateApfsVolume {
|
||||||
) -> Result<StatefulAction<Self>, ActionError> {
|
) -> Result<StatefulAction<Self>, ActionError> {
|
||||||
let output =
|
let output =
|
||||||
execute_command(Command::new("/usr/sbin/diskutil").args(["apfs", "list", "-plist"]))
|
execute_command(Command::new("/usr/sbin/diskutil").args(["apfs", "list", "-plist"]))
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
let parsed: DiskUtilApfsListOutput = plist::from_bytes(&output.stdout)?;
|
let parsed: DiskUtilApfsListOutput = plist::from_bytes(&output.stdout)?;
|
||||||
for container in parsed.containers {
|
for container in parsed.containers {
|
||||||
|
@ -51,6 +50,9 @@ impl CreateApfsVolume {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_volume")]
|
#[typetag::serde(name = "create_volume")]
|
||||||
impl Action for CreateApfsVolume {
|
impl Action for CreateApfsVolume {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_apfs_volume")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Create an APFS volume on `{}` named `{}`",
|
"Create an APFS volume on `{}` named `{}`",
|
||||||
|
@ -98,8 +100,7 @@ impl Action for CreateApfsVolume {
|
||||||
])
|
])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -129,8 +130,7 @@ impl Action for CreateApfsVolume {
|
||||||
.args(["apfs", "deleteVolume", name])
|
.args(["apfs", "deleteVolume", name])
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionError, StatefulAction},
|
action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction},
|
||||||
execute_command,
|
execute_command,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -60,6 +60,9 @@ impl CreateFstabEntry {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_fstab_entry")]
|
#[typetag::serde(name = "create_fstab_entry")]
|
||||||
impl Action for CreateFstabEntry {
|
impl Action for CreateFstabEntry {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_fstab_entry")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Add a UUID based entry for the APFS volume `{}` to `/etc/fstab`",
|
"Add a UUID based entry for the APFS volume `{}` to `/etc/fstab`",
|
||||||
|
@ -178,8 +181,7 @@ async fn get_uuid_for_label(apfs_volume_label: &str) -> Result<Uuid, ActionError
|
||||||
.stdin(std::process::Stdio::null())
|
.stdin(std::process::Stdio::null())
|
||||||
.stdout(std::process::Stdio::piped()),
|
.stdout(std::process::Stdio::piped()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
let parsed: DiskUtilApfsInfoOutput = plist::from_bytes(&output.stdout)?;
|
let parsed: DiskUtilApfsInfoOutput = plist::from_bytes(&output.stdout)?;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::action::{
|
||||||
BootstrapApfsVolume, CreateApfsVolume, CreateSyntheticObjects, EnableOwnership,
|
BootstrapApfsVolume, CreateApfsVolume, CreateSyntheticObjects, EnableOwnership,
|
||||||
EncryptApfsVolume, UnmountApfsVolume,
|
EncryptApfsVolume, UnmountApfsVolume,
|
||||||
},
|
},
|
||||||
Action, ActionDescription, ActionError, StatefulAction,
|
Action, ActionDescription, ActionError, ActionTag, StatefulAction,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -53,17 +53,23 @@ impl CreateNixVolume {
|
||||||
create_or_insert_into_file::Position::End,
|
create_or_insert_into_file::Position::End,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Child(Box::new(e)))?;
|
.map_err(|e| ActionError::Child(CreateOrInsertIntoFile::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let create_synthetic_objects = CreateSyntheticObjects::plan().await?;
|
let create_synthetic_objects = CreateSyntheticObjects::plan()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateSyntheticObjects::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let unmount_volume = UnmountApfsVolume::plan(disk, name.clone()).await?;
|
let unmount_volume = UnmountApfsVolume::plan(disk, name.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(UnmountApfsVolume::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let create_volume = CreateApfsVolume::plan(disk, name.clone(), case_sensitive).await?;
|
let create_volume = CreateApfsVolume::plan(disk, name.clone(), case_sensitive)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateApfsVolume::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let create_fstab_entry = CreateFstabEntry::plan(name.clone())
|
let create_fstab_entry = CreateFstabEntry::plan(name.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Child(Box::new(e)))?;
|
.map_err(|e| ActionError::Child(CreateFstabEntry::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let encrypt_volume = if encrypt {
|
let encrypt_volume = if encrypt {
|
||||||
Some(EncryptApfsVolume::plan(disk, &name).await?)
|
Some(EncryptApfsVolume::plan(disk, &name).await?)
|
||||||
|
@ -106,10 +112,16 @@ impl CreateNixVolume {
|
||||||
", mount_command.iter().map(|v| format!("<string>{v}</string>\n")).collect::<Vec<_>>().join("\n")
|
", mount_command.iter().map(|v| format!("<string>{v}</string>\n")).collect::<Vec<_>>().join("\n")
|
||||||
);
|
);
|
||||||
let setup_volume_daemon =
|
let setup_volume_daemon =
|
||||||
CreateFile::plan(NIX_VOLUME_MOUNTD_DEST, None, None, None, mount_plist, false).await?;
|
CreateFile::plan(NIX_VOLUME_MOUNTD_DEST, None, None, None, mount_plist, false)
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(CreateFile::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let bootstrap_volume = BootstrapApfsVolume::plan(NIX_VOLUME_MOUNTD_DEST).await?;
|
let bootstrap_volume = BootstrapApfsVolume::plan(NIX_VOLUME_MOUNTD_DEST)
|
||||||
let enable_ownership = EnableOwnership::plan("/nix").await?;
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(BootstrapApfsVolume::action_tag(), Box::new(e)))?;
|
||||||
|
let enable_ownership = EnableOwnership::plan("/nix")
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(EnableOwnership::action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
disk: disk.to_path_buf(),
|
disk: disk.to_path_buf(),
|
||||||
|
@ -133,6 +145,9 @@ impl CreateNixVolume {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_apfs_volume")]
|
#[typetag::serde(name = "create_apfs_volume")]
|
||||||
impl Action for CreateNixVolume {
|
impl Action for CreateNixVolume {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_nix_volume")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Create an APFS volume `{}` for Nix on `{}`",
|
"Create an APFS volume `{}` for Nix on `{}`",
|
||||||
|
@ -159,44 +174,57 @@ impl Action for CreateNixVolume {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), ActionError> {
|
async fn execute(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
self.create_or_append_synthetic_conf
|
||||||
disk: _,
|
.try_execute()
|
||||||
name: _,
|
.await
|
||||||
case_sensitive: _,
|
.map_err(|e| {
|
||||||
encrypt: _,
|
ActionError::Child(
|
||||||
create_or_append_synthetic_conf,
|
self.create_or_append_synthetic_conf.action_tag(),
|
||||||
create_synthetic_objects,
|
Box::new(e),
|
||||||
unmount_volume,
|
)
|
||||||
create_volume,
|
})?;
|
||||||
create_fstab_entry,
|
self.create_synthetic_objects
|
||||||
encrypt_volume,
|
.try_execute()
|
||||||
setup_volume_daemon,
|
.await
|
||||||
bootstrap_volume,
|
.map_err(|e| {
|
||||||
enable_ownership,
|
ActionError::Child(self.create_synthetic_objects.action_tag(), Box::new(e))
|
||||||
} = self;
|
})?;
|
||||||
|
self.unmount_volume.try_execute().await.ok(); // We actually expect this may fail.
|
||||||
create_or_append_synthetic_conf.try_execute().await?;
|
self.create_volume
|
||||||
create_synthetic_objects.try_execute().await?;
|
.try_execute()
|
||||||
unmount_volume.try_execute().await.ok(); // We actually expect this may fail.
|
.await
|
||||||
create_volume.try_execute().await?;
|
.map_err(|e| ActionError::Child(self.create_volume.action_tag(), Box::new(e)))?;
|
||||||
create_fstab_entry.try_execute().await?;
|
self.create_fstab_entry
|
||||||
if let Some(encrypt_volume) = encrypt_volume {
|
.try_execute()
|
||||||
encrypt_volume.try_execute().await?;
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.create_fstab_entry.action_tag(), Box::new(e)))?;
|
||||||
|
if let Some(encrypt_volume) = &mut self.encrypt_volume {
|
||||||
|
encrypt_volume
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(encrypt_volume.action_tag(), Box::new(e)))?;
|
||||||
}
|
}
|
||||||
setup_volume_daemon.try_execute().await?;
|
self.setup_volume_daemon
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.setup_volume_daemon.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
bootstrap_volume.try_execute().await?;
|
self.bootstrap_volume
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.bootstrap_volume.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
let mut retry_tokens: usize = 50;
|
let mut retry_tokens: usize = 50;
|
||||||
loop {
|
loop {
|
||||||
tracing::trace!(%retry_tokens, "Checking for Nix Store existence");
|
tracing::trace!(%retry_tokens, "Checking for Nix Store existence");
|
||||||
let status = Command::new("/usr/sbin/diskutil")
|
let mut command = Command::new("/usr/sbin/diskutil");
|
||||||
.args(["info", "/nix"])
|
command.args(["info", "/nix"]);
|
||||||
.stderr(std::process::Stdio::null())
|
command.stderr(std::process::Stdio::null());
|
||||||
.stdout(std::process::Stdio::null())
|
command.stdout(std::process::Stdio::null());
|
||||||
|
let status = command
|
||||||
.status()
|
.status()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
.map_err(|e| ActionError::command(&command, e))?;
|
||||||
if status.success() || retry_tokens == 0 {
|
if status.success() || retry_tokens == 0 {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,7 +233,10 @@ impl Action for CreateNixVolume {
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
enable_ownership.try_execute().await?;
|
self.enable_ownership
|
||||||
|
.try_execute()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.enable_ownership.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -222,36 +253,54 @@ impl Action for CreateNixVolume {
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all)]
|
#[tracing::instrument(level = "debug", skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), ActionError> {
|
async fn revert(&mut self) -> Result<(), ActionError> {
|
||||||
let Self {
|
self.enable_ownership
|
||||||
disk: _,
|
.try_revert()
|
||||||
name: _,
|
.await
|
||||||
case_sensitive: _,
|
.map_err(|e| ActionError::Child(self.enable_ownership.action_tag(), Box::new(e)))?;
|
||||||
encrypt: _,
|
self.bootstrap_volume
|
||||||
create_or_append_synthetic_conf,
|
.try_revert()
|
||||||
create_synthetic_objects,
|
.await
|
||||||
unmount_volume,
|
.map_err(|e| ActionError::Child(self.bootstrap_volume.action_tag(), Box::new(e)))?;
|
||||||
create_volume,
|
self.setup_volume_daemon
|
||||||
create_fstab_entry,
|
.try_revert()
|
||||||
encrypt_volume,
|
.await
|
||||||
setup_volume_daemon,
|
.map_err(|e| ActionError::Child(self.setup_volume_daemon.action_tag(), Box::new(e)))?;
|
||||||
bootstrap_volume,
|
if let Some(encrypt_volume) = &mut self.encrypt_volume {
|
||||||
enable_ownership,
|
encrypt_volume
|
||||||
} = self;
|
.try_revert()
|
||||||
|
.await
|
||||||
enable_ownership.try_revert().await?;
|
.map_err(|e| ActionError::Child(encrypt_volume.action_tag(), Box::new(e)))?;
|
||||||
bootstrap_volume.try_revert().await?;
|
|
||||||
setup_volume_daemon.try_revert().await?;
|
|
||||||
if let Some(encrypt_volume) = encrypt_volume {
|
|
||||||
encrypt_volume.try_revert().await?;
|
|
||||||
}
|
}
|
||||||
create_fstab_entry.try_revert().await?;
|
self.create_fstab_entry
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.create_fstab_entry.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
unmount_volume.try_revert().await?;
|
self.unmount_volume
|
||||||
create_volume.try_revert().await?;
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.unmount_volume.action_tag(), Box::new(e)))?;
|
||||||
|
self.create_volume
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::Child(self.create_volume.action_tag(), Box::new(e)))?;
|
||||||
|
|
||||||
// Purposefully not reversed
|
// Purposefully not reversed
|
||||||
create_or_append_synthetic_conf.try_revert().await?;
|
self.create_or_append_synthetic_conf
|
||||||
create_synthetic_objects.try_revert().await?;
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(
|
||||||
|
self.create_or_append_synthetic_conf.action_tag(),
|
||||||
|
Box::new(e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
self.create_synthetic_objects
|
||||||
|
.try_revert()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ActionError::Child(self.create_synthetic_objects.action_tag(), Box::new(e))
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionError, StatefulAction};
|
use crate::action::{Action, ActionDescription, ActionError, ActionTag, StatefulAction};
|
||||||
|
|
||||||
/// Create the synthetic objects defined in `/etc/syntethic.conf`
|
/// Create the synthetic objects defined in `/etc/syntethic.conf`
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
@ -19,6 +19,9 @@ impl CreateSyntheticObjects {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_synthetic_objects")]
|
#[typetag::serde(name = "create_synthetic_objects")]
|
||||||
impl Action for CreateSyntheticObjects {
|
impl Action for CreateSyntheticObjects {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("create_synthetic_objects")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"Create objects defined in `/etc/synthetic.conf`".to_string()
|
"Create objects defined in `/etc/synthetic.conf`".to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::path::{Path, PathBuf};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{ActionError, StatefulAction};
|
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
@ -31,6 +31,9 @@ impl EnableOwnership {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "enable_ownership")]
|
#[typetag::serde(name = "enable_ownership")]
|
||||||
impl Action for EnableOwnership {
|
impl Action for EnableOwnership {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("enable_ownership")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Enable ownership on {}", self.path.display())
|
format!("Enable ownership on {}", self.path.display())
|
||||||
}
|
}
|
||||||
|
@ -59,8 +62,7 @@ impl Action for EnableOwnership {
|
||||||
.arg(&path)
|
.arg(&path)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?
|
||||||
.map_err(ActionError::Command)?
|
|
||||||
.stdout;
|
.stdout;
|
||||||
let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf))?;
|
let the_plist: DiskUtilOutput = plist::from_reader(Cursor::new(buf))?;
|
||||||
|
|
||||||
|
@ -75,8 +77,7 @@ impl Action for EnableOwnership {
|
||||||
.arg(path)
|
.arg(path)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{
|
action::{
|
||||||
macos::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, StatefulAction,
|
macos::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionError, ActionTag,
|
||||||
|
StatefulAction,
|
||||||
},
|
},
|
||||||
execute_command,
|
execute_command,
|
||||||
};
|
};
|
||||||
|
@ -36,6 +37,9 @@ impl EncryptApfsVolume {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "encrypt_volume")]
|
#[typetag::serde(name = "encrypt_volume")]
|
||||||
impl Action for EncryptApfsVolume {
|
impl Action for EncryptApfsVolume {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("encrypt_apfs_volume")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Encrypt volume `{}` on disk `{}`",
|
"Encrypt volume `{}` on disk `{}`",
|
||||||
|
@ -80,9 +84,7 @@ impl Action for EncryptApfsVolume {
|
||||||
|
|
||||||
let disk_str = disk.to_str().expect("Could not turn disk into string"); /* Should not reasonably ever fail */
|
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))
|
execute_command(Command::new("/usr/sbin/diskutil").arg("mount").arg(&name)).await?;
|
||||||
.await
|
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
// Add the password to the user keychain so they can unlock it later.
|
// Add the password to the user keychain so they can unlock it later.
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -112,8 +114,7 @@ impl Action for EncryptApfsVolume {
|
||||||
"/Library/Keychains/System.keychain",
|
"/Library/Keychains/System.keychain",
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
// Encrypt the mounted volume
|
// Encrypt the mounted volume
|
||||||
execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([
|
execute_command(Command::new("/usr/sbin/diskutil").process_group(0).args([
|
||||||
|
@ -125,8 +126,7 @@ impl Action for EncryptApfsVolume {
|
||||||
"-passphrase",
|
"-passphrase",
|
||||||
password.as_str(),
|
password.as_str(),
|
||||||
]))
|
]))
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
@ -135,8 +135,7 @@ impl Action for EncryptApfsVolume {
|
||||||
.arg("force")
|
.arg("force")
|
||||||
.arg(&name),
|
.arg(&name),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -178,8 +177,7 @@ impl Action for EncryptApfsVolume {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(ActionError::Command)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use tracing::{span, Span};
|
use tracing::{span, Span};
|
||||||
|
|
||||||
use crate::action::{ActionError, StatefulAction};
|
use crate::action::{ActionError, ActionTag, StatefulAction};
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
@ -31,6 +31,9 @@ impl UnmountApfsVolume {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "unmount_volume")]
|
#[typetag::serde(name = "unmount_volume")]
|
||||||
impl Action for UnmountApfsVolume {
|
impl Action for UnmountApfsVolume {
|
||||||
|
fn action_tag() -> ActionTag {
|
||||||
|
ActionTag("unmount_apfs_volume")
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Unmount the `{}` APFS volume", self.name)
|
format!("Unmount the `{}` APFS volume", self.name)
|
||||||
}
|
}
|
||||||
|
@ -59,8 +62,7 @@ impl Action for UnmountApfsVolume {
|
||||||
.arg(name)
|
.arg(name)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -80,8 +82,7 @@ impl Action for UnmountApfsVolume {
|
||||||
.arg(name)
|
.arg(name)
|
||||||
.stdin(std::process::Stdio::null()),
|
.stdin(std::process::Stdio::null()),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
.map_err(|e| ActionError::Command(e))?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,6 +68,9 @@ impl MyAction {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "my_action")]
|
#[typetag::serde(name = "my_action")]
|
||||||
impl Action for MyAction {
|
impl Action for MyAction {
|
||||||
|
fn action_tag() -> nix_installer::action::ActionTag {
|
||||||
|
"my_action".into()
|
||||||
|
}
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
"My action".to_string()
|
"My action".to_string()
|
||||||
}
|
}
|
||||||
|
@ -171,7 +174,7 @@ pub mod macos;
|
||||||
mod stateful;
|
mod stateful;
|
||||||
|
|
||||||
pub use stateful::{ActionState, StatefulAction};
|
pub use stateful::{ActionState, StatefulAction};
|
||||||
use std::error::Error;
|
use std::{error::Error, process::Output};
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
use tracing::Span;
|
use tracing::Span;
|
||||||
|
|
||||||
|
@ -185,6 +188,9 @@ use crate::error::HasExpectedErrors;
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(tag = "action")]
|
#[typetag::serde(tag = "action")]
|
||||||
pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
|
pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone {
|
||||||
|
fn action_tag() -> ActionTag
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
/// A synopsis of the action for tracing purposes
|
/// A synopsis of the action for tracing purposes
|
||||||
fn tracing_synopsis(&self) -> String;
|
fn tracing_synopsis(&self) -> String;
|
||||||
/// A tracing span suitable for the action
|
/// A tracing span suitable for the action
|
||||||
|
@ -252,6 +258,27 @@ impl ActionDescription {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A 'tag' name an action has that corresponds to the one we serialize in [`typetag]`
|
||||||
|
pub struct ActionTag(&'static str);
|
||||||
|
|
||||||
|
impl std::fmt::Display for ActionTag {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for ActionTag {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for ActionTag {
|
||||||
|
fn from(value: &'static str) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An error occurring during an action
|
/// An error occurring during an action
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||||
|
@ -260,10 +287,10 @@ pub enum ActionError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Custom(Box<dyn std::error::Error + Send + Sync>),
|
Custom(Box<dyn std::error::Error + Send + Sync>),
|
||||||
/// A child error
|
/// A child error
|
||||||
#[error(transparent)]
|
#[error("Child action `{0}`")]
|
||||||
Child(#[from] Box<ActionError>),
|
Child(ActionTag, #[source] Box<ActionError>),
|
||||||
/// Several child errors
|
/// Several child errors
|
||||||
#[error("Multiple errors: {}", .0.iter().map(|v| {
|
#[error("Child action errors: {}", .0.iter().map(|v| {
|
||||||
if let Some(source) = v.source() {
|
if let Some(source) = v.source() {
|
||||||
format!("{v} ({source})")
|
format!("{v} ({source})")
|
||||||
} else {
|
} else {
|
||||||
|
@ -341,8 +368,33 @@ pub enum ActionError {
|
||||||
#[error("Chowning path `{0}`")]
|
#[error("Chowning path `{0}`")]
|
||||||
Chown(std::path::PathBuf, #[source] nix::errno::Errno),
|
Chown(std::path::PathBuf, #[source] nix::errno::Errno),
|
||||||
/// Failed to execute command
|
/// Failed to execute command
|
||||||
#[error("Failed to execute command")]
|
#[error("Failed to execute command `{command}`",
|
||||||
Command(#[source] std::io::Error),
|
command = .command,
|
||||||
|
)]
|
||||||
|
Command {
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
program: String,
|
||||||
|
command: String,
|
||||||
|
#[source]
|
||||||
|
error: std::io::Error,
|
||||||
|
},
|
||||||
|
#[error(
|
||||||
|
"Failed to execute command{maybe_status} `{command}`, stdout: {stdout}\nstderr: {stderr}\n",
|
||||||
|
command = .command,
|
||||||
|
stdout = String::from_utf8_lossy(&.output.stdout),
|
||||||
|
stderr = String::from_utf8_lossy(&.output.stderr),
|
||||||
|
maybe_status = if let Some(status) = .output.status.code() {
|
||||||
|
format!(" with status {status}")
|
||||||
|
} else {
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
CommandOutput {
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
program: String,
|
||||||
|
command: String,
|
||||||
|
output: Output,
|
||||||
|
},
|
||||||
#[error("Joining spawned async task")]
|
#[error("Joining spawned async task")]
|
||||||
Join(
|
Join(
|
||||||
#[source]
|
#[source]
|
||||||
|
@ -360,6 +412,25 @@ pub enum ActionError {
|
||||||
Plist(#[from] plist::Error),
|
Plist(#[from] plist::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ActionError {
|
||||||
|
pub fn command(command: &tokio::process::Command, error: std::io::Error) -> Self {
|
||||||
|
Self::Command {
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
program: command.as_std().get_program().to_string_lossy().into(),
|
||||||
|
command: format!("{:?}", command.as_std()),
|
||||||
|
error,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn command_output(command: &tokio::process::Command, output: std::process::Output) -> Self {
|
||||||
|
Self::CommandOutput {
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
program: command.as_std().get_program().to_string_lossy().into(),
|
||||||
|
command: format!("{:?}", command.as_std()),
|
||||||
|
output,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HasExpectedErrors for ActionError {
|
impl HasExpectedErrors for ActionError {
|
||||||
fn expected<'a>(&'a self) -> Option<Box<dyn std::error::Error + 'a>> {
|
fn expected<'a>(&'a self) -> Option<Box<dyn std::error::Error + 'a>> {
|
||||||
match self {
|
match self {
|
||||||
|
@ -370,3 +441,56 @@ impl HasExpectedErrors for ActionError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
impl crate::diagnostics::ErrorDiagnostic for ActionError {
|
||||||
|
fn diagnostic(&self) -> String {
|
||||||
|
let static_str: &'static str = (self).into();
|
||||||
|
let context = match self {
|
||||||
|
Self::Child(action, _) => vec![action.to_string()],
|
||||||
|
Self::Read(path, _)
|
||||||
|
| Self::Open(path, _)
|
||||||
|
| Self::Write(path, _)
|
||||||
|
| Self::Flush(path, _)
|
||||||
|
| Self::SetPermissions(_, path, _)
|
||||||
|
| Self::GettingMetadata(path, _)
|
||||||
|
| Self::CreateDirectory(path, _)
|
||||||
|
| Self::PathWasNotFile(path) => {
|
||||||
|
vec![path.to_string_lossy().to_string()]
|
||||||
|
},
|
||||||
|
Self::Rename(first_path, second_path, _)
|
||||||
|
| Self::Copy(first_path, second_path, _)
|
||||||
|
| Self::Symlink(first_path, second_path, _) => {
|
||||||
|
vec![
|
||||||
|
first_path.to_string_lossy().to_string(),
|
||||||
|
second_path.to_string_lossy().to_string(),
|
||||||
|
]
|
||||||
|
},
|
||||||
|
Self::NoGroup(name) | Self::NoUser(name) => {
|
||||||
|
vec![name.clone()]
|
||||||
|
},
|
||||||
|
Self::Command {
|
||||||
|
program,
|
||||||
|
command: _,
|
||||||
|
error: _,
|
||||||
|
}
|
||||||
|
| Self::CommandOutput {
|
||||||
|
program,
|
||||||
|
command: _,
|
||||||
|
output: _,
|
||||||
|
} => {
|
||||||
|
vec![program.clone()]
|
||||||
|
},
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
return format!(
|
||||||
|
"{}({})",
|
||||||
|
static_str,
|
||||||
|
context
|
||||||
|
.iter()
|
||||||
|
.map(|v| format!("\"{v}\""))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing::{Instrument, Span};
|
use tracing::{Instrument, Span};
|
||||||
|
|
||||||
use super::{Action, ActionDescription, ActionError};
|
use super::{Action, ActionDescription, ActionError, ActionTag};
|
||||||
|
|
||||||
/// A wrapper around an [`Action`](crate::action::Action) which tracks the [`ActionState`] and
|
/// A wrapper around an [`Action`](crate::action::Action) which tracks the [`ActionState`] and
|
||||||
/// handles some tracing output
|
/// handles some tracing output
|
||||||
|
@ -24,6 +24,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatefulAction<Box<dyn Action>> {
|
impl StatefulAction<Box<dyn Action>> {
|
||||||
|
pub fn inner_typetag_name(&self) -> &'static str {
|
||||||
|
self.action.typetag_name()
|
||||||
|
}
|
||||||
pub fn tracing_synopsis(&self) -> String {
|
pub fn tracing_synopsis(&self) -> String {
|
||||||
self.action.tracing_synopsis()
|
self.action.tracing_synopsis()
|
||||||
}
|
}
|
||||||
|
@ -109,6 +112,12 @@ impl<A> StatefulAction<A>
|
||||||
where
|
where
|
||||||
A: Action,
|
A: Action,
|
||||||
{
|
{
|
||||||
|
pub fn tag() -> ActionTag {
|
||||||
|
A::action_tag()
|
||||||
|
}
|
||||||
|
pub fn action_tag(&self) -> ActionTag {
|
||||||
|
A::action_tag()
|
||||||
|
}
|
||||||
pub fn tracing_synopsis(&self) -> String {
|
pub fn tracing_synopsis(&self) -> String {
|
||||||
self.action.tracing_synopsis()
|
self.action.tracing_synopsis()
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,9 @@ use clap::Parser;
|
||||||
use nix_installer::cli::CommandExecute;
|
use nix_installer::cli::CommandExecute;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> color_eyre::Result<ExitCode> {
|
async fn main() -> eyre::Result<ExitCode> {
|
||||||
color_eyre::config::HookBuilder::default()
|
color_eyre::config::HookBuilder::default()
|
||||||
|
.issue_url(concat!(env!("CARGO_PKG_REPOSITORY"), "/issues/new"))
|
||||||
.theme(if !atty::is(atty::Stream::Stderr) {
|
.theme(if !atty::is(atty::Stream::Stderr) {
|
||||||
color_eyre::config::Theme::new()
|
color_eyre::config::Theme::new()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -139,7 +139,7 @@ impl CommandExecute for Install {
|
||||||
eprintln!("{}", expected.red());
|
eprintln!("{}", expected.red());
|
||||||
return Ok(ExitCode::FAILURE);
|
return Ok(ExitCode::FAILURE);
|
||||||
}
|
}
|
||||||
return Err(err.into())
|
return Err(err)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -212,12 +212,12 @@ impl CommandExecute for Install {
|
||||||
let rx2 = tx.subscribe();
|
let rx2 = tx.subscribe();
|
||||||
let res = install_plan.uninstall(rx2).await;
|
let res = install_plan.uninstall(rx2).await;
|
||||||
|
|
||||||
if let Err(e) = res {
|
if let Err(err) = res {
|
||||||
if let Some(expected) = e.expected() {
|
if let Some(expected) = err.expected() {
|
||||||
eprintln!("{}", expected.red());
|
eprintln!("{}", expected.red());
|
||||||
return Ok(ExitCode::FAILURE);
|
return Ok(ExitCode::FAILURE);
|
||||||
}
|
}
|
||||||
return Err(e.into());
|
return Err(err)?;
|
||||||
} else {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"\
|
"\
|
||||||
|
@ -233,7 +233,7 @@ impl CommandExecute for Install {
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = eyre!(err).wrap_err("Install failure");
|
let error = eyre!(err).wrap_err("Install failure");
|
||||||
return Err(error);
|
return Err(error)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|
|
@ -25,21 +25,19 @@ impl CommandExecute for Plan {
|
||||||
|
|
||||||
let planner = match planner {
|
let planner = match planner {
|
||||||
Some(planner) => planner,
|
Some(planner) => planner,
|
||||||
None => BuiltinPlanner::default()
|
None => BuiltinPlanner::default().await?,
|
||||||
.await
|
|
||||||
.map_err(|e| eyre::eyre!(e))?,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = planner.plan().await;
|
let res = planner.plan().await;
|
||||||
|
|
||||||
let install_plan = match res {
|
let install_plan = match res {
|
||||||
Ok(plan) => plan,
|
Ok(plan) => plan,
|
||||||
Err(e) => {
|
Err(err) => {
|
||||||
if let Some(expected) = e.expected() {
|
if let Some(expected) = err.expected() {
|
||||||
eprintln!("{}", expected.red());
|
eprintln!("{}", expected.red());
|
||||||
return Ok(ExitCode::FAILURE);
|
return Ok(ExitCode::FAILURE);
|
||||||
}
|
}
|
||||||
return Err(e.into());
|
return Err(err)?;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -124,12 +124,12 @@ impl CommandExecute for Uninstall {
|
||||||
let (_tx, rx) = signal_channel().await?;
|
let (_tx, rx) = signal_channel().await?;
|
||||||
|
|
||||||
let res = plan.uninstall(rx).await;
|
let res = plan.uninstall(rx).await;
|
||||||
if let Err(e) = res {
|
if let Err(err) = res {
|
||||||
if let Some(expected) = e.expected() {
|
if let Some(expected) = err.expected() {
|
||||||
println!("{}", expected.red());
|
println!("{}", expected.red());
|
||||||
return Ok(ExitCode::FAILURE);
|
return Ok(ExitCode::FAILURE);
|
||||||
}
|
}
|
||||||
return Err(e.into());
|
return Err(err)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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...
|
||||||
|
|
|
@ -10,6 +10,10 @@ use std::time::Duration;
|
||||||
use os_release::OsRelease;
|
use os_release::OsRelease;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
action::ActionError, planner::PlannerError, settings::InstallSettingsError, NixInstallerError,
|
||||||
|
};
|
||||||
|
|
||||||
/// The static of an action attempt
|
/// The static of an action attempt
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub enum DiagnosticStatus {
|
pub enum DiagnosticStatus {
|
||||||
|
@ -39,7 +43,7 @@ pub struct DiagnosticReport {
|
||||||
pub action: DiagnosticAction,
|
pub action: DiagnosticAction,
|
||||||
pub status: DiagnosticStatus,
|
pub status: DiagnosticStatus,
|
||||||
/// Generally this includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets)
|
/// Generally this includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets)
|
||||||
pub failure_variant: Option<String>,
|
pub failure_chain: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A preparation of data to be sent to the `endpoint`.
|
/// A preparation of data to be sent to the `endpoint`.
|
||||||
|
@ -53,7 +57,8 @@ pub struct DiagnosticData {
|
||||||
triple: String,
|
triple: String,
|
||||||
is_ci: bool,
|
is_ci: bool,
|
||||||
endpoint: Option<Url>,
|
endpoint: Option<Url>,
|
||||||
failure_variant: Option<String>,
|
/// Generally this includes the [`strum::IntoStaticStr`] representation of the error, we take special care not to include parameters of the error (which may include secrets)
|
||||||
|
failure_chain: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticData {
|
impl DiagnosticData {
|
||||||
|
@ -73,12 +78,42 @@ impl DiagnosticData {
|
||||||
os_version,
|
os_version,
|
||||||
triple: target_lexicon::HOST.to_string(),
|
triple: target_lexicon::HOST.to_string(),
|
||||||
is_ci,
|
is_ci,
|
||||||
failure_variant: None,
|
failure_chain: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variant(mut self, variant: String) -> Self {
|
pub fn failure(mut self, err: &NixInstallerError) -> Self {
|
||||||
self.failure_variant = Some(variant);
|
let mut failure_chain = vec![];
|
||||||
|
let diagnostic = err.diagnostic();
|
||||||
|
failure_chain.push(diagnostic);
|
||||||
|
|
||||||
|
let mut walker: &dyn std::error::Error = &err;
|
||||||
|
while let Some(source) = walker.source() {
|
||||||
|
if let Some(downcasted) = source.downcast_ref::<ActionError>() {
|
||||||
|
let downcasted_diagnostic = downcasted.diagnostic();
|
||||||
|
failure_chain.push(downcasted_diagnostic);
|
||||||
|
}
|
||||||
|
if let Some(downcasted) = source.downcast_ref::<Box<ActionError>>() {
|
||||||
|
let downcasted_diagnostic = downcasted.diagnostic();
|
||||||
|
failure_chain.push(downcasted_diagnostic);
|
||||||
|
}
|
||||||
|
if let Some(downcasted) = source.downcast_ref::<PlannerError>() {
|
||||||
|
let downcasted_diagnostic = downcasted.diagnostic();
|
||||||
|
failure_chain.push(downcasted_diagnostic);
|
||||||
|
}
|
||||||
|
if let Some(downcasted) = source.downcast_ref::<InstallSettingsError>() {
|
||||||
|
let downcasted_diagnostic = downcasted.diagnostic();
|
||||||
|
failure_chain.push(downcasted_diagnostic);
|
||||||
|
}
|
||||||
|
if let Some(downcasted) = source.downcast_ref::<DiagnosticError>() {
|
||||||
|
let downcasted_diagnostic = downcasted.diagnostic();
|
||||||
|
failure_chain.push(downcasted_diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
walker = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.failure_chain = Some(failure_chain);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +127,7 @@ impl DiagnosticData {
|
||||||
triple,
|
triple,
|
||||||
is_ci,
|
is_ci,
|
||||||
endpoint: _,
|
endpoint: _,
|
||||||
failure_variant: variant,
|
failure_chain,
|
||||||
} = self;
|
} = self;
|
||||||
DiagnosticReport {
|
DiagnosticReport {
|
||||||
version: version.clone(),
|
version: version.clone(),
|
||||||
|
@ -104,7 +139,7 @@ impl DiagnosticData {
|
||||||
is_ci: *is_ci,
|
is_ci: *is_ci,
|
||||||
action,
|
action,
|
||||||
status,
|
status,
|
||||||
failure_variant: variant.clone(),
|
failure_chain: failure_chain.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +188,7 @@ impl DiagnosticData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||||
pub enum DiagnosticError {
|
pub enum DiagnosticError {
|
||||||
#[error("Unknown url scheme")]
|
#[error("Unknown url scheme")]
|
||||||
UnknownUrlScheme,
|
UnknownUrlScheme,
|
||||||
|
@ -172,3 +207,14 @@ pub enum DiagnosticError {
|
||||||
serde_json::Error,
|
serde_json::Error,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ErrorDiagnostic {
|
||||||
|
fn diagnostic(&self) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErrorDiagnostic for DiagnosticError {
|
||||||
|
fn diagnostic(&self) -> String {
|
||||||
|
let static_str: &'static str = (self).into();
|
||||||
|
return static_str.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
36
src/error.rs
36
src/error.rs
|
@ -1,18 +1,18 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{action::ActionError, planner::PlannerError, settings::InstallSettingsError};
|
use crate::{
|
||||||
|
action::{ActionError, ActionTag},
|
||||||
|
planner::PlannerError,
|
||||||
|
settings::InstallSettingsError,
|
||||||
|
};
|
||||||
|
|
||||||
/// An error occurring during a call defined in this crate
|
/// An error occurring during a call defined in this crate
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||||
pub enum NixInstallerError {
|
pub enum NixInstallerError {
|
||||||
/// An error originating from an [`Action`](crate::action::Action)
|
/// An error originating from an [`Action`](crate::action::Action)
|
||||||
#[error("Error executing action")]
|
#[error("Error executing action `{0}`")]
|
||||||
Action(
|
Action(ActionTag, #[source] ActionError),
|
||||||
#[source]
|
|
||||||
#[from]
|
|
||||||
ActionError,
|
|
||||||
),
|
|
||||||
/// An error while writing the [`InstallPlan`](crate::InstallPlan)
|
/// An error while writing the [`InstallPlan`](crate::InstallPlan)
|
||||||
#[error("Recording install receipt")]
|
#[error("Recording install receipt")]
|
||||||
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
RecordingReceipt(PathBuf, #[source] std::io::Error),
|
||||||
|
@ -72,7 +72,7 @@ pub(crate) trait HasExpectedErrors: std::error::Error + Sized + Send + Sync {
|
||||||
impl HasExpectedErrors for NixInstallerError {
|
impl HasExpectedErrors for NixInstallerError {
|
||||||
fn expected<'a>(&'a self) -> Option<Box<dyn std::error::Error + 'a>> {
|
fn expected<'a>(&'a self) -> Option<Box<dyn std::error::Error + 'a>> {
|
||||||
match self {
|
match self {
|
||||||
NixInstallerError::Action(action_error) => action_error.expected(),
|
NixInstallerError::Action(_, action_error) => action_error.expected(),
|
||||||
NixInstallerError::RecordingReceipt(_, _) => None,
|
NixInstallerError::RecordingReceipt(_, _) => None,
|
||||||
NixInstallerError::CopyingSelf(_) => None,
|
NixInstallerError::CopyingSelf(_) => None,
|
||||||
NixInstallerError::SerializingReceipt(_) => None,
|
NixInstallerError::SerializingReceipt(_) => None,
|
||||||
|
@ -85,3 +85,23 @@ impl HasExpectedErrors for NixInstallerError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
impl crate::diagnostics::ErrorDiagnostic for NixInstallerError {
|
||||||
|
fn diagnostic(&self) -> String {
|
||||||
|
let static_str: &'static str = (self).into();
|
||||||
|
let context = match self {
|
||||||
|
Self::Action(action, _) => vec![action.to_string()],
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
return format!(
|
||||||
|
"{}({})",
|
||||||
|
static_str,
|
||||||
|
context
|
||||||
|
.iter()
|
||||||
|
.map(|v| format!("\"{v}\""))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
25
src/lib.rs
25
src/lib.rs
|
@ -83,7 +83,7 @@ pub mod settings;
|
||||||
|
|
||||||
use std::{ffi::OsStr, process::Output};
|
use std::{ffi::OsStr, process::Output};
|
||||||
|
|
||||||
use action::Action;
|
use action::{Action, ActionError};
|
||||||
|
|
||||||
pub use channel_value::ChannelValue;
|
pub use channel_value::ChannelValue;
|
||||||
pub use error::NixInstallerError;
|
pub use error::NixInstallerError;
|
||||||
|
@ -93,24 +93,15 @@ use planner::BuiltinPlanner;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all, fields(command = %format!("{:?}", command.as_std())))]
|
#[tracing::instrument(level = "debug", 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, ActionError> {
|
||||||
let command_str = format!("{:?}", command.as_std());
|
tracing::trace!("Executing `{:?}`", command.as_std());
|
||||||
tracing::trace!("Executing `{command_str}`");
|
let output = command
|
||||||
let output = command.output().await?;
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| ActionError::command(command, e))?;
|
||||||
match output.status.success() {
|
match output.status.success() {
|
||||||
true => Ok(output),
|
true => Ok(output),
|
||||||
false => Err(std::io::Error::new(
|
false => Err(ActionError::command_output(command, output)),
|
||||||
std::io::ErrorKind::Other,
|
|
||||||
format!(
|
|
||||||
"Command `{command_str}` failed{}, stderr:\n{}\n",
|
|
||||||
if let Some(code) = output.status.code() {
|
|
||||||
format!(" status {code}")
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
},
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/plan.rs
18
src/plan.rs
|
@ -154,18 +154,17 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Step: {}", action.tracing_synopsis());
|
tracing::info!("Step: {}", action.tracing_synopsis());
|
||||||
|
let typetag_name = action.inner_typetag_name();
|
||||||
if let Err(err) = action.try_execute().await {
|
if let Err(err) = action.try_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);
|
||||||
}
|
}
|
||||||
|
let err = NixInstallerError::Action(typetag_name.into(), err);
|
||||||
#[cfg(feature = "diagnostics")]
|
#[cfg(feature = "diagnostics")]
|
||||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||||
diagnostic_data
|
diagnostic_data
|
||||||
.clone()
|
.clone()
|
||||||
.variant({
|
.failure(&err)
|
||||||
let x: &'static str = (&err).into();
|
|
||||||
x.to_string()
|
|
||||||
})
|
|
||||||
.send(
|
.send(
|
||||||
crate::diagnostics::DiagnosticAction::Install,
|
crate::diagnostics::DiagnosticAction::Install,
|
||||||
crate::diagnostics::DiagnosticStatus::Failure,
|
crate::diagnostics::DiagnosticStatus::Failure,
|
||||||
|
@ -173,7 +172,7 @@ impl InstallPlan {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Err(NixInstallerError::Action(err));
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,25 +286,24 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("Revert: {}", action.tracing_synopsis());
|
tracing::info!("Revert: {}", action.tracing_synopsis());
|
||||||
|
let typetag_name = action.inner_typetag_name();
|
||||||
if let Err(err) = action.try_revert().await {
|
if let Err(err) = action.try_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);
|
||||||
}
|
}
|
||||||
|
let err = NixInstallerError::Action(typetag_name.into(), err);
|
||||||
#[cfg(feature = "diagnostics")]
|
#[cfg(feature = "diagnostics")]
|
||||||
if let Some(diagnostic_data) = &self.diagnostic_data {
|
if let Some(diagnostic_data) = &self.diagnostic_data {
|
||||||
diagnostic_data
|
diagnostic_data
|
||||||
.clone()
|
.clone()
|
||||||
.variant({
|
.failure(&err)
|
||||||
let x: &'static str = (&err).into();
|
|
||||||
x.to_string()
|
|
||||||
})
|
|
||||||
.send(
|
.send(
|
||||||
crate::diagnostics::DiagnosticAction::Uninstall,
|
crate::diagnostics::DiagnosticAction::Uninstall,
|
||||||
crate::diagnostics::DiagnosticStatus::Failure,
|
crate::diagnostics::DiagnosticStatus::Failure,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
return Err(NixInstallerError::Action(err));
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -322,3 +322,11 @@ impl HasExpectedErrors for PlannerError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
impl crate::diagnostics::ErrorDiagnostic for PlannerError {
|
||||||
|
fn diagnostic(&self) -> String {
|
||||||
|
let static_str: &'static str = (self).into();
|
||||||
|
return static_str.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -562,7 +562,7 @@ impl InitSettings {
|
||||||
|
|
||||||
/// An error originating from a [`Planner::settings`](crate::planner::Planner::settings)
|
/// An error originating from a [`Planner::settings`](crate::planner::Planner::settings)
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug, strum::IntoStaticStr)]
|
||||||
pub enum InstallSettingsError {
|
pub enum InstallSettingsError {
|
||||||
/// `nix-installer` does not support the architecture right now
|
/// `nix-installer` does not support the architecture right now
|
||||||
#[error("`nix-installer` does not support the `{0}` architecture right now")]
|
#[error("`nix-installer` does not support the `{0}` architecture right now")]
|
||||||
|
@ -584,3 +584,11 @@ pub enum InstallSettingsError {
|
||||||
#[error("No supported init system found")]
|
#[error("No supported init system found")]
|
||||||
InitNotSupported,
|
InitNotSupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "diagnostics")]
|
||||||
|
impl crate::diagnostics::ErrorDiagnostic for InstallSettingsError {
|
||||||
|
fn diagnostic(&self) -> String {
|
||||||
|
let static_str: &'static str = (self).into();
|
||||||
|
return static_str.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2
tests/fixtures/linux/linux.json
vendored
2
tests/fixtures/linux/linux.json
vendored
|
@ -884,7 +884,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
"action": "configure_nix_daemon",
|
"action": "configure_init_service",
|
||||||
"init": "Systemd",
|
"init": "Systemd",
|
||||||
"start_daemon": true
|
"start_daemon": true
|
||||||
},
|
},
|
||||||
|
|
2
tests/fixtures/linux/steam-deck.json
vendored
2
tests/fixtures/linux/steam-deck.json
vendored
|
@ -906,7 +906,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
"action": "configure_nix_daemon",
|
"action": "configure_init_service",
|
||||||
"init": "Systemd",
|
"init": "Systemd",
|
||||||
"start_daemon": true
|
"start_daemon": true
|
||||||
},
|
},
|
||||||
|
|
2
tests/fixtures/macos/macos.json
vendored
2
tests/fixtures/macos/macos.json
vendored
|
@ -938,7 +938,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
"action": "configure_nix_daemon",
|
"action": "configure_init_service",
|
||||||
"init": "Launchd",
|
"init": "Launchd",
|
||||||
"start_daemon": true
|
"start_daemon": true
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue