Find xpath crate, scaffold more

This commit is contained in:
Ana Hobden 2022-10-18 11:03:19 -07:00
parent a29baa2b33
commit da0219deb0
14 changed files with 893 additions and 46 deletions

41
Cargo.lock generated
View file

@ -758,6 +758,8 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_with", "serde_with",
"sxd-document",
"sxd-xpath",
"tar", "tar",
"target-lexicon", "target-lexicon",
"tempdir", "tempdir",
@ -1202,6 +1204,12 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "peresil"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57"
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.0.12" version = "1.0.12"
@ -1287,6 +1295,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.21" version = "1.0.21"
@ -1651,6 +1665,27 @@ dependencies = [
"is_ci", "is_ci",
] ]
[[package]]
name = "sxd-document"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d82f37be9faf1b10a82c4bd492b74f698e40082f0f40de38ab275f31d42078"
dependencies = [
"peresil",
"typed-arena",
]
[[package]]
name = "sxd-xpath"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e39da5d30887b5690e29de4c5ebb8ddff64ebd9933f98a01daaa4fd11b36ea"
dependencies = [
"peresil",
"quick-error",
"sxd-document",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.99" version = "1.0.99"
@ -1921,6 +1956,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "typed-arena"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
[[package]] [[package]]
name = "typetag" name = "typetag"
version = "0.2.3" version = "0.2.3"

View file

@ -9,30 +9,32 @@ resolver = "2"
async-tar = "0.4.2" async-tar = "0.4.2"
async-trait = "0.1.57" async-trait = "0.1.57"
atty = "0.2.14" atty = "0.2.14"
bytes = "1.2.1"
clap = { version = "4", features = ["derive", "env"] } clap = { version = "4", features = ["derive", "env"] }
color-eyre = "0.6.2" color-eyre = "0.6.2"
crossterm = { version = "0.25.0", features = ["event-stream"] } crossterm = { version = "0.25.0", features = ["event-stream"] }
eyre = "0.6.8" eyre = "0.6.8"
futures = "0.3.24" futures = "0.3.24"
glob = "0.3.0"
nix = { version = "0.25.0", features = ["user", "fs"], default-features = false }
owo-colors = { version = "3.5.0", features = [ "supports-colors" ] } owo-colors = { version = "3.5.0", features = [ "supports-colors" ] }
reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "stream"] } reqwest = { version = "0.11.11", default-features = false, features = ["rustls-tls", "stream"] }
serde = { version = "1.0.144", features = ["derive"] }
serde_json = "1.0.85"
serde_with = "2.0.1"
tar = "0.4.38"
target-lexicon = "0.12.4" target-lexicon = "0.12.4"
tempdir = { version = "0.3.7"}
thiserror = "1.0.33" thiserror = "1.0.33"
tokio = { version = "1.21.0", features = ["time", "io-std", "process", "fs", "tracing", "rt-multi-thread", "macros", "io-util"] } tokio = { version = "1.21.0", features = ["time", "io-std", "process", "fs", "tracing", "rt-multi-thread", "macros", "io-util"] }
tokio-util = { version = "0.7", features = ["io"] } tokio-util = { version = "0.7", features = ["io"] }
tracing = { version = "0.1.36", features = [ "valuable" ] } tracing = { version = "0.1.36", features = [ "valuable" ] }
tracing-error = "0.2.0" tracing-error = "0.2.0"
tracing-subscriber = { version = "0.3.15", features = [ "env-filter", "valuable" ] } tracing-subscriber = { version = "0.3.15", features = [ "env-filter", "valuable" ] }
valuable = { version = "0.1.0", features = ["derive"] }
tempdir = { version = "0.3.7"}
glob = "0.3.0"
xz2 = { version = "0.1.7", features = ["static", "tokio"] }
bytes = "1.2.1"
tar = "0.4.38"
nix = { version = "0.25.0", features = ["user", "fs"], default-features = false }
walkdir = "2.3.2"
serde = { version = "1.0.144", features = ["derive"] }
url = { version = "2.3.1", features = ["serde"] }
serde_json = "1.0.85"
typetag = "0.2.3" typetag = "0.2.3"
serde_with = "2.0.1" url = { version = "2.3.1", features = ["serde"] }
valuable = { version = "0.1.0", features = ["derive"] }
walkdir = "2.3.2"
sxd-xpath = "0.4.2"
xz2 = { version = "0.1.7", features = ["static", "tokio"] }
sxd-document = "0.3.2"

View file

@ -0,0 +1,116 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct BootstrapVolume {
unit: String,
action_state: ActionState,
}
impl BootstrapVolume {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, BootstrapVolumeError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for BootstrapVolume {
type Error = BootstrapVolumeError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg(format!("{unit}")),
)
.await
.map_err(BootstrapVolumeError::Command)?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
.await
.map_err(BootstrapVolumeError::Command)?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<BootstrapVolume> for Action {
fn from(v: BootstrapVolume) -> Self {
Action::DarwinBootstrapVolume(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum BootstrapVolumeError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -0,0 +1,116 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateSyntheticObjects {
unit: String,
action_state: ActionState,
}
impl CreateSyntheticObjects {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, CreateSyntheticObjectsError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for CreateSyntheticObjects {
type Error = CreateSyntheticObjectsError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg(format!("{unit}")),
)
.await
.map_err(CreateSyntheticObjectsError::Command)?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
.await
.map_err(CreateSyntheticObjectsError::Command)?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<CreateSyntheticObjects> for Action {
fn from(v: CreateSyntheticObjects) -> Self {
Action::DarwinCreateSyntheticObjects(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateSyntheticObjectsError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -0,0 +1,116 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateVolume {
unit: String,
action_state: ActionState,
}
impl CreateVolume {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, CreateVolumeError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for CreateVolume {
type Error = CreateVolumeError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg(format!("{unit}")),
)
.await
.map_err(CreateVolumeError::Command)?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
.await
.map_err(CreateVolumeError::Command)?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<CreateVolume> for Action {
fn from(v: CreateVolume) -> Self {
Action::DarwinCreateVolume(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateVolumeError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -0,0 +1,116 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct EnableOwnership {
unit: String,
action_state: ActionState,
}
impl EnableOwnership {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, EnableOwnershipError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for EnableOwnership {
type Error = EnableOwnershipError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg(format!("{unit}")),
)
.await
.map_err(EnableOwnershipError::Command)?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
.await
.map_err(EnableOwnershipError::Command)?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<EnableOwnership> for Action {
fn from(v: EnableOwnership) -> Self {
Action::DarwinEnableOwnership(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum EnableOwnershipError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -0,0 +1,116 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct EncryptVolume {
unit: String,
action_state: ActionState,
}
impl EncryptVolume {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, EncryptVolumeError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for EncryptVolume {
type Error = EncryptVolumeError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg(format!("{unit}")),
)
.await
.map_err(EncryptVolumeError::Command)?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
.await
.map_err(EncryptVolumeError::Command)?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<EncryptVolume> for Action {
fn from(v: EncryptVolume) -> Self {
Action::DarwinEncryptVolume(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum EncryptVolumeError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -0,0 +1,13 @@
mod bootstrap_volume;
mod create_synthetic_objects;
mod create_volume;
mod enable_ownership;
mod encrypt_volume;
mod unmount_volume;
pub use bootstrap_volume::{BootstrapVolume, BootstrapVolumeError};
pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
pub use create_volume::{CreateVolume, CreateVolumeError};
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
pub use encrypt_volume::{EncryptVolume, EncryptVolumeError};
pub use unmount_volume::{UnmountVolume, UnmountVolumeError};

View file

@ -0,0 +1,116 @@
use serde::Serialize;
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct UnmountVolume {
unit: String,
action_state: ActionState,
}
impl UnmountVolume {
#[tracing::instrument(skip_all)]
pub async fn plan(unit: String) -> Result<Self, UnmountVolumeError> {
Ok(Self {
unit,
action_state: ActionState::Uncompleted,
})
}
}
#[async_trait::async_trait]
impl Actionable for UnmountVolume {
type Error = UnmountVolumeError;
fn describe_execute(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(
"Start the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Starting systemd unit");
return Ok(());
}
tracing::debug!("Starting systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg(format!("{unit}")),
)
.await
.map_err(UnmountVolumeError::Command)?;
tracing::trace!("Started systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
fn describe_revert(&self) -> Vec<ActionDescription> {
if self.action_state == ActionState::Uncompleted {
vec![]
} else {
vec![ActionDescription::new(
"Stop the systemd Nix service and socket".to_string(),
vec![
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
]
)]
}
}
#[tracing::instrument(skip_all, fields(
unit = %self.unit,
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { unit, action_state } = self;
if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Stopping systemd unit");
return Ok(());
}
tracing::debug!("Stopping systemd unit");
// TODO(@Hoverbear): Handle proxy vars
execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}")))
.await
.map_err(UnmountVolumeError::Command)?;
tracing::trace!("Stopped systemd unit");
*action_state = ActionState::Completed;
Ok(())
}
}
impl From<UnmountVolume> for Action {
fn from(v: UnmountVolume) -> Self {
Action::DarwinUnmountVolume(v)
}
}
#[derive(Debug, thiserror::Error, Serialize)]
pub enum UnmountVolumeError {
#[error("Failed to execute command")]
Command(
#[source]
#[serde(serialize_with = "crate::serialize_error_to_display")]
std::io::Error,
),
}

View file

@ -6,6 +6,7 @@ mod create_file;
mod create_group; mod create_group;
mod create_or_append_file; mod create_or_append_file;
mod create_user; mod create_user;
pub mod darwin;
mod fetch_nix; mod fetch_nix;
mod move_unpacked_nix; mod move_unpacked_nix;
mod setup_default_profile; mod setup_default_profile;

View file

@ -10,7 +10,7 @@ use crate::actions::base::{
CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError, CreateOrAppendFile, CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError, CreateOrAppendFile,
CreateOrAppendFileError, CreateOrAppendFileError,
}; };
use crate::actions::{Action, ActionDescription, ActionState, Actionable}; use crate::actions::{base::darwin, Action, ActionDescription, ActionState, Actionable};
const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist"; const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist";
@ -71,7 +71,7 @@ impl CreateApfsVolume {
None None
}; };
let bootstrap_volume = BootstrapVolume::plan(NIX_VOLUME_MOUNTD_DEST).await?; let bootstrap_volume = BootstrapVolume::plan(NIX_VOLUME_MOUNTD_DEST, disk, name).await?;
let enable_ownership = EnableOwnership::plan("/nix").await?; let enable_ownership = EnableOwnership::plan("/nix").await?;
Ok(Self { Ok(Self {
@ -116,7 +116,7 @@ impl Actionable for CreateApfsVolume {
vec![] vec![]
} else { } else {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Create an APFS volume", destination.display()), format!("Create an APFS volume `{name}` on `{}`", disk.display()),
vec![format!( vec![format!(
"Create a writable, persistent systemd system extension.", "Create a writable, persistent systemd system extension.",
)], )],
@ -142,18 +142,21 @@ impl Actionable for CreateApfsVolume {
action_state, action_state,
} = self; } = self;
if *action_state == ActionState::Completed { if *action_state == ActionState::Completed {
tracing::trace!("Already completed: Creating sysext"); tracing::trace!("Already completed: Creating APFS volume");
return Ok(()); return Ok(());
} }
tracing::debug!("Creating sysext"); tracing::debug!("Creating APFS volume");
for create_directory in create_directories { create_or_append_synthetic_conf.execute().await?;
create_directory.execute().await?; create_synthetic_objects.execute().await?;
} unmount_volume.execute().await?;
create_extension_release.execute().await?; create_volume.execute().await?;
create_bind_mount_unit.execute().await?; create_or_append_fstab.execute().await?;
encrypt_volume.execute().await?;
bootstrap_volume.execute().await?;
enable_ownership.execute().await?;
tracing::trace!("Created sysext"); tracing::trace!("Created APFS volume");
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
@ -178,19 +181,21 @@ impl Actionable for CreateApfsVolume {
vec![] vec![]
} else { } else {
vec![ActionDescription::new( vec![ActionDescription::new(
format!("Remove the sysext located at `{}`", destination.display()), format!("Remove the APFS volume `{name}` on `{}`", disk.display()),
vec![], vec![format!(
"Create a writable, persistent systemd system extension.",
)],
)] )]
} }
} }
#[tracing::instrument(skip_all, fields(destination,))] #[tracing::instrument(skip_all, fields(disk, name))]
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
let Self { let Self {
disk, disk: _,
name, name: _,
case_sensitive, case_sensitive: _,
encrypt, encrypt: _,
create_or_append_synthetic_conf, create_or_append_synthetic_conf,
create_synthetic_objects, create_synthetic_objects,
unmount_volume, unmount_volume,
@ -202,20 +207,26 @@ impl Actionable for CreateApfsVolume {
action_state, action_state,
} = self; } = self;
if *action_state == ActionState::Uncompleted { if *action_state == ActionState::Uncompleted {
tracing::trace!("Already reverted: Removing sysext"); tracing::trace!("Already reverted: Removing APFS volume");
return Ok(()); return Ok(());
} }
tracing::debug!("Removing sysext"); tracing::debug!("Removing APFS volume");
create_bind_mount_unit.revert().await?; enable_ownership.revert().await?;
bootstrap_volume.revert().await?;
create_extension_release.revert().await?; if let Some(encrypt_volume) = encrypt_volume {
encrypt_volume.revert().await?;
for create_directory in create_directories.iter_mut().rev() {
create_directory.revert().await?;
} }
create_or_append_fstab.revert().await?;
tracing::trace!("Removed sysext"); unmount_volume.revert().await?;
create_volume.revert().await?;
// Purposefully not reversed
create_or_append_synthetic_conf.revert().await?;
create_synthetic_objects.revert().await?;
tracing::trace!("Removed APFS volume");
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
@ -229,6 +240,18 @@ impl From<CreateApfsVolume> for Action {
#[derive(Debug, thiserror::Error, Serialize)] #[derive(Debug, thiserror::Error, Serialize)]
pub enum CreateApfsVolumeError { pub enum CreateApfsVolumeError {
#[error(transparent)]
DarwinBootstrapVolume(#[from] BootstrapVolumeError),
#[error(transparent)]
DarwinCreateSyntheticObjects(#[from] CreateSyntheticObjectsError),
#[error(transparent)]
DarwinCreateVolume(#[from] CreateVolumeError),
#[error(transparent)]
DarwinEnableOwnership(#[from] EnableOwnershipError),
#[error(transparent)]
DarwinEncryptVolume(#[from] EncryptVolumeError),
#[error(transparent)]
DarwinUnmountVolume(#[from] UnmountVolumeError),
#[error(transparent)] #[error(transparent)]
CreateOrAppendFile(#[from] CreateOrAppendFileError), CreateOrAppendFile(#[from] CreateOrAppendFileError),
} }

View file

@ -57,6 +57,12 @@ impl ActionDescription {
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub enum Action { pub enum Action {
DarwinBootstrapVolume(base::darwin::BootstrapVolume),
DarwinCreateSyntheticObjects(base::darwin::CreateSyntheticObjects),
DarwinCreateVolume(base::darwin::CreateVolume),
DarwinEnableOwnership(base::darwin::EnableOwnership),
DarwinEncryptVolume(base::darwin::EncryptVolume),
DarwinUnmountVolume(base::darwin::UnmountVolume),
ConfigureNix(ConfigureNix), ConfigureNix(ConfigureNix),
ConfigureNixDaemonService(ConfigureNixDaemonService), ConfigureNixDaemonService(ConfigureNixDaemonService),
ConfigureShellProfile(ConfigureShellProfile), ConfigureShellProfile(ConfigureShellProfile),
@ -82,6 +88,18 @@ pub enum Action {
#[derive(Debug, thiserror::Error, serde::Serialize)] #[derive(Debug, thiserror::Error, serde::Serialize)]
pub enum ActionError { pub enum ActionError {
#[error(transparent)]
DarwinBootstrapVolume(#[from] base::darwin::BootstrapVolumeError),
#[error(transparent)]
DarwinCreateSyntheticObjects(#[from] base::darwin::CreateSyntheticObjectsError),
#[error(transparent)]
DarwinCreateVolume(#[from] base::darwin::CreateVolumeError),
#[error(transparent)]
DarwinEnableOwnership(#[from] base::darwin::EnableOwnershipError),
#[error(transparent)]
DarwinEncryptVolume(#[from] base::darwin::EncryptVolumeError),
#[error(transparent)]
DarwinUnmountVolume(#[from] base::darwin::UnmountVolumeError),
#[error("Attempted to revert an unexecuted action")] #[error("Attempted to revert an unexecuted action")]
NotExecuted(Action), NotExecuted(Action),
#[error("Attempted to execute an already executed action")] #[error("Attempted to execute an already executed action")]
@ -137,6 +155,12 @@ impl Actionable for Action {
type Error = ActionError; type Error = ActionError;
fn describe_execute(&self) -> Vec<ActionDescription> { fn describe_execute(&self) -> Vec<ActionDescription> {
match self { match self {
Action::DarwinBootstrapVolume(i) => i.describe_execute(),
Action::DarwinCreateSyntheticObjects(i) => i.describe_execute(),
Action::DarwinCreateVolume(i) => i.describe_execute(),
Action::DarwinEnableOwnership(i) => i.describe_execute(),
Action::DarwinEncryptVolume(i) => i.describe_execute(),
Action::DarwinUnmountVolume(i) => i.describe_execute(),
Action::ConfigureNix(i) => i.describe_execute(), Action::ConfigureNix(i) => i.describe_execute(),
Action::ConfigureNixDaemonService(i) => i.describe_execute(), Action::ConfigureNixDaemonService(i) => i.describe_execute(),
Action::ConfigureShellProfile(i) => i.describe_execute(), Action::ConfigureShellProfile(i) => i.describe_execute(),
@ -163,6 +187,12 @@ impl Actionable for Action {
async fn execute(&mut self) -> Result<(), Self::Error> { async fn execute(&mut self) -> Result<(), Self::Error> {
match self { match self {
Action::DarwinBootstrapVolume(i) => i.execute().await?,
Action::DarwinCreateSyntheticObjects(i) => i.execute().await?,
Action::DarwinCreateVolume(i) => i.execute().await?,
Action::DarwinEnableOwnership(i) => i.execute().await?,
Action::DarwinEncryptVolume(i) => i.execute().await?,
Action::DarwinUnmountVolume(i) => i.execute().await?,
Action::ConfigureNix(i) => i.execute().await?, Action::ConfigureNix(i) => i.execute().await?,
Action::ConfigureNixDaemonService(i) => i.execute().await?, Action::ConfigureNixDaemonService(i) => i.execute().await?,
Action::ConfigureShellProfile(i) => i.execute().await?, Action::ConfigureShellProfile(i) => i.execute().await?,
@ -190,6 +220,12 @@ impl Actionable for Action {
fn describe_revert(&self) -> Vec<ActionDescription> { fn describe_revert(&self) -> Vec<ActionDescription> {
match self { match self {
Action::DarwinBootstrapVolume(i) => i.describe_revert(),
Action::DarwinCreateSyntheticObjects(i) => i.describe_revert(),
Action::DarwinCreateVolume(i) => i.describe_revert(),
Action::DarwinEnableOwnership(i) => i.describe_revert(),
Action::DarwinEncryptVolume(i) => i.describe_revert(),
Action::DarwinUnmountVolume(i) => i.describe_revert(),
Action::ConfigureNix(i) => i.describe_revert(), Action::ConfigureNix(i) => i.describe_revert(),
Action::ConfigureNixDaemonService(i) => i.describe_revert(), Action::ConfigureNixDaemonService(i) => i.describe_revert(),
Action::ConfigureShellProfile(i) => i.describe_revert(), Action::ConfigureShellProfile(i) => i.describe_revert(),
@ -216,6 +252,12 @@ impl Actionable for Action {
async fn revert(&mut self) -> Result<(), Self::Error> { async fn revert(&mut self) -> Result<(), Self::Error> {
match self { match self {
Action::DarwinBootstrapVolume(i) => i.revert().await?,
Action::DarwinCreateSyntheticObjects(i) => i.revert().await?,
Action::DarwinCreateVolume(i) => i.revert().await?,
Action::DarwinEnableOwnership(i) => i.revert().await?,
Action::DarwinEncryptVolume(i) => i.revert().await?,
Action::DarwinUnmountVolume(i) => i.revert().await?,
Action::ConfigureNix(i) => i.revert().await?, Action::ConfigureNix(i) => i.revert().await?,
Action::ConfigureNixDaemonService(i) => i.revert().await?, Action::ConfigureNixDaemonService(i) => i.revert().await?,
Action::ConfigureShellProfile(i) => i.revert().await?, Action::ConfigureShellProfile(i) => i.revert().await?,

View file

@ -4,7 +4,11 @@ mod plan;
mod planner; mod planner;
mod settings; mod settings;
use std::{ffi::OsStr, fmt::Display, process::ExitStatus}; use std::{
ffi::OsStr,
fmt::Display,
process::{ExitStatus, Output},
};
pub use error::HarmonicError; pub use error::HarmonicError;
pub use plan::InstallPlan; pub use plan::InstallPlan;
@ -15,12 +19,12 @@ pub use settings::InstallSettings;
use tokio::process::Command; use tokio::process::Command;
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))] #[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
async fn execute_command(command: &mut Command) -> Result<ExitStatus, std::io::Error> { async fn execute_command(command: &mut Command) -> Result<Output, std::io::Error> {
tracing::trace!("Executing"); tracing::trace!("Executing");
let command_str = format!("{:?}", command.as_std()); let command_str = format!("{:?}", command.as_std());
let status = command.status().await?; let output = command.output().await?;
match status.success() { match output.status.success() {
true => Ok(status), true => Ok(output),
false => Err(std::io::Error::new( false => Err(std::io::Error::new(
std::io::ErrorKind::Other, std::io::ErrorKind::Other,
format!("Command `{command_str}` failed status"), format!("Command `{command_str}` failed status"),

View file

@ -1,9 +1,12 @@
use tokio::process::Command;
use crate::{ use crate::{
actions::{ actions::{
meta::{darwin::CreateApfsVolume, ConfigureNix, ProvisionNix, StartNixDaemon}, meta::{darwin::CreateApfsVolume, ConfigureNix, ProvisionNix, StartNixDaemon},
Action, ActionError, Action, ActionError,
}, },
planner::Plannable, execute_command,
planner::{Plannable, PlannerError},
InstallPlan, Planner, InstallPlan, Planner,
}; };
@ -18,6 +21,28 @@ impl Plannable for DarwinMultiUser {
async fn plan( async fn plan(
settings: crate::InstallSettings, settings: crate::InstallSettings,
) -> Result<crate::InstallPlan, crate::planner::PlannerError> { ) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
let root_disk = {
let root_disk_buf =
execute_command(Command::new("/usr/sbin/diskutil").args(["info", "-plist", "/"]))
.await
.unwrap()
.stdout;
let package =
sxd_document::parser::parse(&String::from_utf8(root_disk_buf).unwrap()).unwrap();
match sxd_xpath::evaluate_xpath(
&package.as_document(),
"/plist/dict/key[text()='ParentWholeDisk']/following-sibling::string[1]/text()",
)
.unwrap()
{
sxd_xpath::Value::String(s) => s,
_ => panic!("At the disk i/o!!!"),
}
};
let volume_label = "Nix Store".into();
Ok(InstallPlan { Ok(InstallPlan {
planner: Self.into(), planner: Self.into(),
settings: settings.clone(), settings: settings.clone(),
@ -26,7 +51,7 @@ impl Plannable for DarwinMultiUser {
// //
// setup_Synthetic -> create_synthetic_objects // setup_Synthetic -> create_synthetic_objects
// Unmount -> create_volume -> Setup_fstab -> maybe encrypt_volume -> launchctl bootstrap -> launchctl kickstart -> await_volume -> maybe enableOwnership // Unmount -> create_volume -> Setup_fstab -> maybe encrypt_volume -> launchctl bootstrap -> launchctl kickstart -> await_volume -> maybe enableOwnership
CreateApfsVolume::plan(settings.clone()) CreateApfsVolume::plan(root_disk, volume_label, false, None)
.await .await
.map(Action::from) .map(Action::from)
.map_err(ActionError::from)?, .map_err(ActionError::from)?,