forked from lix-project/lix-installer
Flesh out docs and tidy up public API substantially (#67)
* Make plans versioned * Delint * speeeeeeeeling * remove file that was dead * Flesh out docs and improve public API * Speeling * Fixups * Fix doctests * Do a better job with actionstate * Add some more docs * Fix doctest * Make CLI stuff optional * Touchup * Speeling
This commit is contained in:
parent
f1d12149de
commit
c39bf0a510
69
Cargo.lock
generated
69
Cargo.lock
generated
|
@ -594,12 +594,6 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuchsia-cprng"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.1.31"
|
version = "0.1.31"
|
||||||
|
@ -795,7 +789,7 @@ dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
"plist",
|
"plist",
|
||||||
"rand 0.8.5",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"semver",
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -805,7 +799,6 @@ dependencies = [
|
||||||
"sxd-xpath",
|
"sxd-xpath",
|
||||||
"tar",
|
"tar",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tempdir",
|
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1389,19 +1382,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
|
||||||
dependencies = [
|
|
||||||
"fuchsia-cprng",
|
|
||||||
"libc",
|
|
||||||
"rand_core 0.3.1",
|
|
||||||
"rdrand",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
@ -1410,7 +1390,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core 0.6.4",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1420,24 +1400,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core 0.6.4",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core 0.4.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
@ -1447,15 +1412,6 @@ dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rdrand"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core 0.3.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -1500,15 +1456,6 @@ version = "0.6.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "remove_dir_all"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
|
||||||
dependencies = [
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.13"
|
version = "0.11.13"
|
||||||
|
@ -1849,16 +1796,6 @@ version = "0.12.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d"
|
checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tempdir"
|
|
||||||
version = "0.3.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
|
||||||
dependencies = [
|
|
||||||
"rand 0.4.6",
|
|
||||||
"remove_dir_all",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
23
Cargo.toml
23
Cargo.toml
|
@ -11,15 +11,23 @@ build-inputs = ["darwin.apple_sdk.frameworks.Security"]
|
||||||
[package.metadata.riff.targets.x86_64-apple-darwin]
|
[package.metadata.riff.targets.x86_64-apple-darwin]
|
||||||
build-inputs = ["darwin.apple_sdk.frameworks.Security"]
|
build-inputs = ["darwin.apple_sdk.frameworks.Security"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["cli"]
|
||||||
|
cli = [ "eyre", "color-eyre", "crossterm", "clap", "tracing-subscriber", "tracing-error", "atty" ]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "harmonic"
|
||||||
|
required-features = [ "cli" ]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
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 = { version = "0.2.14", optional = true }
|
||||||
bytes = "1.2.1"
|
bytes = "1.2.1"
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"], optional = true }
|
||||||
color-eyre = "0.6.2"
|
color-eyre = { version = "0.6.2", optional = true }
|
||||||
crossterm = { version = "0.25.0", features = ["event-stream"] }
|
crossterm = { version = "0.25.0", features = ["event-stream"], optional = true }
|
||||||
eyre = "0.6.8"
|
eyre = { version = "0.6.8", optional = true }
|
||||||
futures = "0.3.24"
|
futures = "0.3.24"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
nix = { version = "0.25.0", features = ["user", "fs", "process", "term"], default-features = false }
|
nix = { version = "0.25.0", features = ["user", "fs", "process", "term"], default-features = false }
|
||||||
|
@ -30,13 +38,12 @@ serde_json = "1.0.85"
|
||||||
serde_with = "2.0.1"
|
serde_with = "2.0.1"
|
||||||
tar = "0.4.38"
|
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", "signal", "tracing", "rt-multi-thread", "macros", "io-util", "parking_lot" ] }
|
tokio = { version = "1.21.0", features = ["time", "io-std", "process", "fs", "signal", "tracing", "rt-multi-thread", "macros", "io-util", "parking_lot" ] }
|
||||||
tokio-util = { version = "0.7", features = ["io"] }
|
tokio-util = { version = "0.7", features = ["io"] }
|
||||||
tracing = { version = "0.1.36", features = [ "valuable" ] }
|
tracing = { version = "0.1.36", features = [ "valuable" ] }
|
||||||
tracing-error = "0.2.0"
|
tracing-error = { version = "0.2.0", optional = true }
|
||||||
tracing-subscriber = { version = "0.3.15", features = [ "env-filter", "valuable" ] }
|
tracing-subscriber = { version = "0.3.15", features = [ "env-filter", "valuable" ], optional = true }
|
||||||
url = { version = "2.3.1", features = ["serde"] }
|
url = { version = "2.3.1", features = ["serde"] }
|
||||||
valuable = { version = "0.1.0", features = ["derive"] }
|
valuable = { version = "0.1.0", features = ["derive"] }
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
|
|
|
@ -121,14 +121,16 @@
|
||||||
pkg-config
|
pkg-config
|
||||||
];
|
];
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
|
|
||||||
openssl
|
openssl
|
||||||
] ++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs.darwin.apple_sdk.frameworks; [
|
] ++ lib.optionals (pkgs.stdenv.isDarwin) (with pkgs.darwin.apple_sdk.frameworks; [
|
||||||
SystemConfiguration
|
SystemConfiguration
|
||||||
]);
|
]);
|
||||||
|
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
|
doDoc = true;
|
||||||
|
doDocFail = true;
|
||||||
RUSTFLAGS = "--cfg tracing_unstable --cfg tokio_unstable";
|
RUSTFLAGS = "--cfg tracing_unstable --cfg tokio_unstable";
|
||||||
|
cargoTestOptions = f: f ++ ["--all"];
|
||||||
|
|
||||||
override = { preBuild ? "", ... }: {
|
override = { preBuild ? "", ... }: {
|
||||||
preBuild = preBuild + ''
|
preBuild = preBuild + ''
|
||||||
|
|
|
@ -5,18 +5,23 @@ use nix::unistd::{chown, Group, User};
|
||||||
|
|
||||||
use tokio::fs::{create_dir, remove_dir_all};
|
use tokio::fs::{create_dir, remove_dir_all};
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, ActionState},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Create a directory at the given location, optionally with an owning user, group, and mode.
|
||||||
|
|
||||||
|
If `force_prune_on_revert` is set, the folder will always be deleted on
|
||||||
|
[`revert`](CreateDirectory::revert).
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateDirectory {
|
pub struct CreateDirectory {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
user: Option<String>,
|
user: Option<String>,
|
||||||
group: Option<String>,
|
group: Option<String>,
|
||||||
mode: Option<u32>,
|
mode: Option<u32>,
|
||||||
action_state: ActionState,
|
|
||||||
force_prune_on_revert: bool,
|
force_prune_on_revert: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +33,7 @@ impl CreateDirectory {
|
||||||
group: impl Into<Option<String>>,
|
group: impl Into<Option<String>>,
|
||||||
mode: impl Into<Option<u32>>,
|
mode: impl Into<Option<u32>>,
|
||||||
force_prune_on_revert: bool,
|
force_prune_on_revert: bool,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let path = path.as_ref();
|
let path = path.as_ref();
|
||||||
let user = user.into();
|
let user = user.into();
|
||||||
let group = group.into();
|
let group = group.into();
|
||||||
|
@ -59,13 +64,15 @@ impl CreateDirectory {
|
||||||
ActionState::Uncompleted
|
ActionState::Uncompleted
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(StatefulAction {
|
||||||
path: path.to_path_buf(),
|
action: Self {
|
||||||
user,
|
path: path.to_path_buf(),
|
||||||
group,
|
user,
|
||||||
mode,
|
group,
|
||||||
force_prune_on_revert,
|
mode,
|
||||||
action_state,
|
force_prune_on_revert,
|
||||||
|
},
|
||||||
|
state: action_state,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +101,6 @@ impl Action for CreateDirectory {
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
force_prune_on_revert: _,
|
force_prune_on_revert: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let gid = if let Some(group) = group {
|
let gid = if let Some(group) = group {
|
||||||
|
@ -141,7 +147,6 @@ impl Action for CreateDirectory {
|
||||||
group: _,
|
group: _,
|
||||||
mode: _,
|
mode: _,
|
||||||
force_prune_on_revert,
|
force_prune_on_revert,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!(
|
format!(
|
||||||
|
@ -170,7 +175,6 @@ impl Action for CreateDirectory {
|
||||||
group: _,
|
group: _,
|
||||||
mode: _,
|
mode: _,
|
||||||
force_prune_on_revert,
|
force_prune_on_revert,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let is_empty = path
|
let is_empty = path
|
||||||
|
@ -189,14 +193,6 @@ impl Action for CreateDirectory {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -7,10 +7,16 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Create a file at the given location with the provided `buf`,
|
||||||
|
optionally with an owning user, group, and mode.
|
||||||
|
|
||||||
|
If `force` is set, the file will always be overwritten (and deleted)
|
||||||
|
regardless of its presence prior to install.
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateFile {
|
pub struct CreateFile {
|
||||||
pub(crate) path: PathBuf,
|
pub(crate) path: PathBuf,
|
||||||
|
@ -19,7 +25,6 @@ pub struct CreateFile {
|
||||||
mode: Option<u32>,
|
mode: Option<u32>,
|
||||||
buf: String,
|
buf: String,
|
||||||
force: bool,
|
force: bool,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateFile {
|
impl CreateFile {
|
||||||
|
@ -31,7 +36,7 @@ impl CreateFile {
|
||||||
mode: impl Into<Option<u32>>,
|
mode: impl Into<Option<u32>>,
|
||||||
buf: String,
|
buf: String,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
|
|
||||||
if path.exists() && !force {
|
if path.exists() && !force {
|
||||||
|
@ -45,8 +50,8 @@ impl CreateFile {
|
||||||
mode: mode.into(),
|
mode: mode.into(),
|
||||||
buf,
|
buf,
|
||||||
force,
|
force,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +79,6 @@ impl Action for CreateFile {
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
force: _,
|
force: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut options = OpenOptions::new();
|
let mut options = OpenOptions::new();
|
||||||
|
@ -126,7 +130,6 @@ impl Action for CreateFile {
|
||||||
mode: _,
|
mode: _,
|
||||||
buf: _,
|
buf: _,
|
||||||
force: _,
|
force: _,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
@ -149,7 +152,6 @@ impl Action for CreateFile {
|
||||||
mode: _,
|
mode: _,
|
||||||
buf: _,
|
buf: _,
|
||||||
force: _,
|
force: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
remove_file(&path)
|
remove_file(&path)
|
||||||
|
@ -158,14 +160,6 @@ impl Action for CreateFile {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -3,25 +3,23 @@ use tokio::process::Command;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create an operating system level user group
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateGroup {
|
pub struct CreateGroup {
|
||||||
name: String,
|
name: String,
|
||||||
gid: usize,
|
gid: usize,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateGroup {
|
impl CreateGroup {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn plan(name: String, gid: usize) -> Self {
|
pub fn plan(name: String, gid: usize) -> StatefulAction<Self> {
|
||||||
Self {
|
Self { name, gid }.into()
|
||||||
name,
|
|
||||||
gid,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +30,7 @@ impl Action for CreateGroup {
|
||||||
format!("Create group `{}` (GID {})", self.name, self.gid)
|
format!("Create group `{}` (GID {})", self.name, self.gid)
|
||||||
}
|
}
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self { name: _, gid: _ } = &self;
|
||||||
name: _,
|
|
||||||
gid: _,
|
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
self.tracing_synopsis(),
|
self.tracing_synopsis(),
|
||||||
vec![format!(
|
vec![format!(
|
||||||
|
@ -50,11 +44,7 @@ impl Action for CreateGroup {
|
||||||
gid = self.gid,
|
gid = self.gid,
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { name, gid } = self;
|
||||||
name,
|
|
||||||
gid,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
use target_lexicon::OperatingSystem;
|
use target_lexicon::OperatingSystem;
|
||||||
match target_lexicon::OperatingSystem::host() {
|
match target_lexicon::OperatingSystem::host() {
|
||||||
|
@ -109,11 +99,7 @@ impl Action for CreateGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self { name, gid } = &self;
|
||||||
name,
|
|
||||||
gid,
|
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Delete group `{name}` (GID {gid})"),
|
format!("Delete group `{name}` (GID {gid})"),
|
||||||
vec![format!(
|
vec![format!(
|
||||||
|
@ -127,11 +113,7 @@ impl Action for CreateGroup {
|
||||||
gid = self.gid,
|
gid = self.gid,
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { name, gid: _ } = self;
|
||||||
name,
|
|
||||||
gid: _,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
use target_lexicon::OperatingSystem;
|
use target_lexicon::OperatingSystem;
|
||||||
match target_lexicon::OperatingSystem::host() {
|
match target_lexicon::OperatingSystem::host() {
|
||||||
|
@ -166,14 +148,6 @@ impl Action for CreateGroup {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -11,10 +11,18 @@ use tokio::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Create a file at the given location with the provided `buf`,
|
||||||
|
optionally with an owning user, group, and mode.
|
||||||
|
|
||||||
|
If the file exists, the provided `buf` will be appended.
|
||||||
|
|
||||||
|
If `force` is set, the file will always be overwritten (and deleted)
|
||||||
|
regardless of its presence prior to install.
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateOrAppendFile {
|
pub struct CreateOrAppendFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -22,7 +30,6 @@ pub struct CreateOrAppendFile {
|
||||||
group: Option<String>,
|
group: Option<String>,
|
||||||
mode: Option<u32>,
|
mode: Option<u32>,
|
||||||
buf: String,
|
buf: String,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateOrAppendFile {
|
impl CreateOrAppendFile {
|
||||||
|
@ -33,7 +40,7 @@ impl CreateOrAppendFile {
|
||||||
group: impl Into<Option<String>>,
|
group: impl Into<Option<String>>,
|
||||||
mode: impl Into<Option<u32>>,
|
mode: impl Into<Option<u32>>,
|
||||||
buf: String,
|
buf: String,
|
||||||
) -> Result<Self, CreateOrAppendFileError> {
|
) -> Result<StatefulAction<Self>, CreateOrAppendFileError> {
|
||||||
let path = path.as_ref().to_path_buf();
|
let path = path.as_ref().to_path_buf();
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
@ -42,8 +49,8 @@ impl CreateOrAppendFile {
|
||||||
group: group.into(),
|
group: group.into(),
|
||||||
mode: mode.into(),
|
mode: mode.into(),
|
||||||
buf,
|
buf,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +78,6 @@ impl Action for CreateOrAppendFile {
|
||||||
group,
|
group,
|
||||||
mode,
|
mode,
|
||||||
buf,
|
buf,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
|
@ -132,7 +138,6 @@ impl Action for CreateOrAppendFile {
|
||||||
group: _,
|
group: _,
|
||||||
mode: _,
|
mode: _,
|
||||||
buf,
|
buf,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Delete Nix related fragment from file `{}`", path.display()),
|
format!("Delete Nix related fragment from file `{}`", path.display()),
|
||||||
|
@ -156,7 +161,6 @@ impl Action for CreateOrAppendFile {
|
||||||
group: _,
|
group: _,
|
||||||
mode: _,
|
mode: _,
|
||||||
buf,
|
buf,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.create(false)
|
.create(false)
|
||||||
|
@ -190,14 +194,6 @@ impl Action for CreateOrAppendFile {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -3,29 +3,31 @@ use tokio::process::Command;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create an operating system level user in the given group
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateUser {
|
pub struct CreateUser {
|
||||||
name: String,
|
name: String,
|
||||||
uid: usize,
|
uid: usize,
|
||||||
groupname: String,
|
groupname: String,
|
||||||
gid: usize,
|
gid: usize,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUser {
|
impl CreateUser {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn plan(name: String, uid: usize, groupname: String, gid: usize) -> Self {
|
pub fn plan(name: String, uid: usize, groupname: String, gid: usize) -> StatefulAction<Self> {
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
uid,
|
uid,
|
||||||
groupname,
|
groupname,
|
||||||
gid,
|
gid,
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
}
|
}
|
||||||
|
.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +61,6 @@ impl Action for CreateUser {
|
||||||
uid,
|
uid,
|
||||||
groupname,
|
groupname,
|
||||||
gid,
|
gid,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
use target_lexicon::OperatingSystem;
|
use target_lexicon::OperatingSystem;
|
||||||
|
@ -241,7 +242,6 @@ impl Action for CreateUser {
|
||||||
uid: _,
|
uid: _,
|
||||||
groupname: _,
|
groupname: _,
|
||||||
gid: _,
|
gid: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
use target_lexicon::OperatingSystem;
|
use target_lexicon::OperatingSystem;
|
||||||
|
@ -277,14 +277,6 @@ impl Action for CreateUser {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -6,46 +6,38 @@ use reqwest::Url;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Fetch a URL to the given path
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct FetchNix {
|
pub struct FetchAndUnpackNix {
|
||||||
url: Url,
|
url: Url,
|
||||||
dest: PathBuf,
|
dest: PathBuf,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FetchNix {
|
impl FetchAndUnpackNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(url: Url, dest: PathBuf) -> Result<Self, FetchNixError> {
|
pub async fn plan(url: Url, dest: PathBuf) -> Result<StatefulAction<Self>, FetchUrlError> {
|
||||||
// TODO(@hoverbear): Check URL exists?
|
// TODO(@hoverbear): Check URL exists?
|
||||||
// TODO(@hoverbear): Check tempdir exists
|
// TODO(@hoverbear): Check tempdir exists
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self { url, dest }.into())
|
||||||
url,
|
|
||||||
dest,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "fetch_nix")]
|
#[typetag::serde(name = "fetch_and_unpack_nix")]
|
||||||
impl Action for FetchNix {
|
impl Action for FetchAndUnpackNix {
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Fetch Nix from `{}`", self.url)
|
format!("Fetch `{}` to `{}`", self.url, self.dest.display())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||||
self.tracing_synopsis(),
|
|
||||||
vec![format!(
|
|
||||||
"Unpack it to `{}` (moved later)",
|
|
||||||
self.dest.display()
|
|
||||||
)],
|
|
||||||
)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
@ -53,19 +45,15 @@ impl Action for FetchNix {
|
||||||
dest = %self.dest.display(),
|
dest = %self.dest.display(),
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { url, dest } = self;
|
||||||
url,
|
|
||||||
dest,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let res = reqwest::get(url.clone())
|
let res = reqwest::get(url.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| FetchNixError::Reqwest(e).boxed())?;
|
.map_err(|e| FetchUrlError::Reqwest(e).boxed())?;
|
||||||
let bytes = res
|
let bytes = res
|
||||||
.bytes()
|
.bytes()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| FetchNixError::Reqwest(e).boxed())?;
|
.map_err(|e| FetchUrlError::Reqwest(e).boxed())?;
|
||||||
// TODO(@Hoverbear): Pick directory
|
// TODO(@Hoverbear): Pick directory
|
||||||
tracing::trace!("Unpacking tar.xz");
|
tracing::trace!("Unpacking tar.xz");
|
||||||
let dest_clone = dest.clone();
|
let dest_clone = dest.clone();
|
||||||
|
@ -74,7 +62,7 @@ impl Action for FetchNix {
|
||||||
let mut archive = tar::Archive::new(decoder);
|
let mut archive = tar::Archive::new(decoder);
|
||||||
archive
|
archive
|
||||||
.unpack(&dest_clone)
|
.unpack(&dest_clone)
|
||||||
.map_err(|e| FetchNixError::Unarchive(e).boxed())?;
|
.map_err(|e| FetchUrlError::Unarchive(e).boxed())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -88,26 +76,14 @@ impl Action for FetchNix {
|
||||||
dest = %self.dest.display(),
|
dest = %self.dest.display(),
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { url: _, dest: _ } = self;
|
||||||
url: _,
|
|
||||||
dest: _,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum FetchNixError {
|
pub enum FetchUrlError {
|
||||||
#[error("Joining spawned async task")]
|
#[error("Joining spawned async task")]
|
||||||
Join(
|
Join(
|
||||||
#[source]
|
#[source]
|
|
@ -1,21 +1,19 @@
|
||||||
//! Base actions that themselves have no other actions as dependencies
|
//! Base [`Action`](crate::action::Action)s that themselves have no other actions as dependencies
|
||||||
|
|
||||||
mod configure_nix_daemon_service;
|
|
||||||
mod create_directory;
|
mod create_directory;
|
||||||
mod create_file;
|
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;
|
||||||
mod fetch_nix;
|
mod fetch_and_unpack_nix;
|
||||||
mod move_unpacked_nix;
|
mod move_unpacked_nix;
|
||||||
mod setup_default_profile;
|
mod setup_default_profile;
|
||||||
|
|
||||||
pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError};
|
|
||||||
pub use create_directory::{CreateDirectory, CreateDirectoryError};
|
pub use create_directory::{CreateDirectory, CreateDirectoryError};
|
||||||
pub use create_file::{CreateFile, CreateFileError};
|
pub use create_file::{CreateFile, CreateFileError};
|
||||||
pub use create_group::{CreateGroup, CreateGroupError};
|
pub use create_group::{CreateGroup, CreateGroupError};
|
||||||
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError};
|
pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError};
|
||||||
pub use create_user::{CreateUser, CreateUserError};
|
pub use create_user::{CreateUser, CreateUserError};
|
||||||
pub use fetch_nix::{FetchNix, FetchNixError};
|
pub use fetch_and_unpack_nix::{FetchAndUnpackNix, FetchUrlError};
|
||||||
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
|
pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError};
|
||||||
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};
|
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEST: &str = "/nix/store";
|
const DEST: &str = "/nix/store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Move an unpacked Nix at `src` to `/nix`
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct MoveUnpackedNix {
|
pub struct MoveUnpackedNix {
|
||||||
src: PathBuf,
|
src: PathBuf,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MoveUnpackedNix {
|
impl MoveUnpackedNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(src: PathBuf) -> Result<Self, MoveUnpackedNixError> {
|
pub async fn plan(src: PathBuf) -> Result<StatefulAction<Self>, MoveUnpackedNixError> {
|
||||||
// Note: Do NOT try to check for the src/dest since the installer creates those
|
// Note: Do NOT try to check for the src/dest since the installer creates those
|
||||||
Ok(Self {
|
Ok(Self { src }.into())
|
||||||
src,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,12 +45,7 @@ impl Action for MoveUnpackedNix {
|
||||||
dest = DEST,
|
dest = DEST,
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self { src, action_state } = self;
|
let Self { src } = self;
|
||||||
if *action_state == ActionState::Completed {
|
|
||||||
tracing::trace!("Already completed: Moving Nix");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
tracing::debug!("Moving Nix");
|
|
||||||
|
|
||||||
// TODO(@Hoverbear): I would like to make this less awful
|
// TODO(@Hoverbear): I would like to make this less awful
|
||||||
let found_nix_paths = glob::glob(&format!("{}/nix-*", src.display()))
|
let found_nix_paths = glob::glob(&format!("{}/nix-*", src.display()))
|
||||||
|
@ -76,8 +70,7 @@ impl Action for MoveUnpackedNix {
|
||||||
tokio::fs::remove_dir_all(src)
|
tokio::fs::remove_dir_all(src)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| MoveUnpackedNixError::Rename(src_store, dest.to_owned(), e).boxed())?;
|
.map_err(|e| MoveUnpackedNixError::Rename(src_store, dest.to_owned(), e).boxed())?;
|
||||||
tracing::trace!("Moved Nix");
|
|
||||||
*action_state = ActionState::Completed;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,14 +86,6 @@ impl Action for MoveUnpackedNix {
|
||||||
// Noop
|
// Noop
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{action::ActionState, execute_command, set_env, BoxableError};
|
use crate::{action::StatefulAction, execute_command, set_env, BoxableError};
|
||||||
|
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
|
|
||||||
|
@ -6,19 +6,20 @@ use tokio::process::Command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription};
|
use crate::action::{Action, ActionDescription};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Setup the default Nix profile with `nss-cacert` and `nix` itself.
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct SetupDefaultProfile {
|
pub struct SetupDefaultProfile {
|
||||||
channels: Vec<String>,
|
channels: Vec<String>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SetupDefaultProfile {
|
impl SetupDefaultProfile {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(channels: Vec<String>) -> Result<Self, SetupDefaultProfileError> {
|
pub async fn plan(
|
||||||
Ok(Self {
|
channels: Vec<String>,
|
||||||
channels,
|
) -> Result<StatefulAction<Self>, SetupDefaultProfileError> {
|
||||||
action_state: ActionState::Uncompleted,
|
Ok(Self { channels }.into())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,10 +38,7 @@ impl Action for SetupDefaultProfile {
|
||||||
channels = %self.channels.join(","),
|
channels = %self.channels.join(","),
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { channels } = self;
|
||||||
channels,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
// Find an `nix` package
|
// Find an `nix` package
|
||||||
let nix_pkg_glob = "/nix/store/*-nix-*";
|
let nix_pkg_glob = "/nix/store/*-nix-*";
|
||||||
|
@ -159,14 +157,6 @@ impl Action for SetupDefaultProfile {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,30 +1,34 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{
|
action::{
|
||||||
base::{ConfigureNixDaemonService, SetupDefaultProfile},
|
base::SetupDefaultProfile,
|
||||||
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
|
common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
|
||||||
Action, ActionDescription, ActionImplementation, ActionState,
|
linux::ConfigureNixDaemonService,
|
||||||
|
Action, ActionDescription, StatefulAction,
|
||||||
},
|
},
|
||||||
channel_value::ChannelValue,
|
channel_value::ChannelValue,
|
||||||
BoxableError, CommonSettings,
|
settings::CommonSettings,
|
||||||
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configure Nix and start it
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ConfigureNix {
|
pub struct ConfigureNix {
|
||||||
setup_default_profile: SetupDefaultProfile,
|
setup_default_profile: StatefulAction<SetupDefaultProfile>,
|
||||||
configure_shell_profile: Option<ConfigureShellProfile>,
|
configure_shell_profile: Option<StatefulAction<ConfigureShellProfile>>,
|
||||||
place_channel_configuration: PlaceChannelConfiguration,
|
place_channel_configuration: StatefulAction<PlaceChannelConfiguration>,
|
||||||
place_nix_configuration: PlaceNixConfiguration,
|
place_nix_configuration: StatefulAction<PlaceNixConfiguration>,
|
||||||
configure_nix_daemon_service: ConfigureNixDaemonService,
|
configure_nix_daemon_service: StatefulAction<ConfigureNixDaemonService>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigureNix {
|
impl ConfigureNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
settings: &CommonSettings,
|
settings: &CommonSettings,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let channels: Vec<(String, Url)> = settings
|
let channels: Vec<(String, Url)> = settings
|
||||||
.channels
|
.channels
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -57,8 +61,8 @@ impl ConfigureNix {
|
||||||
setup_default_profile,
|
setup_default_profile,
|
||||||
configure_nix_daemon_service,
|
configure_nix_daemon_service,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,15 +80,14 @@ impl Action for ConfigureNix {
|
||||||
place_nix_configuration,
|
place_nix_configuration,
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut buf = setup_default_profile.execute_description();
|
let mut buf = setup_default_profile.describe_execute();
|
||||||
buf.append(&mut configure_nix_daemon_service.execute_description());
|
buf.append(&mut configure_nix_daemon_service.describe_execute());
|
||||||
buf.append(&mut place_nix_configuration.execute_description());
|
buf.append(&mut place_nix_configuration.describe_execute());
|
||||||
buf.append(&mut place_channel_configuration.execute_description());
|
buf.append(&mut place_channel_configuration.describe_execute());
|
||||||
if let Some(configure_shell_profile) = configure_shell_profile {
|
if let Some(configure_shell_profile) = configure_shell_profile {
|
||||||
buf.append(&mut configure_shell_profile.execute_description());
|
buf.append(&mut configure_shell_profile.describe_execute());
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
@ -97,7 +100,6 @@ impl Action for ConfigureNix {
|
||||||
place_nix_configuration,
|
place_nix_configuration,
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if let Some(configure_shell_profile) = configure_shell_profile {
|
if let Some(configure_shell_profile) = configure_shell_profile {
|
||||||
|
@ -126,17 +128,16 @@ impl Action for ConfigureNix {
|
||||||
place_nix_configuration,
|
place_nix_configuration,
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut buf = Vec::default();
|
let mut buf = Vec::default();
|
||||||
if let Some(configure_shell_profile) = configure_shell_profile {
|
if let Some(configure_shell_profile) = configure_shell_profile {
|
||||||
buf.append(&mut configure_shell_profile.revert_description());
|
buf.append(&mut configure_shell_profile.describe_revert());
|
||||||
}
|
}
|
||||||
buf.append(&mut place_channel_configuration.revert_description());
|
buf.append(&mut place_channel_configuration.describe_revert());
|
||||||
buf.append(&mut place_nix_configuration.revert_description());
|
buf.append(&mut place_nix_configuration.describe_revert());
|
||||||
buf.append(&mut configure_nix_daemon_service.revert_description());
|
buf.append(&mut configure_nix_daemon_service.describe_revert());
|
||||||
buf.append(&mut setup_default_profile.revert_description());
|
buf.append(&mut setup_default_profile.describe_revert());
|
||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
@ -149,7 +150,6 @@ impl Action for ConfigureNix {
|
||||||
place_nix_configuration,
|
place_nix_configuration,
|
||||||
place_channel_configuration,
|
place_channel_configuration,
|
||||||
configure_shell_profile,
|
configure_shell_profile,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
configure_nix_daemon_service.try_revert().await?;
|
configure_nix_daemon_service.try_revert().await?;
|
||||||
|
@ -162,12 +162,4 @@ impl Action for ConfigureNix {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::action::base::{CreateOrAppendFile, CreateOrAppendFileError};
|
use crate::action::base::{CreateOrAppendFile, CreateOrAppendFileError};
|
||||||
use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
|
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||||
use crate::BoxableError;
|
use crate::BoxableError;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -15,15 +15,17 @@ const PROFILE_TARGETS: &[&str] = &[
|
||||||
];
|
];
|
||||||
const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
|
const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Configure any detected shell profiles to include Nix support
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ConfigureShellProfile {
|
pub struct ConfigureShellProfile {
|
||||||
create_or_append_files: Vec<CreateOrAppendFile>,
|
create_or_append_files: Vec<StatefulAction<CreateOrAppendFile>>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConfigureShellProfile {
|
impl ConfigureShellProfile {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let mut create_or_append_files = Vec::default();
|
let mut create_or_append_files = Vec::default();
|
||||||
for profile_target in PROFILE_TARGETS {
|
for profile_target in PROFILE_TARGETS {
|
||||||
let path = Path::new(profile_target);
|
let path = Path::new(profile_target);
|
||||||
|
@ -49,8 +51,8 @@ impl ConfigureShellProfile {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_or_append_files,
|
create_or_append_files,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +74,6 @@ impl Action for ConfigureShellProfile {
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
create_or_append_files,
|
create_or_append_files,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
|
@ -121,7 +122,6 @@ impl Action for ConfigureShellProfile {
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
create_or_append_files,
|
create_or_append_files,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
|
@ -130,7 +130,7 @@ impl Action for ConfigureShellProfile {
|
||||||
for (idx, create_or_append_file) in create_or_append_files.iter().enumerate() {
|
for (idx, create_or_append_file) in create_or_append_files.iter().enumerate() {
|
||||||
let mut create_or_append_file_clone = create_or_append_file.clone();
|
let mut create_or_append_file_clone = create_or_append_file.clone();
|
||||||
let _abort_handle = set.spawn(async move {
|
let _abort_handle = set.spawn(async move {
|
||||||
create_or_append_file_clone.revert().await?;
|
create_or_append_file_clone.try_revert().await?;
|
||||||
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((
|
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((
|
||||||
idx,
|
idx,
|
||||||
create_or_append_file_clone,
|
create_or_append_file_clone,
|
||||||
|
@ -158,14 +158,6 @@ impl Action for ConfigureShellProfile {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::action::base::{CreateDirectory, CreateDirectoryError};
|
use crate::action::base::{CreateDirectory, CreateDirectoryError};
|
||||||
use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
|
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||||
|
|
||||||
const PATHS: &[&str] = &[
|
const PATHS: &[&str] = &[
|
||||||
"/nix/var",
|
"/nix/var",
|
||||||
|
@ -17,25 +17,24 @@ const PATHS: &[&str] = &[
|
||||||
"/nix/var/nix/daemon-socket",
|
"/nix/var/nix/daemon-socket",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create the `/nix` tree
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateNixTree {
|
pub struct CreateNixTree {
|
||||||
create_directories: Vec<CreateDirectory>,
|
create_directories: Vec<StatefulAction<CreateDirectory>>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateNixTree {
|
impl CreateNixTree {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
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?)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self { create_directories }.into())
|
||||||
create_directories,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,10 +66,7 @@ impl Action for CreateNixTree {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { create_directories } = self;
|
||||||
create_directories,
|
|
||||||
action_state: _,
|
|
||||||
} = 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 create_directories {
|
||||||
|
@ -102,26 +98,15 @@ impl Action for CreateNixTree {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { create_directories } = self;
|
||||||
create_directories,
|
|
||||||
action_state: _,
|
|
||||||
} = 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 create_directories.iter_mut().rev() {
|
||||||
create_directory.revert().await?
|
create_directory.try_revert().await?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
use crate::CommonSettings;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{
|
action::{
|
||||||
base::{CreateGroup, CreateGroupError, CreateUser, CreateUserError},
|
base::{CreateGroup, CreateGroupError, CreateUser, CreateUserError},
|
||||||
Action, ActionDescription, ActionImplementation, ActionState,
|
Action, ActionDescription, StatefulAction,
|
||||||
},
|
},
|
||||||
|
settings::CommonSettings,
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
use tokio::task::{JoinError, JoinSet};
|
use tokio::task::{JoinError, JoinSet};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateUsersAndGroup {
|
pub struct CreateUsersAndGroups {
|
||||||
daemon_user_count: usize,
|
daemon_user_count: usize,
|
||||||
nix_build_group_name: String,
|
nix_build_group_name: String,
|
||||||
nix_build_group_id: usize,
|
nix_build_group_id: usize,
|
||||||
nix_build_user_prefix: String,
|
nix_build_user_prefix: String,
|
||||||
nix_build_user_id_base: usize,
|
nix_build_user_id_base: usize,
|
||||||
create_group: CreateGroup,
|
create_group: StatefulAction<CreateGroup>,
|
||||||
create_users: Vec<CreateUser>,
|
create_users: Vec<StatefulAction<CreateUser>>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUsersAndGroup {
|
impl CreateUsersAndGroups {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: CommonSettings) -> Result<Self, CreateUsersAndGroupError> {
|
pub async fn plan(
|
||||||
|
settings: CommonSettings,
|
||||||
|
) -> Result<StatefulAction<Self>, CreateUsersAndGroupsError> {
|
||||||
// TODO(@hoverbear): CHeck if it exist, error if so
|
// TODO(@hoverbear): CHeck if it exist, error if so
|
||||||
let create_group = CreateGroup::plan(
|
let create_group = CreateGroup::plan(
|
||||||
settings.nix_build_group_name.clone(),
|
settings.nix_build_group_name.clone(),
|
||||||
|
@ -47,14 +48,14 @@ impl CreateUsersAndGroup {
|
||||||
nix_build_user_id_base: settings.nix_build_user_id_base,
|
nix_build_user_id_base: settings.nix_build_user_id_base,
|
||||||
create_group,
|
create_group,
|
||||||
create_users,
|
create_users,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 CreateUsersAndGroup {
|
impl Action for CreateUsersAndGroups {
|
||||||
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 {})",
|
||||||
|
@ -73,7 +74,6 @@ impl Action for CreateUsersAndGroup {
|
||||||
nix_build_user_id_base: _,
|
nix_build_user_id_base: _,
|
||||||
create_group,
|
create_group,
|
||||||
create_users,
|
create_users,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut create_users_descriptions = Vec::new();
|
let mut create_users_descriptions = Vec::new();
|
||||||
|
@ -110,7 +110,6 @@ impl Action for CreateUsersAndGroup {
|
||||||
nix_build_group_id: _,
|
nix_build_group_id: _,
|
||||||
nix_build_user_prefix: _,
|
nix_build_user_prefix: _,
|
||||||
nix_build_user_id_base: _,
|
nix_build_user_id_base: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
|
@ -152,7 +151,7 @@ impl Action for CreateUsersAndGroup {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap().into());
|
return Err(errors.into_iter().next().unwrap().into());
|
||||||
} else {
|
} else {
|
||||||
return Err(CreateUsersAndGroupError::CreateUsers(errors).boxed());
|
return Err(CreateUsersAndGroupsError::CreateUsers(errors).boxed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -170,31 +169,26 @@ impl Action for CreateUsersAndGroup {
|
||||||
nix_build_user_id_base: _,
|
nix_build_user_id_base: _,
|
||||||
create_group,
|
create_group,
|
||||||
create_users,
|
create_users,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
if self.action_state == ActionState::Uncompleted {
|
let mut create_users_descriptions = Vec::new();
|
||||||
vec![]
|
for create_user in create_users {
|
||||||
} else {
|
if let Some(val) = create_user.describe_revert().iter().next() {
|
||||||
let mut create_users_descriptions = Vec::new();
|
create_users_descriptions.push(val.description.clone())
|
||||||
for create_user in create_users {
|
|
||||||
if let Some(val) = create_user.describe_revert().iter().next() {
|
|
||||||
create_users_descriptions.push(val.description.clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut explanation = vec![
|
|
||||||
format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"),
|
|
||||||
];
|
|
||||||
if let Some(val) = create_group.describe_revert().iter().next() {
|
|
||||||
explanation.push(val.description.clone())
|
|
||||||
}
|
|
||||||
explanation.append(&mut create_users_descriptions);
|
|
||||||
|
|
||||||
vec![ActionDescription::new(
|
|
||||||
format!("Remove Nix users and group"),
|
|
||||||
explanation,
|
|
||||||
)]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut explanation = vec![
|
||||||
|
format!("The nix daemon requires system users (and a group they share) which it can act as in order to build"),
|
||||||
|
];
|
||||||
|
if let Some(val) = create_group.describe_revert().iter().next() {
|
||||||
|
explanation.push(val.description.clone())
|
||||||
|
}
|
||||||
|
explanation.append(&mut create_users_descriptions);
|
||||||
|
|
||||||
|
vec![ActionDescription::new(
|
||||||
|
format!("Remove Nix users and group"),
|
||||||
|
explanation,
|
||||||
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
@ -213,7 +207,6 @@ impl Action for CreateUsersAndGroup {
|
||||||
nix_build_group_id: _,
|
nix_build_group_id: _,
|
||||||
nix_build_user_prefix: _,
|
nix_build_user_prefix: _,
|
||||||
nix_build_user_id_base: _,
|
nix_build_user_id_base: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
|
|
||||||
|
@ -222,7 +215,7 @@ impl Action for CreateUsersAndGroup {
|
||||||
for (idx, create_user) in create_users.iter().enumerate() {
|
for (idx, create_user) in create_users.iter().enumerate() {
|
||||||
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.revert().await?;
|
create_user_clone.try_revert().await?;
|
||||||
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((idx, create_user_clone))
|
Result::<_, Box<dyn std::error::Error + Send + Sync>>::Ok((idx, create_user_clone))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -239,27 +232,19 @@ impl Action for CreateUsersAndGroup {
|
||||||
if errors.len() == 1 {
|
if errors.len() == 1 {
|
||||||
return Err(errors.into_iter().next().unwrap().into());
|
return Err(errors.into_iter().next().unwrap().into());
|
||||||
} else {
|
} else {
|
||||||
return Err(CreateUsersAndGroupError::CreateUsers(errors).boxed());
|
return Err(CreateUsersAndGroupsError::CreateUsers(errors).boxed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
create_group.revert().await?;
|
create_group.try_revert().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum CreateUsersAndGroupError {
|
pub enum CreateUsersAndGroupsError {
|
||||||
#[error("Creating user")]
|
#[error("Creating user")]
|
||||||
CreateUser(
|
CreateUser(
|
||||||
#[source]
|
#[source]
|
|
@ -1,9 +1,9 @@
|
||||||
/*! Actions which only call other base plugins. */
|
//! [`Action`](crate::action::Action)s which only call other base plugins
|
||||||
|
|
||||||
mod configure_nix;
|
mod configure_nix;
|
||||||
mod configure_shell_profile;
|
mod configure_shell_profile;
|
||||||
mod create_nix_tree;
|
mod create_nix_tree;
|
||||||
mod create_users_and_group;
|
mod create_users_and_groups;
|
||||||
mod place_channel_configuration;
|
mod place_channel_configuration;
|
||||||
mod place_nix_configuration;
|
mod place_nix_configuration;
|
||||||
mod provision_nix;
|
mod provision_nix;
|
||||||
|
@ -11,7 +11,7 @@ mod provision_nix;
|
||||||
pub use configure_nix::ConfigureNix;
|
pub use configure_nix::ConfigureNix;
|
||||||
pub use configure_shell_profile::ConfigureShellProfile;
|
pub use configure_shell_profile::ConfigureShellProfile;
|
||||||
pub use create_nix_tree::{CreateNixTree, CreateNixTreeError};
|
pub use create_nix_tree::{CreateNixTree, CreateNixTreeError};
|
||||||
pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError};
|
pub use create_users_and_groups::{CreateUsersAndGroups, CreateUsersAndGroupsError};
|
||||||
pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError};
|
pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError};
|
||||||
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError};
|
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError};
|
||||||
pub use provision_nix::{ProvisionNix, ProvisionNixError};
|
pub use provision_nix::{ProvisionNix, ProvisionNixError};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
use crate::action::base::{CreateFile, CreateFileError};
|
use crate::action::base::{CreateFile, CreateFileError};
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionImplementation, ActionState},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Place a channel configuration containing `channels` to the `$ROOT_HOME/.nix-channels` file
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct PlaceChannelConfiguration {
|
pub struct PlaceChannelConfiguration {
|
||||||
channels: Vec<(String, Url)>,
|
channels: Vec<(String, Url)>,
|
||||||
create_file: CreateFile,
|
create_file: StatefulAction<CreateFile>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceChannelConfiguration {
|
impl PlaceChannelConfiguration {
|
||||||
|
@ -17,7 +19,7 @@ impl PlaceChannelConfiguration {
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
channels: Vec<(String, Url)>,
|
channels: Vec<(String, Url)>,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let buf = channels
|
let buf = channels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, url)| format!("{} {}", url, name))
|
.map(|(name, url)| format!("{} {}", url, name))
|
||||||
|
@ -37,8 +39,8 @@ impl PlaceChannelConfiguration {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels,
|
channels,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +50,7 @@ impl Action for PlaceChannelConfiguration {
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Place channel configuration at `{}`",
|
"Place channel configuration at `{}`",
|
||||||
self.create_file.path.display()
|
self.create_file.inner().path.display()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +65,6 @@ impl Action for PlaceChannelConfiguration {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels: _,
|
channels: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
create_file.try_execute().await?;
|
create_file.try_execute().await?;
|
||||||
|
@ -75,7 +76,7 @@ impl Action for PlaceChannelConfiguration {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!(
|
format!(
|
||||||
"Remove channel configuration at `{}`",
|
"Remove channel configuration at `{}`",
|
||||||
self.create_file.path.display()
|
self.create_file.inner().path.display()
|
||||||
),
|
),
|
||||||
vec![],
|
vec![],
|
||||||
)]
|
)]
|
||||||
|
@ -88,21 +89,12 @@ impl Action for PlaceChannelConfiguration {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
channels: _,
|
channels: _,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
create_file.revert().await?;
|
create_file.try_revert().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
|
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
|
||||||
use crate::action::{Action, ActionDescription, ActionImplementation, ActionState};
|
use crate::action::{Action, ActionDescription, 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Place the `/etc/nix.conf` file
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct PlaceNixConfiguration {
|
pub struct PlaceNixConfiguration {
|
||||||
create_directory: CreateDirectory,
|
create_directory: StatefulAction<CreateDirectory>,
|
||||||
create_file: CreateFile,
|
create_file: StatefulAction<CreateFile>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlaceNixConfiguration {
|
impl PlaceNixConfiguration {
|
||||||
|
@ -17,7 +19,7 @@ impl PlaceNixConfiguration {
|
||||||
nix_build_group_name: String,
|
nix_build_group_name: String,
|
||||||
extra_conf: Option<String>,
|
extra_conf: Option<String>,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let buf = format!(
|
let buf = format!(
|
||||||
"\
|
"\
|
||||||
{extra_conf}\n\
|
{extra_conf}\n\
|
||||||
|
@ -36,8 +38,8 @@ impl PlaceNixConfiguration {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
create_directory,
|
create_directory,
|
||||||
create_file,
|
create_file,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +65,6 @@ impl Action for PlaceNixConfiguration {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
create_directory,
|
create_directory,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
create_directory.try_execute().await?;
|
create_directory.try_execute().await?;
|
||||||
|
@ -87,22 +88,13 @@ impl Action for PlaceNixConfiguration {
|
||||||
let Self {
|
let Self {
|
||||||
create_file,
|
create_file,
|
||||||
create_directory,
|
create_directory,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
create_file.revert().await?;
|
create_file.try_revert().await?;
|
||||||
create_directory.revert().await?;
|
create_directory.try_revert().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,37 +1,42 @@
|
||||||
use crate::action::base::{
|
|
||||||
CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError,
|
|
||||||
};
|
|
||||||
use crate::CommonSettings;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionImplementation, ActionState},
|
action::{
|
||||||
|
base::{
|
||||||
|
CreateDirectoryError, FetchAndUnpackNix, FetchUrlError, MoveUnpackedNix,
|
||||||
|
MoveUnpackedNixError,
|
||||||
|
},
|
||||||
|
Action, ActionDescription, StatefulAction,
|
||||||
|
},
|
||||||
|
settings::CommonSettings,
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
use super::{CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError};
|
use super::{CreateNixTree, CreateNixTreeError, CreateUsersAndGroups, CreateUsersAndGroupsError};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Place Nix and it's requirements onto the target
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ProvisionNix {
|
pub struct ProvisionNix {
|
||||||
fetch_nix: FetchNix,
|
fetch_nix: StatefulAction<FetchAndUnpackNix>,
|
||||||
create_users_and_group: CreateUsersAndGroup,
|
create_users_and_group: StatefulAction<CreateUsersAndGroups>,
|
||||||
create_nix_tree: CreateNixTree,
|
create_nix_tree: StatefulAction<CreateNixTree>,
|
||||||
move_unpacked_nix: MoveUnpackedNix,
|
move_unpacked_nix: StatefulAction<MoveUnpackedNix>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProvisionNix {
|
impl ProvisionNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
settings: &CommonSettings,
|
settings: &CommonSettings,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let fetch_nix = FetchNix::plan(
|
let fetch_nix = FetchAndUnpackNix::plan(
|
||||||
settings.nix_package_url.clone(),
|
settings.nix_package_url.clone(),
|
||||||
PathBuf::from("/nix/temp-install-dir"),
|
PathBuf::from("/nix/temp-install-dir"),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.boxed())?;
|
.map_err(|e| e.boxed())?;
|
||||||
let create_users_and_group = CreateUsersAndGroup::plan(settings.clone())
|
let create_users_and_group = CreateUsersAndGroups::plan(settings.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.boxed())?;
|
.map_err(|e| e.boxed())?;
|
||||||
let create_nix_tree = CreateNixTree::plan().await?;
|
let create_nix_tree = CreateNixTree::plan().await?;
|
||||||
|
@ -43,8 +48,8 @@ impl ProvisionNix {
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,14 +66,13 @@ impl Action for ProvisionNix {
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut buf = Vec::default();
|
let mut buf = Vec::default();
|
||||||
buf.append(&mut fetch_nix.execute_description());
|
buf.append(&mut fetch_nix.describe_execute());
|
||||||
buf.append(&mut create_users_and_group.execute_description());
|
buf.append(&mut create_users_and_group.describe_execute());
|
||||||
buf.append(&mut create_nix_tree.execute_description());
|
buf.append(&mut create_nix_tree.describe_execute());
|
||||||
buf.append(&mut move_unpacked_nix.execute_description());
|
buf.append(&mut move_unpacked_nix.describe_execute());
|
||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
@ -80,7 +84,6 @@ impl Action for ProvisionNix {
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// We fetch nix while doing the rest, then move it over.
|
// We fetch nix while doing the rest, then move it over.
|
||||||
|
@ -105,14 +108,13 @@ impl Action for ProvisionNix {
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
action_state: _,
|
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
||||||
let mut buf = Vec::default();
|
let mut buf = Vec::default();
|
||||||
buf.append(&mut move_unpacked_nix.revert_description());
|
buf.append(&mut move_unpacked_nix.describe_revert());
|
||||||
buf.append(&mut create_nix_tree.revert_description());
|
buf.append(&mut create_nix_tree.describe_revert());
|
||||||
buf.append(&mut create_users_and_group.revert_description());
|
buf.append(&mut create_users_and_group.describe_revert());
|
||||||
buf.append(&mut fetch_nix.revert_description());
|
buf.append(&mut fetch_nix.describe_revert());
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +125,6 @@ impl Action for ProvisionNix {
|
||||||
create_nix_tree,
|
create_nix_tree,
|
||||||
create_users_and_group,
|
create_users_and_group,
|
||||||
move_unpacked_nix,
|
move_unpacked_nix,
|
||||||
action_state: _,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// We fetch nix while doing the rest, then move it over.
|
// We fetch nix while doing the rest, then move it over.
|
||||||
|
@ -143,18 +144,10 @@ impl Action for ProvisionNix {
|
||||||
}
|
}
|
||||||
|
|
||||||
*fetch_nix = fetch_nix_handle.await.map_err(|e| e.boxed())??;
|
*fetch_nix = fetch_nix_handle.await.map_err(|e| e.boxed())??;
|
||||||
move_unpacked_nix.revert().await?;
|
move_unpacked_nix.try_revert().await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -163,7 +156,7 @@ pub enum ProvisionNixError {
|
||||||
FetchNix(
|
FetchNix(
|
||||||
#[source]
|
#[source]
|
||||||
#[from]
|
#[from]
|
||||||
FetchNixError,
|
FetchUrlError,
|
||||||
),
|
),
|
||||||
#[error("Joining spawned async task")]
|
#[error("Joining spawned async task")]
|
||||||
Join(
|
Join(
|
||||||
|
@ -181,7 +174,7 @@ pub enum ProvisionNixError {
|
||||||
CreateUsersAndGroup(
|
CreateUsersAndGroup(
|
||||||
#[source]
|
#[source]
|
||||||
#[from]
|
#[from]
|
||||||
CreateUsersAndGroupError,
|
CreateUsersAndGroupsError,
|
||||||
),
|
),
|
||||||
#[error("Creating nix tree")]
|
#[error("Creating nix tree")]
|
||||||
CreateNixTree(
|
CreateNixTree(
|
||||||
|
|
|
@ -2,34 +2,37 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Bootstrap and kickstart an APFS volume
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct BootstrapVolume {
|
pub struct BootstrapApfsVolume {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BootstrapVolume {
|
impl BootstrapApfsVolume {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path: path.as_ref().to_path_buf(),
|
path: path.as_ref().to_path_buf(),
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "bootstrap_volume")]
|
#[typetag::serde(name = "bootstrap_volume")]
|
||||||
impl Action for BootstrapVolume {
|
impl Action for BootstrapApfsVolume {
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Bootstrap and kickstart `{}`", self.path.display())
|
format!("Bootstrap and kickstart `{}`", self.path.display())
|
||||||
}
|
}
|
||||||
|
@ -42,10 +45,7 @@ impl Action for BootstrapVolume {
|
||||||
path = %self.path.display(),
|
path = %self.path.display(),
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { path } = self;
|
||||||
path,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
|
@ -79,10 +79,7 @@ impl Action for BootstrapVolume {
|
||||||
path = %self.path.display(),
|
path = %self.path.display(),
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { path } = self;
|
||||||
path,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
|
@ -96,14 +93,6 @@ impl Action for BootstrapVolume {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
|
@ -1,39 +1,20 @@
|
||||||
use crate::{
|
use std::path::{Path, PathBuf};
|
||||||
action::{
|
|
||||||
base::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError},
|
|
||||||
darwin::{
|
|
||||||
BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects,
|
|
||||||
CreateSyntheticObjectsError, CreateVolume, CreateVolumeError, EnableOwnership,
|
|
||||||
EnableOwnershipError, EncryptVolume, EncryptVolumeError, UnmountVolume,
|
|
||||||
UnmountVolumeError,
|
|
||||||
},
|
|
||||||
Action, ActionDescription, ActionImplementation, ActionState,
|
|
||||||
},
|
|
||||||
BoxableError,
|
|
||||||
};
|
|
||||||
use std::{
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist";
|
use crate::action::StatefulAction;
|
||||||
|
use crate::execute_command;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
action::{Action, ActionDescription, ActionState},
|
||||||
|
BoxableError,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateApfsVolume {
|
pub struct CreateApfsVolume {
|
||||||
disk: PathBuf,
|
disk: PathBuf,
|
||||||
name: String,
|
name: String,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
encrypt: bool,
|
|
||||||
create_or_append_synthetic_conf: CreateOrAppendFile,
|
|
||||||
create_synthetic_objects: CreateSyntheticObjects,
|
|
||||||
unmount_volume: UnmountVolume,
|
|
||||||
create_volume: CreateVolume,
|
|
||||||
create_or_append_fstab: CreateOrAppendFile,
|
|
||||||
encrypt_volume: Option<EncryptVolume>,
|
|
||||||
setup_volume_daemon: CreateFile,
|
|
||||||
bootstrap_volume: BootstrapVolume,
|
|
||||||
enable_ownership: EnableOwnership,
|
|
||||||
action_state: ActionState,
|
action_state: ActionState,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,246 +24,107 @@ impl CreateApfsVolume {
|
||||||
disk: impl AsRef<Path>,
|
disk: impl AsRef<Path>,
|
||||||
name: String,
|
name: String,
|
||||||
case_sensitive: bool,
|
case_sensitive: bool,
|
||||||
encrypt: bool,
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let disk = disk.as_ref();
|
|
||||||
let create_or_append_synthetic_conf = CreateOrAppendFile::plan(
|
|
||||||
"/etc/synthetic.conf",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
0o0655,
|
|
||||||
"nix\n".into(), /* The newline is required otherwise it segfaults */
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.boxed())?;
|
|
||||||
|
|
||||||
let create_synthetic_objects = CreateSyntheticObjects::plan().await?;
|
|
||||||
|
|
||||||
let unmount_volume = UnmountVolume::plan(disk, name.clone()).await?;
|
|
||||||
|
|
||||||
let create_volume = CreateVolume::plan(disk, name.clone(), case_sensitive).await?;
|
|
||||||
|
|
||||||
let create_or_append_fstab = CreateOrAppendFile::plan(
|
|
||||||
"/etc/fstab",
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
0o0655,
|
|
||||||
format!("NAME=\"{name}\" /nix apfs rw,noauto,nobrowse,suid,owners"),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.boxed())?;
|
|
||||||
|
|
||||||
let encrypt_volume = if encrypt {
|
|
||||||
Some(EncryptVolume::plan(disk, &name).await?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let name_with_qoutes = format!("\"{name}\"");
|
|
||||||
let encrypted_command;
|
|
||||||
let mount_command = if encrypt {
|
|
||||||
encrypted_command = format!("/usr/bin/security find-generic-password -s {name_with_qoutes} -w | /usr/sbin/diskutil apfs unlockVolume {name_with_qoutes} -mountpoint /nix -stdinpassphrase");
|
|
||||||
vec!["/bin/sh", "-c", encrypted_command.as_str()]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
"/usr/sbin/diskutil",
|
|
||||||
"mount",
|
|
||||||
"-mountPoint",
|
|
||||||
"/nix",
|
|
||||||
name.as_str(),
|
|
||||||
]
|
|
||||||
};
|
|
||||||
// TODO(@hoverbear): Use plist lib we have in tree...
|
|
||||||
let mount_plist = format!(
|
|
||||||
"\
|
|
||||||
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
|
||||||
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
|
||||||
<plist version=\"1.0\">\n\
|
|
||||||
<dict>\n\
|
|
||||||
<key>RunAtLoad</key>\n\
|
|
||||||
<true/>\n\
|
|
||||||
<key>Label</key>\n\
|
|
||||||
<string>org.nixos.darwin-store</string>\n\
|
|
||||||
<key>ProgramArguments</key>\n\
|
|
||||||
<array>\n\
|
|
||||||
{}\
|
|
||||||
</array>\n\
|
|
||||||
</dict>\n\
|
|
||||||
</plist>\n\
|
|
||||||
\
|
|
||||||
", mount_command.iter().map(|v| format!("<string>{v}</string>\n")).collect::<Vec<_>>().join("\n")
|
|
||||||
);
|
|
||||||
let setup_volume_daemon =
|
|
||||||
CreateFile::plan(NIX_VOLUME_MOUNTD_DEST, None, None, None, mount_plist, false).await?;
|
|
||||||
|
|
||||||
let bootstrap_volume = BootstrapVolume::plan(NIX_VOLUME_MOUNTD_DEST).await?;
|
|
||||||
let enable_ownership = EnableOwnership::plan("/nix").await?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
disk: disk.to_path_buf(),
|
disk: disk.as_ref().to_path_buf(),
|
||||||
name,
|
name,
|
||||||
case_sensitive,
|
case_sensitive,
|
||||||
encrypt,
|
|
||||||
create_or_append_synthetic_conf,
|
|
||||||
create_synthetic_objects,
|
|
||||||
unmount_volume,
|
|
||||||
create_volume,
|
|
||||||
create_or_append_fstab,
|
|
||||||
encrypt_volume,
|
|
||||||
setup_volume_daemon,
|
|
||||||
bootstrap_volume,
|
|
||||||
enable_ownership,
|
|
||||||
action_state: ActionState::Uncompleted,
|
action_state: ActionState::Uncompleted,
|
||||||
})
|
}
|
||||||
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "create_apfs_volume")]
|
#[typetag::serde(name = "create_volume")]
|
||||||
impl Action for CreateApfsVolume {
|
impl Action for CreateApfsVolume {
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Create an APFS volume `{}` on `{}`",
|
"Create an APFS volume on `{}` named `{}`",
|
||||||
self.name,
|
self.disk.display(),
|
||||||
self.disk.display()
|
self.name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
|
||||||
disk: _, name: _, ..
|
|
||||||
} = &self;
|
|
||||||
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(destination,))]
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
disk = %self.disk.display(),
|
||||||
|
name = %self.name,
|
||||||
|
case_sensitive = %self.case_sensitive,
|
||||||
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
disk: _,
|
disk,
|
||||||
name: _,
|
name,
|
||||||
case_sensitive: _,
|
case_sensitive,
|
||||||
encrypt: _,
|
|
||||||
create_or_append_synthetic_conf,
|
|
||||||
create_synthetic_objects,
|
|
||||||
unmount_volume,
|
|
||||||
create_volume,
|
|
||||||
create_or_append_fstab,
|
|
||||||
encrypt_volume,
|
|
||||||
setup_volume_daemon,
|
|
||||||
bootstrap_volume,
|
|
||||||
enable_ownership,
|
|
||||||
action_state: _,
|
action_state: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
create_or_append_synthetic_conf.try_execute().await?;
|
execute_command(
|
||||||
create_synthetic_objects.try_execute().await?;
|
Command::new("/usr/sbin/diskutil")
|
||||||
unmount_volume.try_execute().await.ok(); // We actually expect this may fail.
|
.process_group(0)
|
||||||
create_volume.try_execute().await?;
|
.args([
|
||||||
create_or_append_fstab.try_execute().await?;
|
"apfs",
|
||||||
if let Some(encrypt_volume) = encrypt_volume {
|
"addVolume",
|
||||||
encrypt_volume.try_execute().await?;
|
&format!("{}", disk.display()),
|
||||||
}
|
if !*case_sensitive {
|
||||||
setup_volume_daemon.try_execute().await?;
|
"APFS"
|
||||||
|
} else {
|
||||||
bootstrap_volume.try_execute().await?;
|
"Case-sensitive APFS"
|
||||||
|
},
|
||||||
let mut retry_tokens: usize = 50;
|
name,
|
||||||
loop {
|
"-nomount",
|
||||||
tracing::trace!(%retry_tokens, "Checking for Nix Store existence");
|
])
|
||||||
let status = Command::new("/usr/sbin/diskutil")
|
.stdin(std::process::Stdio::null()),
|
||||||
.args(["info", "/nix"])
|
)
|
||||||
.stderr(std::process::Stdio::null())
|
.await
|
||||||
.stdout(std::process::Stdio::null())
|
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
||||||
.status()
|
|
||||||
.await
|
|
||||||
.map_err(|e| CreateApfsVolumeError::Command(e).boxed())?;
|
|
||||||
if status.success() || retry_tokens == 0 {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
retry_tokens = retry_tokens.saturating_sub(1);
|
|
||||||
}
|
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
enable_ownership.try_execute().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||||
let Self { disk, name, .. } = &self;
|
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
format!("Remove the APFS volume `{name}` on `{}`", disk.display()),
|
format!(
|
||||||
vec![format!(
|
"Remove the volume on `{}` named `{}`",
|
||||||
"Create a writable, persistent systemd system extension.",
|
self.disk.display(),
|
||||||
)],
|
self.name
|
||||||
|
),
|
||||||
|
vec![],
|
||||||
)]
|
)]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(disk, name))]
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
disk = %self.disk.display(),
|
||||||
|
name = %self.name,
|
||||||
|
case_sensitive = %self.case_sensitive,
|
||||||
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
disk: _,
|
disk: _,
|
||||||
name: _,
|
name,
|
||||||
case_sensitive: _,
|
case_sensitive: _,
|
||||||
encrypt: _,
|
|
||||||
create_or_append_synthetic_conf,
|
|
||||||
create_synthetic_objects,
|
|
||||||
unmount_volume,
|
|
||||||
create_volume,
|
|
||||||
create_or_append_fstab,
|
|
||||||
encrypt_volume,
|
|
||||||
setup_volume_daemon,
|
|
||||||
bootstrap_volume,
|
|
||||||
enable_ownership,
|
|
||||||
action_state: _,
|
action_state: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
enable_ownership.try_revert().await?;
|
execute_command(
|
||||||
bootstrap_volume.try_revert().await?;
|
Command::new("/usr/sbin/diskutil")
|
||||||
setup_volume_daemon.try_revert().await?;
|
.process_group(0)
|
||||||
if let Some(encrypt_volume) = encrypt_volume {
|
.args(["apfs", "deleteVolume", name])
|
||||||
encrypt_volume.try_revert().await?;
|
.stdin(std::process::Stdio::null()),
|
||||||
}
|
)
|
||||||
create_or_append_fstab.try_revert().await?;
|
.await
|
||||||
|
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
||||||
unmount_volume.try_revert().await?;
|
|
||||||
create_volume.try_revert().await?;
|
|
||||||
|
|
||||||
// Purposefully not reversed
|
|
||||||
create_or_append_synthetic_conf.try_revert().await?;
|
|
||||||
create_synthetic_objects.try_revert().await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum CreateApfsVolumeError {
|
pub enum CreateVolumeError {
|
||||||
#[error(transparent)]
|
|
||||||
CreateFile(#[from] CreateFileError),
|
|
||||||
#[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)]
|
|
||||||
CreateOrAppendFile(#[from] CreateOrAppendFileError),
|
|
||||||
#[error("Failed to execute command")]
|
#[error("Failed to execute command")]
|
||||||
Command(#[source] std::io::Error),
|
Command(#[source] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
277
src/action/darwin/create_nix_volume.rs
Normal file
277
src/action/darwin/create_nix_volume.rs
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
use crate::{
|
||||||
|
action::{
|
||||||
|
base::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError},
|
||||||
|
darwin::{
|
||||||
|
BootstrapApfsVolume, BootstrapVolumeError, CreateApfsVolume, CreateSyntheticObjects,
|
||||||
|
CreateSyntheticObjectsError, CreateVolumeError, EnableOwnership, EnableOwnershipError,
|
||||||
|
EncryptApfsVolume, EncryptVolumeError, UnmountApfsVolume, UnmountVolumeError,
|
||||||
|
},
|
||||||
|
Action, ActionDescription, StatefulAction,
|
||||||
|
},
|
||||||
|
BoxableError,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist";
|
||||||
|
|
||||||
|
/// Create an APFS volume
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
pub struct CreateNixVolume {
|
||||||
|
disk: PathBuf,
|
||||||
|
name: String,
|
||||||
|
case_sensitive: bool,
|
||||||
|
encrypt: bool,
|
||||||
|
create_or_append_synthetic_conf: StatefulAction<CreateOrAppendFile>,
|
||||||
|
create_synthetic_objects: StatefulAction<CreateSyntheticObjects>,
|
||||||
|
unmount_volume: StatefulAction<UnmountApfsVolume>,
|
||||||
|
create_volume: StatefulAction<CreateApfsVolume>,
|
||||||
|
create_or_append_fstab: StatefulAction<CreateOrAppendFile>,
|
||||||
|
encrypt_volume: Option<StatefulAction<EncryptApfsVolume>>,
|
||||||
|
setup_volume_daemon: StatefulAction<CreateFile>,
|
||||||
|
bootstrap_volume: StatefulAction<BootstrapApfsVolume>,
|
||||||
|
enable_ownership: StatefulAction<EnableOwnership>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateNixVolume {
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn plan(
|
||||||
|
disk: impl AsRef<Path>,
|
||||||
|
name: String,
|
||||||
|
case_sensitive: bool,
|
||||||
|
encrypt: bool,
|
||||||
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let disk = disk.as_ref();
|
||||||
|
let create_or_append_synthetic_conf = CreateOrAppendFile::plan(
|
||||||
|
"/etc/synthetic.conf",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
0o0655,
|
||||||
|
"nix\n".into(), /* The newline is required otherwise it segfaults */
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.boxed())?;
|
||||||
|
|
||||||
|
let create_synthetic_objects = CreateSyntheticObjects::plan().await?;
|
||||||
|
|
||||||
|
let unmount_volume = UnmountApfsVolume::plan(disk, name.clone()).await?;
|
||||||
|
|
||||||
|
let create_volume = CreateApfsVolume::plan(disk, name.clone(), case_sensitive).await?;
|
||||||
|
|
||||||
|
let create_or_append_fstab = CreateOrAppendFile::plan(
|
||||||
|
"/etc/fstab",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
0o0655,
|
||||||
|
format!("NAME=\"{name}\" /nix apfs rw,noauto,nobrowse,suid,owners"),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| e.boxed())?;
|
||||||
|
|
||||||
|
let encrypt_volume = if encrypt {
|
||||||
|
Some(EncryptApfsVolume::plan(disk, &name).await?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let name_with_qoutes = format!("\"{name}\"");
|
||||||
|
let encrypted_command;
|
||||||
|
let mount_command = if encrypt {
|
||||||
|
encrypted_command = format!("/usr/bin/security find-generic-password -s {name_with_qoutes} -w | /usr/sbin/diskutil apfs unlockVolume {name_with_qoutes} -mountpoint /nix -stdinpassphrase");
|
||||||
|
vec!["/bin/sh", "-c", encrypted_command.as_str()]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
"/usr/sbin/diskutil",
|
||||||
|
"mount",
|
||||||
|
"-mountPoint",
|
||||||
|
"/nix",
|
||||||
|
name.as_str(),
|
||||||
|
]
|
||||||
|
};
|
||||||
|
// TODO(@hoverbear): Use plist lib we have in tree...
|
||||||
|
let mount_plist = format!(
|
||||||
|
"\
|
||||||
|
<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||||
|
<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
||||||
|
<plist version=\"1.0\">\n\
|
||||||
|
<dict>\n\
|
||||||
|
<key>RunAtLoad</key>\n\
|
||||||
|
<true/>\n\
|
||||||
|
<key>Label</key>\n\
|
||||||
|
<string>org.nixos.darwin-store</string>\n\
|
||||||
|
<key>ProgramArguments</key>\n\
|
||||||
|
<array>\n\
|
||||||
|
{}\
|
||||||
|
</array>\n\
|
||||||
|
</dict>\n\
|
||||||
|
</plist>\n\
|
||||||
|
\
|
||||||
|
", mount_command.iter().map(|v| format!("<string>{v}</string>\n")).collect::<Vec<_>>().join("\n")
|
||||||
|
);
|
||||||
|
let setup_volume_daemon =
|
||||||
|
CreateFile::plan(NIX_VOLUME_MOUNTD_DEST, None, None, None, mount_plist, false).await?;
|
||||||
|
|
||||||
|
let bootstrap_volume = BootstrapApfsVolume::plan(NIX_VOLUME_MOUNTD_DEST).await?;
|
||||||
|
let enable_ownership = EnableOwnership::plan("/nix").await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
disk: disk.to_path_buf(),
|
||||||
|
name,
|
||||||
|
case_sensitive,
|
||||||
|
encrypt,
|
||||||
|
create_or_append_synthetic_conf,
|
||||||
|
create_synthetic_objects,
|
||||||
|
unmount_volume,
|
||||||
|
create_volume,
|
||||||
|
create_or_append_fstab,
|
||||||
|
encrypt_volume,
|
||||||
|
setup_volume_daemon,
|
||||||
|
bootstrap_volume,
|
||||||
|
enable_ownership,
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[typetag::serde(name = "create_apfs_volume")]
|
||||||
|
impl Action for CreateNixVolume {
|
||||||
|
fn tracing_synopsis(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Create an APFS volume `{}` for Nix on `{}`",
|
||||||
|
self.name,
|
||||||
|
self.disk.display()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
|
let Self {
|
||||||
|
disk: _, name: _, ..
|
||||||
|
} = &self;
|
||||||
|
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(destination,))]
|
||||||
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let Self {
|
||||||
|
disk: _,
|
||||||
|
name: _,
|
||||||
|
case_sensitive: _,
|
||||||
|
encrypt: _,
|
||||||
|
create_or_append_synthetic_conf,
|
||||||
|
create_synthetic_objects,
|
||||||
|
unmount_volume,
|
||||||
|
create_volume,
|
||||||
|
create_or_append_fstab,
|
||||||
|
encrypt_volume,
|
||||||
|
setup_volume_daemon,
|
||||||
|
bootstrap_volume,
|
||||||
|
enable_ownership,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
create_or_append_synthetic_conf.try_execute().await?;
|
||||||
|
create_synthetic_objects.try_execute().await?;
|
||||||
|
unmount_volume.try_execute().await.ok(); // We actually expect this may fail.
|
||||||
|
create_volume.try_execute().await?;
|
||||||
|
create_or_append_fstab.try_execute().await?;
|
||||||
|
if let Some(encrypt_volume) = encrypt_volume {
|
||||||
|
encrypt_volume.try_execute().await?;
|
||||||
|
}
|
||||||
|
setup_volume_daemon.try_execute().await?;
|
||||||
|
|
||||||
|
bootstrap_volume.try_execute().await?;
|
||||||
|
|
||||||
|
let mut retry_tokens: usize = 50;
|
||||||
|
loop {
|
||||||
|
tracing::trace!(%retry_tokens, "Checking for Nix Store existence");
|
||||||
|
let status = Command::new("/usr/sbin/diskutil")
|
||||||
|
.args(["info", "/nix"])
|
||||||
|
.stderr(std::process::Stdio::null())
|
||||||
|
.stdout(std::process::Stdio::null())
|
||||||
|
.status()
|
||||||
|
.await
|
||||||
|
.map_err(|e| CreateApfsVolumeError::Command(e).boxed())?;
|
||||||
|
if status.success() || retry_tokens == 0 {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
retry_tokens = retry_tokens.saturating_sub(1);
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_ownership.try_execute().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||||
|
let Self { disk, name, .. } = &self;
|
||||||
|
vec![ActionDescription::new(
|
||||||
|
format!("Remove the APFS volume `{name}` on `{}`", disk.display()),
|
||||||
|
vec![format!(
|
||||||
|
"Create a writable, persistent systemd system extension.",
|
||||||
|
)],
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(disk, name))]
|
||||||
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let Self {
|
||||||
|
disk: _,
|
||||||
|
name: _,
|
||||||
|
case_sensitive: _,
|
||||||
|
encrypt: _,
|
||||||
|
create_or_append_synthetic_conf,
|
||||||
|
create_synthetic_objects,
|
||||||
|
unmount_volume,
|
||||||
|
create_volume,
|
||||||
|
create_or_append_fstab,
|
||||||
|
encrypt_volume,
|
||||||
|
setup_volume_daemon,
|
||||||
|
bootstrap_volume,
|
||||||
|
enable_ownership,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
enable_ownership.try_revert().await?;
|
||||||
|
bootstrap_volume.try_revert().await?;
|
||||||
|
setup_volume_daemon.try_revert().await?;
|
||||||
|
if let Some(encrypt_volume) = encrypt_volume {
|
||||||
|
encrypt_volume.try_revert().await?;
|
||||||
|
}
|
||||||
|
create_or_append_fstab.try_revert().await?;
|
||||||
|
|
||||||
|
unmount_volume.try_revert().await?;
|
||||||
|
create_volume.try_revert().await?;
|
||||||
|
|
||||||
|
// Purposefully not reversed
|
||||||
|
create_or_append_synthetic_conf.try_revert().await?;
|
||||||
|
create_synthetic_objects.try_revert().await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum CreateApfsVolumeError {
|
||||||
|
#[error(transparent)]
|
||||||
|
CreateFile(#[from] CreateFileError),
|
||||||
|
#[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)]
|
||||||
|
CreateOrAppendFile(#[from] CreateOrAppendFileError),
|
||||||
|
#[error("Failed to execute command")]
|
||||||
|
Command(#[source] std::io::Error),
|
||||||
|
}
|
|
@ -2,19 +2,16 @@ use tokio::process::Command;
|
||||||
|
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::action::{Action, ActionDescription, ActionState};
|
use crate::action::{Action, ActionDescription, StatefulAction};
|
||||||
|
|
||||||
|
/// Create the synthetic objects defined in `/etc/syntethic.conf`
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateSyntheticObjects {
|
pub struct CreateSyntheticObjects;
|
||||||
action_state: ActionState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CreateSyntheticObjects {
|
impl CreateSyntheticObjects {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
Ok(Self {
|
Ok(Self.into())
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,14 +81,6 @@ impl Action for CreateSyntheticObjects {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
use crate::execute_command;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
action::{Action, ActionDescription, ActionState},
|
|
||||||
BoxableError,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
|
||||||
pub struct CreateVolume {
|
|
||||||
disk: PathBuf,
|
|
||||||
name: String,
|
|
||||||
case_sensitive: bool,
|
|
||||||
action_state: ActionState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CreateVolume {
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
pub async fn plan(
|
|
||||||
disk: impl AsRef<Path>,
|
|
||||||
name: String,
|
|
||||||
case_sensitive: bool,
|
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
Ok(Self {
|
|
||||||
disk: disk.as_ref().to_path_buf(),
|
|
||||||
name,
|
|
||||||
case_sensitive,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
#[typetag::serde(name = "create_volume")]
|
|
||||||
impl Action for CreateVolume {
|
|
||||||
fn tracing_synopsis(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"Create a volume on `{}` named `{}`",
|
|
||||||
self.disk.display(),
|
|
||||||
self.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(
|
|
||||||
disk = %self.disk.display(),
|
|
||||||
name = %self.name,
|
|
||||||
case_sensitive = %self.case_sensitive,
|
|
||||||
))]
|
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let Self {
|
|
||||||
disk,
|
|
||||||
name,
|
|
||||||
case_sensitive,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
|
||||||
Command::new("/usr/sbin/diskutil")
|
|
||||||
.process_group(0)
|
|
||||||
.args([
|
|
||||||
"apfs",
|
|
||||||
"addVolume",
|
|
||||||
&format!("{}", disk.display()),
|
|
||||||
if !*case_sensitive {
|
|
||||||
"APFS"
|
|
||||||
} else {
|
|
||||||
"Case-sensitive APFS"
|
|
||||||
},
|
|
||||||
name,
|
|
||||||
"-nomount",
|
|
||||||
])
|
|
||||||
.stdin(std::process::Stdio::null()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn revert_description(&self) -> Vec<ActionDescription> {
|
|
||||||
vec![ActionDescription::new(
|
|
||||||
format!(
|
|
||||||
"Remove the volume on `{}` named `{}`",
|
|
||||||
self.disk.display(),
|
|
||||||
self.name
|
|
||||||
),
|
|
||||||
vec![],
|
|
||||||
)]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all, fields(
|
|
||||||
disk = %self.disk.display(),
|
|
||||||
name = %self.name,
|
|
||||||
case_sensitive = %self.case_sensitive,
|
|
||||||
))]
|
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let Self {
|
|
||||||
disk: _,
|
|
||||||
name,
|
|
||||||
case_sensitive: _,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
|
||||||
Command::new("/usr/sbin/diskutil")
|
|
||||||
.process_group(0)
|
|
||||||
.args(["apfs", "deleteVolume", name])
|
|
||||||
.stdin(std::process::Stdio::null()),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| CreateVolumeError::Command(e).boxed())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum CreateVolumeError {
|
|
||||||
#[error("Failed to execute command")]
|
|
||||||
Command(#[source] std::io::Error),
|
|
||||||
}
|
|
|
@ -3,29 +3,32 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::os::darwin::DiskUtilOutput;
|
use crate::os::darwin::DiskUtilOutput;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable ownership on a volume
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct EnableOwnership {
|
pub struct EnableOwnership {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EnableOwnership {
|
impl EnableOwnership {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
path: impl AsRef<Path>,
|
path: impl AsRef<Path>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
path: path.as_ref().to_path_buf(),
|
path: path.as_ref().to_path_buf(),
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +47,7 @@ impl Action for EnableOwnership {
|
||||||
path = %self.path.display(),
|
path = %self.path.display(),
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { path } = self;
|
||||||
path,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let should_enable_ownership = {
|
let should_enable_ownership = {
|
||||||
let buf = execute_command(
|
let buf = execute_command(
|
||||||
|
@ -90,14 +90,6 @@ impl Action for EnableOwnership {
|
||||||
// noop
|
// noop
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,36 +1,38 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, ActionState},
|
action::{darwin::NIX_VOLUME_MOUNTD_DEST, Action, ActionDescription, StatefulAction},
|
||||||
execute_command,
|
execute_command,
|
||||||
};
|
};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Encrypt an APFS volume
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct EncryptVolume {
|
pub struct EncryptApfsVolume {
|
||||||
disk: PathBuf,
|
disk: PathBuf,
|
||||||
name: String,
|
name: String,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EncryptVolume {
|
impl EncryptApfsVolume {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
disk: impl AsRef<Path>,
|
disk: impl AsRef<Path>,
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let name = name.as_ref().to_owned();
|
let name = name.as_ref().to_owned();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
disk: disk.as_ref().to_path_buf(),
|
disk: disk.as_ref().to_path_buf(),
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "encrypt_volume")]
|
#[typetag::serde(name = "encrypt_volume")]
|
||||||
impl Action for EncryptVolume {
|
impl Action for EncryptApfsVolume {
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"Encrypt volume `{}` on disk `{}`",
|
"Encrypt volume `{}` on disk `{}`",
|
||||||
|
@ -47,11 +49,7 @@ impl Action for EncryptVolume {
|
||||||
disk = %self.disk.display(),
|
disk = %self.disk.display(),
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { disk, name } = self;
|
||||||
disk,
|
|
||||||
name,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
// Generate a random password.
|
// Generate a random password.
|
||||||
let password: String = {
|
let password: String = {
|
||||||
|
@ -141,11 +139,7 @@ impl Action for EncryptVolume {
|
||||||
disk = %self.disk.display(),
|
disk = %self.disk.display(),
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { disk, name } = self;
|
||||||
disk,
|
|
||||||
name,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
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 */
|
||||||
|
|
||||||
|
@ -172,14 +166,6 @@ impl Action for EncryptVolume {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
|
@ -1,25 +1,27 @@
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Kickstart a `launchctl` service
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct KickstartLaunchctlService {
|
pub struct KickstartLaunchctlService {
|
||||||
unit: String,
|
unit: String,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KickstartLaunchctlService {
|
impl KickstartLaunchctlService {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(unit: String) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
pub async fn plan(
|
||||||
Ok(Self {
|
unit: String,
|
||||||
unit,
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
action_state: ActionState::Uncompleted,
|
Ok(Self { unit }.into())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,10 +41,7 @@ impl Action for KickstartLaunchctlService {
|
||||||
unit = %self.unit,
|
unit = %self.unit,
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { unit } = self;
|
||||||
unit,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("launchctl")
|
Command::new("launchctl")
|
||||||
|
@ -69,14 +68,6 @@ impl Action for KickstartLaunchctlService {
|
||||||
// noop
|
// noop
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
mod bootstrap_volume;
|
/*! [`Action`](crate::action::Action)s for Darwin based systems
|
||||||
mod create_apfs_volume;
|
*/
|
||||||
mod create_synthetic_objects;
|
|
||||||
mod create_volume;
|
|
||||||
mod enable_ownership;
|
|
||||||
mod encrypt_volume;
|
|
||||||
mod kickstart_launchctl_service;
|
|
||||||
mod unmount_volume;
|
|
||||||
|
|
||||||
pub use bootstrap_volume::{BootstrapVolume, BootstrapVolumeError};
|
mod bootstrap_apfs_volume;
|
||||||
pub use create_apfs_volume::{CreateApfsVolume, CreateApfsVolumeError, NIX_VOLUME_MOUNTD_DEST};
|
mod create_apfs_volume;
|
||||||
|
mod create_nix_volume;
|
||||||
|
mod create_synthetic_objects;
|
||||||
|
mod enable_ownership;
|
||||||
|
mod encrypt_apfs_volume;
|
||||||
|
mod kickstart_launchctl_service;
|
||||||
|
mod unmount_apfs_volume;
|
||||||
|
|
||||||
|
pub use bootstrap_apfs_volume::{BootstrapApfsVolume, BootstrapVolumeError};
|
||||||
|
pub use create_apfs_volume::{CreateApfsVolume, CreateVolumeError};
|
||||||
|
pub use create_nix_volume::{CreateApfsVolumeError, CreateNixVolume, NIX_VOLUME_MOUNTD_DEST};
|
||||||
pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
|
pub use create_synthetic_objects::{CreateSyntheticObjects, CreateSyntheticObjectsError};
|
||||||
pub use create_volume::{CreateVolume, CreateVolumeError};
|
|
||||||
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
|
pub use enable_ownership::{EnableOwnership, EnableOwnershipError};
|
||||||
pub use encrypt_volume::{EncryptVolume, EncryptVolumeError};
|
pub use encrypt_apfs_volume::{EncryptApfsVolume, EncryptVolumeError};
|
||||||
pub use kickstart_launchctl_service::{KickstartLaunchctlService, KickstartLaunchctlServiceError};
|
pub use kickstart_launchctl_service::{KickstartLaunchctlService, KickstartLaunchctlServiceError};
|
||||||
pub use unmount_volume::{UnmountVolume, UnmountVolumeError};
|
pub use unmount_apfs_volume::{UnmountApfsVolume, UnmountVolumeError};
|
||||||
|
|
|
@ -2,40 +2,39 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Unmount an APFS volume
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct UnmountVolume {
|
pub struct UnmountApfsVolume {
|
||||||
disk: PathBuf,
|
disk: PathBuf,
|
||||||
name: String,
|
name: String,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnmountVolume {
|
impl UnmountApfsVolume {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
disk: impl AsRef<Path>,
|
disk: impl AsRef<Path>,
|
||||||
name: String,
|
name: String,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let disk = disk.as_ref().to_owned();
|
let disk = disk.as_ref().to_owned();
|
||||||
Ok(Self {
|
Ok(Self { disk, name }.into())
|
||||||
disk,
|
|
||||||
name,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "unmount_volume")]
|
#[typetag::serde(name = "unmount_volume")]
|
||||||
impl Action for UnmountVolume {
|
impl Action for UnmountApfsVolume {
|
||||||
fn tracing_synopsis(&self) -> String {
|
fn tracing_synopsis(&self) -> String {
|
||||||
format!("Unmount the `{}` volume", self.name)
|
format!("Unmount the `{}` APFS volume", self.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_description(&self) -> Vec<ActionDescription> {
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
|
@ -47,11 +46,7 @@ impl Action for UnmountVolume {
|
||||||
name = %self.name,
|
name = %self.name,
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { disk: _, name } = self;
|
||||||
disk: _,
|
|
||||||
name,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
@ -75,11 +70,7 @@ impl Action for UnmountVolume {
|
||||||
name = %self.name,
|
name = %self.name,
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { disk: _, name } = self;
|
||||||
disk: _,
|
|
||||||
name,
|
|
||||||
action_state: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
execute_command(
|
execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
|
@ -93,14 +84,6 @@ impl Action for UnmountVolume {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
|
@ -4,10 +4,11 @@ use target_lexicon::OperatingSystem;
|
||||||
use tokio::fs::remove_file;
|
use tokio::fs::remove_file;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,14 +18,15 @@ const TMPFILES_SRC: &str = "/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-da
|
||||||
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
|
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
|
||||||
const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
|
const DARWIN_NIX_DAEMON_DEST: &str = "/Library/LaunchDaemons/org.nixos.nix-daemon.plist";
|
||||||
|
|
||||||
|
/**
|
||||||
|
Run systemd utilities to configure the Nix daemon
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct ConfigureNixDaemonService {
|
pub struct ConfigureNixDaemonService {}
|
||||||
action_state: ActionState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigureNixDaemonService {
|
impl ConfigureNixDaemonService {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
match OperatingSystem::host() {
|
match OperatingSystem::host() {
|
||||||
OperatingSystem::MacOSX {
|
OperatingSystem::MacOSX {
|
||||||
major: _,
|
major: _,
|
||||||
|
@ -39,9 +41,7 @@ impl ConfigureNixDaemonService {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {}.into())
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ impl Action for ConfigureNixDaemonService {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self { action_state: _ } = self;
|
let Self {} = self;
|
||||||
|
|
||||||
match OperatingSystem::host() {
|
match OperatingSystem::host() {
|
||||||
OperatingSystem::MacOSX {
|
OperatingSystem::MacOSX {
|
||||||
|
@ -274,14 +274,6 @@ impl Action for ConfigureNixDaemonService {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
|
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionImplementation, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -16,17 +17,16 @@ const PATHS: &[&str] = &[
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateSystemdSysext {
|
pub struct CreateSystemdSysext {
|
||||||
destination: PathBuf,
|
destination: PathBuf,
|
||||||
create_directories: Vec<CreateDirectory>,
|
create_directories: Vec<StatefulAction<CreateDirectory>>,
|
||||||
create_extension_release: CreateFile,
|
create_extension_release: StatefulAction<CreateFile>,
|
||||||
create_bind_mount_unit: CreateFile,
|
create_bind_mount_unit: StatefulAction<CreateFile>,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateSystemdSysext {
|
impl CreateSystemdSysext {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(
|
pub async fn plan(
|
||||||
destination: impl AsRef<Path>,
|
destination: impl AsRef<Path>,
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let destination = destination.as_ref();
|
let destination = destination.as_ref();
|
||||||
|
|
||||||
let mut create_directories =
|
let mut create_directories =
|
||||||
|
@ -85,8 +85,8 @@ impl CreateSystemdSysext {
|
||||||
create_directories,
|
create_directories,
|
||||||
create_extension_release,
|
create_extension_release,
|
||||||
create_bind_mount_unit,
|
create_bind_mount_unit,
|
||||||
action_state: ActionState::Uncompleted,
|
}
|
||||||
})
|
.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +113,6 @@ impl Action for CreateSystemdSysext {
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
destination: _,
|
destination: _,
|
||||||
action_state: _,
|
|
||||||
create_directories,
|
create_directories,
|
||||||
create_extension_release,
|
create_extension_release,
|
||||||
create_bind_mount_unit,
|
create_bind_mount_unit,
|
||||||
|
@ -142,7 +141,6 @@ impl Action for CreateSystemdSysext {
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self {
|
||||||
destination: _,
|
destination: _,
|
||||||
action_state: _,
|
|
||||||
create_directories,
|
create_directories,
|
||||||
create_extension_release,
|
create_extension_release,
|
||||||
create_bind_mount_unit,
|
create_bind_mount_unit,
|
||||||
|
@ -158,14 +156,6 @@ impl Action for CreateSystemdSysext {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
//! [`Action`](crate::action::Action)s for Linux based systems
|
||||||
|
|
||||||
|
mod configure_nix_daemon_service;
|
||||||
mod create_systemd_sysext;
|
mod create_systemd_sysext;
|
||||||
mod start_systemd_unit;
|
mod start_systemd_unit;
|
||||||
|
|
||||||
|
pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError};
|
||||||
pub use create_systemd_sysext::{CreateSystemdSysext, CreateSystemdSysextError};
|
pub use create_systemd_sysext::{CreateSystemdSysext, CreateSystemdSysextError};
|
||||||
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError};
|
pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError};
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::action::StatefulAction;
|
||||||
use crate::execute_command;
|
use crate::execute_command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionState},
|
action::{Action, ActionDescription},
|
||||||
BoxableError,
|
BoxableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Start a given systemd unit
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct StartSystemdUnit {
|
pub struct StartSystemdUnit {
|
||||||
unit: String,
|
unit: String,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StartSystemdUnit {
|
impl StartSystemdUnit {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(unit: String) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
pub async fn plan(
|
||||||
Ok(Self {
|
unit: String,
|
||||||
unit,
|
) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
action_state: ActionState::Uncompleted,
|
Ok(Self { unit }.into())
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,14 +93,6 @@ impl Action for StartSystemdUnit {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -9,16 +9,12 @@ use tokio::process::Command;
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct SystemdSysextMerge {
|
pub struct SystemdSysextMerge {
|
||||||
device: PathBuf,
|
device: PathBuf,
|
||||||
action_state: ActionState,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemdSysextMerge {
|
impl SystemdSysextMerge {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(device: PathBuf) -> Result<Self, SystemdSysextMergeError> {
|
pub async fn plan(device: PathBuf) -> Result<Self, SystemdSysextMergeError> {
|
||||||
Ok(Self {
|
Ok(Self { device })
|
||||||
device,
|
|
||||||
action_state: ActionState::Uncompleted,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,10 +64,7 @@ impl Action for SystemdSysextMerge {
|
||||||
device = %self.device.display(),
|
device = %self.device.display(),
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let Self {
|
let Self { device } = self;
|
||||||
device,
|
|
||||||
action_state,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
// TODO(@Hoverbear): Handle proxy vars
|
// TODO(@Hoverbear): Handle proxy vars
|
||||||
execute_command(
|
execute_command(
|
||||||
|
@ -86,14 +79,6 @@ impl Action for SystemdSysextMerge {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_state(&self) -> ActionState {
|
|
||||||
self.action_state
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_action_state(&mut self, action_state: ActionState) {
|
|
||||||
self.action_state = action_state;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|
|
@ -1,99 +1,224 @@
|
||||||
|
/*! An executable or revertable step, possibly orcestrating sub-[`Action`]s using things like
|
||||||
|
[`JoinSet`](tokio::task::JoinSet)s
|
||||||
|
|
||||||
|
|
||||||
|
[`Action`]s should be considered an 'atom' of change. Typically they are either a 'base' or
|
||||||
|
a 'composite' [`Action`].
|
||||||
|
|
||||||
|
Base actions are things like:
|
||||||
|
|
||||||
|
* [`CreateDirectory`](base::CreateDirectory)
|
||||||
|
* [`CreateFile`](base::CreateFile)
|
||||||
|
* [`CreateUser`](base::CreateUser)
|
||||||
|
|
||||||
|
Composite actions are things like:
|
||||||
|
|
||||||
|
* [`CreateNixTree`](common::CreateNixTree)
|
||||||
|
* [`CreateUsersAndGroups`](common::CreateUsersAndGroups)
|
||||||
|
|
||||||
|
During their `plan` phase, [`Planner`](crate::planner::Planner)s call an [`Action`]s `plan` function, which may accept any
|
||||||
|
arguments. For example, several 'composite' actions accept a [`CommonSettings`](crate::settings::CommonSettings). Later, the
|
||||||
|
[`InstallPlan`](crate::InstallPlan) will call [`try_execute`](StatefulAction::try_execute) on the [`StatefulAction`].
|
||||||
|
|
||||||
|
You can manually plan, execute, then revert an [`Action`] like so:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
# async fn wrapper() -> Result<(), harmonic::HarmonicError> {
|
||||||
|
use harmonic::action::base::CreateDirectory;
|
||||||
|
let mut action = CreateDirectory::plan("/nix", None, None, 0o0755, true).await?;
|
||||||
|
action.try_execute().await?;
|
||||||
|
action.try_revert().await?;
|
||||||
|
# Ok(())
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
A general guidance for what determines how fine-grained an [`Action`] should be is the unit of
|
||||||
|
reversion. The [`ConfigureNixDaemonService`](linux::ConfigureNixDaemonService) action is a good
|
||||||
|
example of this,it takes several steps, such as running `systemd-tmpfiles`, and calling
|
||||||
|
`systemctl link` on some systemd units.
|
||||||
|
|
||||||
|
Where possible, tasks which could break during execution should be broken up, as uninstalling/installing
|
||||||
|
step detection is determined by the wrapping [`StatefulAction`]. If an [`Action`] is a 'composite'
|
||||||
|
its sub-[`Action`]s can be reverted piece-by-piece. So breaking up actions into faillable units is
|
||||||
|
ideal.
|
||||||
|
|
||||||
|
A custom [`Action`] can be created then used in a custom [`Planner`](crate::planner::Planner):
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use std::{error::Error, collections::HashMap};
|
||||||
|
use harmonic::{
|
||||||
|
InstallPlan,
|
||||||
|
settings::{CommonSettings, InstallSettingsError},
|
||||||
|
planner::{Planner, PlannerError, specific::SteamDeck},
|
||||||
|
action::{Action, StatefulAction, ActionDescription},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
pub struct MyAction {}
|
||||||
|
|
||||||
|
|
||||||
|
impl MyAction {
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn plan() -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
Ok(Self {}.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[typetag::serde(name = "my_action")]
|
||||||
|
impl Action for MyAction {
|
||||||
|
fn tracing_synopsis(&self) -> String {
|
||||||
|
"My action".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_description(&self) -> Vec<ActionDescription> {
|
||||||
|
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
// Tracing fields...
|
||||||
|
))]
|
||||||
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// Execute steps ...
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revert_description(&self) -> Vec<ActionDescription> {
|
||||||
|
vec![ActionDescription::new(self.tracing_synopsis(), vec![])]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
// Tracing fields...
|
||||||
|
))]
|
||||||
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
// Revert steps...
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct MyPlanner {
|
||||||
|
pub common: CommonSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[typetag::serde(name = "my-planner")]
|
||||||
|
impl Planner for MyPlanner {
|
||||||
|
async fn default() -> Result<Self, PlannerError> {
|
||||||
|
Ok(Self {
|
||||||
|
common: CommonSettings::default()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
|
||||||
|
Ok(vec![
|
||||||
|
// ...
|
||||||
|
MyAction::plan()
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?.boxed(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||||
|
let Self { common } = self;
|
||||||
|
let mut map = std::collections::HashMap::default();
|
||||||
|
|
||||||
|
map.extend(common.settings()?.into_iter());
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# async fn custom_planner_install() -> color_eyre::Result<()> {
|
||||||
|
let planner = MyPlanner::default().await?;
|
||||||
|
let mut plan = InstallPlan::plan(planner).await?;
|
||||||
|
match plan.install(None).await {
|
||||||
|
Ok(()) => tracing::info!("Done"),
|
||||||
|
Err(e) => {
|
||||||
|
match e.source() {
|
||||||
|
Some(source) => tracing::error!("{e}: {}", source),
|
||||||
|
None => tracing::error!("{e}"),
|
||||||
|
};
|
||||||
|
plan.uninstall(None).await?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ok(())
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
pub mod base;
|
pub mod base;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod darwin;
|
pub mod darwin;
|
||||||
pub mod linux;
|
pub mod linux;
|
||||||
|
mod stateful;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
pub use stateful::{ActionState, StatefulAction};
|
||||||
|
|
||||||
/// An action which can be reverted or completed, with an action state
|
/// An action which can be reverted or completed, with an action state
|
||||||
///
|
///
|
||||||
/// This trait interacts with [`ActionImplementation`] which does the [`ActionState`] manipulation and provides some tracing facilities.
|
/// This trait interacts with [`StatefulAction`] which does the [`ActionState`] manipulation and provides some tracing facilities.
|
||||||
///
|
///
|
||||||
/// Instead of calling [`execute`][Action::execute] or [`revert`][Action::revert], you should prefer [`try_execute`][ActionImplementation::try_execute] and [`try_revert`][ActionImplementation::try_revert]
|
/// Instead of calling [`execute`][Action::execute] or [`revert`][Action::revert], you should prefer [`try_execute`][StatefulAction::try_execute] and [`try_revert`][StatefulAction::try_revert]
|
||||||
#[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 {
|
||||||
|
/// A synopsis of the action for tracing purposes
|
||||||
fn tracing_synopsis(&self) -> String;
|
fn tracing_synopsis(&self) -> String;
|
||||||
|
/// A description of what this action would do during execution
|
||||||
|
///
|
||||||
|
/// If this action calls sub-[`Action`]s, care should be taken to use [`StatefulAction::describe_execute`] on those actions, not [`execute_description`][Action::execute_description].
|
||||||
|
///
|
||||||
|
/// This is called by [`InstallPlan::describe_install`](crate::InstallPlan::describe_install) through [`StatefulAction::describe_execute`] which will skip output if the action is completed.
|
||||||
fn execute_description(&self) -> Vec<ActionDescription>;
|
fn execute_description(&self) -> Vec<ActionDescription>;
|
||||||
|
/// A description of what this action would do during revert
|
||||||
|
///
|
||||||
|
/// If this action calls sub-[`Action`]s, care should be taken to use [`StatefulAction::describe_revert`] on those actions, not [`revert_description`][Action::revert_description].
|
||||||
|
///
|
||||||
|
/// This is called by [`InstallPlan::describe_uninstall`](crate::InstallPlan::describe_uninstall) through [`StatefulAction::describe_revert`] which will skip output if the action is completed.
|
||||||
fn revert_description(&self) -> Vec<ActionDescription>;
|
fn revert_description(&self) -> Vec<ActionDescription>;
|
||||||
/// Instead of calling [`execute`][Action::execute], you should prefer [`try_execute`][ActionImplementation::try_execute], so [`ActionState`] is handled correctly and tracing is done.
|
/// Perform any execution steps
|
||||||
|
///
|
||||||
|
/// If this action calls sub-[`Action`]s, care should be taken to call [`try_execute`][StatefulAction::try_execute], not [`execute`][Action::execute], so that [`ActionState`] is handled correctly and tracing is done.
|
||||||
|
///
|
||||||
|
/// This is called by [`InstallPlan::install`](crate::InstallPlan::install) through [`StatefulAction::try_execute`] which handles tracing as well as if the action needs to execute based on its `action_state`.
|
||||||
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||||
/// Instead of calling [`revert`][Action::revert], you should prefer [`try_revert`][ActionImplementation::try_revert], so [`ActionState`] is handled correctly and tracing is done.
|
/// Perform any revert steps
|
||||||
|
///
|
||||||
|
/// If this action calls sub-[`Action`]s, care should be taken to call [`try_revert`][StatefulAction::try_revert], not [`revert`][Action::revert], so that [`ActionState`] is handled correctly and tracing is done.
|
||||||
|
///
|
||||||
|
/// /// This is called by [`InstallPlan::uninstall`](crate::InstallPlan::uninstall) through [`StatefulAction::try_revert`] which handles tracing as well as if the action needs to revert based on its `action_state`.
|
||||||
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
|
||||||
fn action_state(&self) -> ActionState;
|
|
||||||
fn set_action_state(&mut self, new_state: ActionState);
|
|
||||||
|
|
||||||
// They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Box<dyn std::error::Error + Send + Sync>>;`
|
fn stateful(self) -> StatefulAction<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
StatefulAction {
|
||||||
|
action: self,
|
||||||
|
state: ActionState::Uncompleted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// They should also have an `async fn plan(args...) -> Result<StatefulAction<Self>, Box<dyn std::error::Error + Send + Sync>>;`
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main wrapper around [`Action`], handling [`ActionState`] and tracing.
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
pub trait ActionImplementation: Action {
|
|
||||||
fn describe_execute(&self) -> Vec<ActionDescription> {
|
|
||||||
if self.action_state() == ActionState::Completed {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
return self.execute_description();
|
|
||||||
}
|
|
||||||
fn describe_revert(&self) -> Vec<ActionDescription> {
|
|
||||||
if self.action_state() == ActionState::Uncompleted {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
return self.revert_description();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// You should prefer this ([`try_execute`][ActionImplementation::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing.
|
|
||||||
async fn try_execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
if self.action_state() == ActionState::Completed {
|
|
||||||
tracing::trace!("Completed: (Already done) {}", self.tracing_synopsis());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.set_action_state(ActionState::Progress);
|
|
||||||
tracing::debug!("Executing: {}", self.tracing_synopsis());
|
|
||||||
self.execute().await?;
|
|
||||||
self.set_action_state(ActionState::Completed);
|
|
||||||
tracing::debug!("Completed: {}", self.tracing_synopsis());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// You should prefer this ([`try_revert`][ActionImplementation::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing.
|
|
||||||
async fn try_revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
if self.action_state() == ActionState::Uncompleted {
|
|
||||||
tracing::trace!("Reverted: (Already done) {}", self.tracing_synopsis());
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
self.set_action_state(ActionState::Progress);
|
|
||||||
tracing::debug!("Reverting: {}", self.tracing_synopsis());
|
|
||||||
self.revert().await?;
|
|
||||||
tracing::debug!("Reverted: {}", self.tracing_synopsis());
|
|
||||||
self.set_action_state(ActionState::Uncompleted);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActionImplementation for dyn Action {}
|
|
||||||
|
|
||||||
impl<A> ActionImplementation for A where A: Action {}
|
|
||||||
|
|
||||||
dyn_clone::clone_trait_object!(Action);
|
dyn_clone::clone_trait_object!(Action);
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy)]
|
/**
|
||||||
pub enum ActionState {
|
A description of an [`Action`](crate::action::Action), intended for humans to review
|
||||||
Completed,
|
*/
|
||||||
// Only applicable to meta-actions that start multiple sub-actions.
|
|
||||||
Progress,
|
|
||||||
Uncompleted,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
|
||||||
pub struct ActionDescription {
|
pub struct ActionDescription {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub explanation: Vec<String>,
|
pub explanation: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionDescription {
|
impl ActionDescription {
|
||||||
fn new(description: String, explanation: Vec<String>) -> Self {
|
pub fn new(description: String, explanation: Vec<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
description,
|
description,
|
||||||
explanation,
|
explanation,
|
||||||
|
|
168
src/action/stateful.rs
Normal file
168
src/action/stateful.rs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{Action, ActionDescription};
|
||||||
|
|
||||||
|
/// A wrapper around an [`Action`](crate::action::Action) which tracks the [`ActionState`] and
|
||||||
|
/// handles some tracing output
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
pub struct StatefulAction<A> {
|
||||||
|
pub(crate) action: A,
|
||||||
|
pub(crate) state: ActionState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> From<A> for StatefulAction<A>
|
||||||
|
where
|
||||||
|
A: Action,
|
||||||
|
{
|
||||||
|
fn from(action: A) -> Self {
|
||||||
|
Self {
|
||||||
|
action,
|
||||||
|
state: ActionState::Uncompleted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StatefulAction<Box<dyn Action>> {
|
||||||
|
/// A description of what this action would do during execution
|
||||||
|
pub fn describe_execute(&self) -> Vec<ActionDescription> {
|
||||||
|
if self.state == ActionState::Completed {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
return self.action.execute_description();
|
||||||
|
}
|
||||||
|
/// A description of what this action would do during revert
|
||||||
|
pub fn describe_revert(&self) -> Vec<ActionDescription> {
|
||||||
|
if self.state == ActionState::Uncompleted {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
return self.action.revert_description();
|
||||||
|
}
|
||||||
|
/// Perform any execution steps
|
||||||
|
///
|
||||||
|
/// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing
|
||||||
|
pub async fn try_execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
if self.state == ActionState::Completed {
|
||||||
|
tracing::trace!(
|
||||||
|
"Completed: (Already done) {}",
|
||||||
|
self.action.tracing_synopsis()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.state = ActionState::Progress;
|
||||||
|
tracing::debug!("Executing: {}", self.action.tracing_synopsis());
|
||||||
|
self.action.execute().await?;
|
||||||
|
self.state = ActionState::Completed;
|
||||||
|
tracing::debug!("Completed: {}", self.action.tracing_synopsis());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Perform any revert steps
|
||||||
|
///
|
||||||
|
/// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing
|
||||||
|
pub async fn try_revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
if self.state == ActionState::Uncompleted {
|
||||||
|
tracing::trace!(
|
||||||
|
"Reverted: (Already done) {}",
|
||||||
|
self.action.tracing_synopsis()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.state = ActionState::Progress;
|
||||||
|
tracing::debug!("Reverting: {}", self.action.tracing_synopsis());
|
||||||
|
self.action.revert().await?;
|
||||||
|
tracing::debug!("Reverted: {}", self.action.tracing_synopsis());
|
||||||
|
self.state = ActionState::Uncompleted;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> StatefulAction<A>
|
||||||
|
where
|
||||||
|
A: Action,
|
||||||
|
{
|
||||||
|
pub fn inner(&self) -> &A {
|
||||||
|
&self.action
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn boxed(self) -> StatefulAction<Box<dyn Action>>
|
||||||
|
where
|
||||||
|
Self: 'static,
|
||||||
|
{
|
||||||
|
StatefulAction {
|
||||||
|
action: Box::new(self.action),
|
||||||
|
state: self.state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A description of what this action would do during execution
|
||||||
|
pub fn describe_execute(&self) -> Vec<ActionDescription> {
|
||||||
|
if self.state == ActionState::Completed {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
return self.action.execute_description();
|
||||||
|
}
|
||||||
|
/// A description of what this action would do during revert
|
||||||
|
pub fn describe_revert(&self) -> Vec<ActionDescription> {
|
||||||
|
if self.state == ActionState::Uncompleted {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
return self.action.revert_description();
|
||||||
|
}
|
||||||
|
/// Perform any execution steps
|
||||||
|
///
|
||||||
|
/// You should prefer this ([`try_execute`][StatefulAction::try_execute]) over [`execute`][Action::execute] as it handles [`ActionState`] and does tracing
|
||||||
|
pub async fn try_execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
if self.state == ActionState::Completed {
|
||||||
|
tracing::trace!(
|
||||||
|
"Completed: (Already done) {}",
|
||||||
|
self.action.tracing_synopsis()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.state = ActionState::Progress;
|
||||||
|
tracing::debug!("Executing: {}", self.action.tracing_synopsis());
|
||||||
|
self.action.execute().await?;
|
||||||
|
self.state = ActionState::Completed;
|
||||||
|
tracing::debug!("Completed: {}", self.action.tracing_synopsis());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Perform any revert steps
|
||||||
|
///
|
||||||
|
/// You should prefer this ([`try_revert`][StatefulAction::try_revert]) over [`revert`][Action::revert] as it handles [`ActionState`] and does tracing
|
||||||
|
pub async fn try_revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
if self.state == ActionState::Uncompleted {
|
||||||
|
tracing::trace!(
|
||||||
|
"Reverted: (Already done) {}",
|
||||||
|
self.action.tracing_synopsis()
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.state = ActionState::Progress;
|
||||||
|
tracing::debug!("Reverting: {}", self.action.tracing_synopsis());
|
||||||
|
self.action.revert().await?;
|
||||||
|
tracing::debug!("Reverted: {}", self.action.tracing_synopsis());
|
||||||
|
self.state = ActionState::Uncompleted;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The state of an [`Action`](crate::action::Action)
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy)]
|
||||||
|
pub enum ActionState {
|
||||||
|
/**
|
||||||
|
If [`Completed`](ActionState::Completed) an [`Action`](crate::action::Action) will be skipped
|
||||||
|
on [`InstallPlan::install`](crate::InstallPlan::install), and reverted on [`InstallPlan::uninstall`](crate::InstallPlan::uninstall)
|
||||||
|
*/
|
||||||
|
Completed,
|
||||||
|
/**
|
||||||
|
If [`Progress`](ActionState::Progress) an [`Action`](crate::action::Action) will be run on
|
||||||
|
[`InstallPlan::install`](crate::InstallPlan::install) and [`InstallPlan::uninstall`](crate::InstallPlan::uninstall)
|
||||||
|
|
||||||
|
Only applicable to meta-actions that contain other multiple sub-actions.
|
||||||
|
*/
|
||||||
|
Progress,
|
||||||
|
/**
|
||||||
|
If [`Completed`](ActionState::Completed) an [`Action`](crate::action::Action) will be skipped
|
||||||
|
on [`InstallPlan::uninstall`](crate::InstallPlan::uninstall) and executed on [`InstallPlan::install`](crate::InstallPlan::install)
|
||||||
|
*/
|
||||||
|
Uncompleted,
|
||||||
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/**
|
||||||
|
A pair of [`String`] and [`Url`] destined for the list of subscribed channels for [`nix-channel`](https://nixos.org/manual/nix/stable/command-ref/nix-channel.html)
|
||||||
|
*/
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ChannelValue(pub String, pub Url);
|
pub struct ChannelValue(pub String, pub Url);
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
impl clap::builder::ValueParserFactory for ChannelValue {
|
impl clap::builder::ValueParserFactory for ChannelValue {
|
||||||
type Parser = ChannelValueParser;
|
type Parser = ChannelValueParser;
|
||||||
fn value_parser() -> Self::Parser {
|
fn value_parser() -> Self::Parser {
|
||||||
|
@ -19,6 +23,8 @@ impl From<(String, Url)> for ChannelValue {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ChannelValueParser;
|
pub struct ChannelValueParser;
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
impl clap::builder::TypedValueParser for ChannelValueParser {
|
impl clap::builder::TypedValueParser for ChannelValueParser {
|
||||||
type Value = ChannelValue;
|
type Value = ChannelValue;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
|
/*! CLI argument structures and utilities
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
pub(crate) mod arg;
|
pub(crate) mod arg;
|
||||||
|
mod interaction;
|
||||||
pub(crate) mod subcommand;
|
pub(crate) mod subcommand;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
|
@ -4,8 +4,11 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::ActionState, cli::is_root, cli::signal_channel, cli::CommandExecute, interaction,
|
action::ActionState,
|
||||||
plan::RECEIPT_LOCATION, BuiltinPlanner, InstallPlan, Planner,
|
cli::{interaction, is_root, signal_channel, CommandExecute},
|
||||||
|
plan::RECEIPT_LOCATION,
|
||||||
|
planner::Planner,
|
||||||
|
BuiltinPlanner, InstallPlan,
|
||||||
};
|
};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::{eyre, WrapErr};
|
use eyre::{eyre, WrapErr};
|
||||||
|
@ -77,13 +80,13 @@ impl CommandExecute for Install {
|
||||||
if existing_receipt.planner.settings().map_err(|e| eyre!(e))? != chosen_planner.settings().map_err(|e| eyre!(e))? {
|
if existing_receipt.planner.settings().map_err(|e| eyre!(e))? != chosen_planner.settings().map_err(|e| eyre!(e))? {
|
||||||
return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install"))
|
return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install"))
|
||||||
}
|
}
|
||||||
if existing_receipt.actions.iter().all(|v| v.action_state() == ActionState::Completed) {
|
if existing_receipt.actions.iter().all(|v| v.state == ActionState::Completed) {
|
||||||
return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed, try uninstalling and reinstalling if Nix isn't working"))
|
return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed, try uninstalling and reinstalling if Nix isn't working"))
|
||||||
}
|
}
|
||||||
existing_receipt
|
existing_receipt
|
||||||
} ,
|
} ,
|
||||||
None => {
|
None => {
|
||||||
InstallPlan::plan(planner.boxed()).await.map_err(|e| eyre!(e))?
|
planner.plan().await.map_err(|e| eyre!(e))?
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -97,7 +100,7 @@ impl CommandExecute for Install {
|
||||||
let builtin_planner = BuiltinPlanner::default()
|
let builtin_planner = BuiltinPlanner::default()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| eyre::eyre!(e))?;
|
.map_err(|e| eyre::eyre!(e))?;
|
||||||
InstallPlan::plan(builtin_planner.boxed()).await.map_err(|e| eyre!(e))?
|
builtin_planner.plan().await.map_err(|e| eyre!(e))?
|
||||||
},
|
},
|
||||||
(Some(_), Some(_)) => return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense")),
|
(Some(_), Some(_)) => return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense")),
|
||||||
};
|
};
|
||||||
|
@ -105,7 +108,7 @@ impl CommandExecute for Install {
|
||||||
if !no_confirm {
|
if !no_confirm {
|
||||||
if !interaction::confirm(
|
if !interaction::confirm(
|
||||||
install_plan
|
install_plan
|
||||||
.describe_execute(explain)
|
.describe_install(explain)
|
||||||
.map_err(|e| eyre!(e))?,
|
.map_err(|e| eyre!(e))?,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -122,7 +125,7 @@ impl CommandExecute for Install {
|
||||||
tracing::error!("{:?}", error);
|
tracing::error!("{:?}", error);
|
||||||
if !interaction::confirm(
|
if !interaction::confirm(
|
||||||
install_plan
|
install_plan
|
||||||
.describe_revert(explain)
|
.describe_uninstall(explain)
|
||||||
.map_err(|e| eyre!(e))?,
|
.map_err(|e| eyre!(e))?,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -130,7 +133,7 @@ impl CommandExecute for Install {
|
||||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
||||||
}
|
}
|
||||||
let rx2 = tx.subscribe();
|
let rx2 = tx.subscribe();
|
||||||
install_plan.revert(rx2).await?
|
install_plan.uninstall(rx2).await?
|
||||||
} else {
|
} else {
|
||||||
return Err(error);
|
return Err(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::{eyre, WrapErr};
|
use eyre::{eyre, WrapErr};
|
||||||
|
|
||||||
use crate::{cli::CommandExecute, interaction};
|
use crate::cli::{interaction, CommandExecute};
|
||||||
|
|
||||||
/// Uninstall a previously installed Nix (only Harmonic done installs supported)
|
/// Uninstall a previously installed Nix (only Harmonic done installs supported)
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
|
@ -53,14 +53,16 @@ impl CommandExecute for Uninstall {
|
||||||
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
|
let mut plan: InstallPlan = serde_json::from_str(&install_receipt_string)?;
|
||||||
|
|
||||||
if !no_confirm {
|
if !no_confirm {
|
||||||
if !interaction::confirm(plan.describe_revert(explain).map_err(|e| eyre!(e))?).await? {
|
if !interaction::confirm(plan.describe_uninstall(explain).map_err(|e| eyre!(e))?)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (_tx, rx) = signal_channel().await?;
|
let (_tx, rx) = signal_channel().await?;
|
||||||
|
|
||||||
plan.revert(rx).await?;
|
plan.uninstall(rx).await?;
|
||||||
// TODO(@hoverbear): It would be so nice to catch errors and offer the user a way to keep going...
|
// TODO(@hoverbear): It would be so nice to catch errors and offer the user a way to keep going...
|
||||||
// However that will require being able to link error -> step and manually setting that step as `Uncompleted`.
|
// However that will require being able to link error -> step and manually setting that step as `Uncompleted`.
|
||||||
|
|
||||||
|
|
36
src/error.rs
36
src/error.rs
|
@ -1,17 +1,49 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::{planner::PlannerError, settings::InstallSettingsError};
|
||||||
|
|
||||||
|
/// An error occurring during a call defined in this crate
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum HarmonicError {
|
pub enum HarmonicError {
|
||||||
|
/// An error originating from an [`Action`](crate::action::Action)
|
||||||
#[error("Error executing action")]
|
#[error("Error executing action")]
|
||||||
Action(
|
Action(
|
||||||
#[source]
|
#[source]
|
||||||
#[from]
|
#[from]
|
||||||
Box<dyn std::error::Error + Send + Sync>,
|
Box<dyn std::error::Error + Send + Sync>,
|
||||||
),
|
),
|
||||||
|
/// 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),
|
||||||
#[error(transparent)]
|
/// An error while serializing the [`InstallPlan`](crate::InstallPlan)
|
||||||
SerializingReceipt(serde_json::Error),
|
#[error("Serializing receipt")]
|
||||||
|
SerializingReceipt(
|
||||||
|
#[from]
|
||||||
|
#[source]
|
||||||
|
serde_json::Error,
|
||||||
|
),
|
||||||
|
/// An error ocurring when a signal is issued along [`InstallPlan::install`](crate::InstallPlan::install)'s `cancel_channel` argument
|
||||||
#[error("Cancelled by user")]
|
#[error("Cancelled by user")]
|
||||||
Cancelled,
|
Cancelled,
|
||||||
|
/// Semver error
|
||||||
|
#[error("Semantic Versioning error")]
|
||||||
|
SemVer(
|
||||||
|
#[from]
|
||||||
|
#[source]
|
||||||
|
semver::Error,
|
||||||
|
),
|
||||||
|
/// Planner error
|
||||||
|
#[error("Planner error")]
|
||||||
|
Planner(
|
||||||
|
#[from]
|
||||||
|
#[source]
|
||||||
|
PlannerError,
|
||||||
|
),
|
||||||
|
/// Install setting error
|
||||||
|
#[error("Install setting error")]
|
||||||
|
InstallSettings(
|
||||||
|
#[from]
|
||||||
|
#[source]
|
||||||
|
InstallSettingsError,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
80
src/lib.rs
80
src/lib.rs
|
@ -1,24 +1,90 @@
|
||||||
|
/*! A [Nix](https://github.com/NixOS/nix) installer and uninstaller.
|
||||||
|
|
||||||
|
Harmonic breaks down into three main concepts:
|
||||||
|
|
||||||
|
* [`Action`]: An executable or revertable step, possibly orcestrating sub-[`Action`]s using things
|
||||||
|
like [`JoinSet`](tokio::task::JoinSet)s.
|
||||||
|
* [`InstallPlan`]: A set of [`Action`]s, along with some metadata, which can be carried out to
|
||||||
|
drive an install or revert.
|
||||||
|
* [`Planner`](planner::Planner): Something which can be used to plan out an [`InstallPlan`].
|
||||||
|
|
||||||
|
It is possible to create custom [`Action`]s and [`Planner`](planner::Planner)s to suit the needs of your project, team, or organization.
|
||||||
|
|
||||||
|
In the simplest case, Harmonic can be asked to determine a default plan for the platform and install
|
||||||
|
it, uninstalling if anything goes wrong:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use std::error::Error;
|
||||||
|
use harmonic::InstallPlan;
|
||||||
|
|
||||||
|
# async fn default_install() -> color_eyre::Result<()> {
|
||||||
|
let mut plan = InstallPlan::default().await?;
|
||||||
|
match plan.install(None).await {
|
||||||
|
Ok(()) => tracing::info!("Done"),
|
||||||
|
Err(e) => {
|
||||||
|
match e.source() {
|
||||||
|
Some(source) => tracing::error!("{e}: {}", source),
|
||||||
|
None => tracing::error!("{e}"),
|
||||||
|
};
|
||||||
|
plan.uninstall(None).await?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
#
|
||||||
|
# Ok(())
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
Sometimes choosing a specific plan is desired:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use std::error::Error;
|
||||||
|
use harmonic::{InstallPlan, planner::{Planner, specific::SteamDeck}};
|
||||||
|
|
||||||
|
# async fn chosen_planner_install() -> color_eyre::Result<()> {
|
||||||
|
let planner = SteamDeck::default().await?;
|
||||||
|
|
||||||
|
// Or call `crate::planner::BuiltinPlanner::default()`
|
||||||
|
// Match on the result to customize.
|
||||||
|
|
||||||
|
// Customize any settings...
|
||||||
|
|
||||||
|
let mut plan = InstallPlan::plan(planner).await?;
|
||||||
|
match plan.install(None).await {
|
||||||
|
Ok(()) => tracing::info!("Done"),
|
||||||
|
Err(e) => {
|
||||||
|
match e.source() {
|
||||||
|
Some(source) => tracing::error!("{e}: {}", source),
|
||||||
|
None => tracing::error!("{e}"),
|
||||||
|
};
|
||||||
|
plan.uninstall(None).await?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
#
|
||||||
|
# Ok(())
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod channel_value;
|
mod channel_value;
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
mod error;
|
mod error;
|
||||||
mod interaction;
|
|
||||||
mod os;
|
mod os;
|
||||||
mod plan;
|
mod plan;
|
||||||
pub mod planner;
|
pub mod planner;
|
||||||
mod settings;
|
pub mod settings;
|
||||||
|
|
||||||
use std::{ffi::OsStr, process::Output};
|
use std::{ffi::OsStr, process::Output};
|
||||||
|
|
||||||
pub use action::Action;
|
use action::Action;
|
||||||
pub use planner::Planner;
|
|
||||||
|
|
||||||
|
pub use channel_value::ChannelValue;
|
||||||
pub use error::HarmonicError;
|
pub use error::HarmonicError;
|
||||||
pub use plan::InstallPlan;
|
pub use plan::InstallPlan;
|
||||||
use planner::BuiltinPlanner;
|
use planner::BuiltinPlanner;
|
||||||
|
|
||||||
pub use settings::CommonSettings;
|
|
||||||
|
|
||||||
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())))]
|
||||||
|
|
56
src/plan.rs
56
src/plan.rs
|
@ -1,43 +1,56 @@
|
||||||
use std::{path::PathBuf, str::FromStr};
|
use std::{path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{Action, ActionDescription, ActionImplementation},
|
action::{Action, ActionDescription, StatefulAction},
|
||||||
planner::Planner,
|
planner::{BuiltinPlanner, Planner},
|
||||||
HarmonicError,
|
HarmonicError,
|
||||||
};
|
};
|
||||||
use crossterm::style::Stylize;
|
use owo_colors::OwoColorize;
|
||||||
use semver::{Version, VersionReq};
|
use semver::{Version, VersionReq};
|
||||||
use serde::{de::Error, Deserialize, Deserializer};
|
use serde::{de::Error, Deserialize, Deserializer};
|
||||||
use tokio::sync::broadcast::Receiver;
|
use tokio::sync::broadcast::Receiver;
|
||||||
|
|
||||||
pub const RECEIPT_LOCATION: &str = "/nix/receipt.json";
|
pub const RECEIPT_LOCATION: &str = "/nix/receipt.json";
|
||||||
|
|
||||||
|
/**
|
||||||
|
A set of [`Action`]s, along with some metadata, which can be carried out to drive an install or
|
||||||
|
revert
|
||||||
|
*/
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct InstallPlan {
|
pub struct InstallPlan {
|
||||||
#[serde(deserialize_with = "ensure_version")]
|
#[serde(deserialize_with = "ensure_version")]
|
||||||
pub(crate) version: Version,
|
pub(crate) version: Version,
|
||||||
|
|
||||||
pub(crate) actions: Vec<Box<dyn Action>>,
|
pub(crate) actions: Vec<StatefulAction<Box<dyn Action>>>,
|
||||||
|
|
||||||
pub(crate) planner: Box<dyn Planner>,
|
pub(crate) planner: Box<dyn Planner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstallPlan {
|
impl InstallPlan {
|
||||||
pub async fn plan(
|
pub async fn default() -> Result<Self, HarmonicError> {
|
||||||
planner: Box<dyn Planner>,
|
let planner = BuiltinPlanner::default().await?.boxed();
|
||||||
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let actions = planner.plan().await?;
|
let actions = planner.plan().await?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
planner,
|
planner,
|
||||||
actions,
|
actions,
|
||||||
version: current_version()?,
|
version: current_version()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn plan<P>(planner: P) -> Result<Self, HarmonicError>
|
||||||
|
where
|
||||||
|
P: Planner + 'static,
|
||||||
|
{
|
||||||
|
let actions = planner.plan().await?;
|
||||||
|
Ok(Self {
|
||||||
|
planner: planner.boxed(),
|
||||||
|
actions,
|
||||||
|
version: current_version()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn describe_execute(
|
pub fn describe_install(&self, explain: bool) -> Result<String, HarmonicError> {
|
||||||
&self,
|
|
||||||
explain: bool,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let Self {
|
let Self {
|
||||||
planner,
|
planner,
|
||||||
actions,
|
actions,
|
||||||
|
@ -134,10 +147,7 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn describe_revert(
|
pub fn describe_uninstall(&self, explain: bool) -> Result<String, HarmonicError> {
|
||||||
&self,
|
|
||||||
explain: bool,
|
|
||||||
) -> Result<String, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let Self {
|
let Self {
|
||||||
version: _,
|
version: _,
|
||||||
planner,
|
planner,
|
||||||
|
@ -196,7 +206,7 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn revert(
|
pub async fn uninstall(
|
||||||
&mut self,
|
&mut self,
|
||||||
cancel_channel: impl Into<Option<Receiver<()>>>,
|
cancel_channel: impl Into<Option<Receiver<()>>>,
|
||||||
) -> Result<(), HarmonicError> {
|
) -> Result<(), HarmonicError> {
|
||||||
|
@ -277,13 +287,11 @@ fn ensure_version<'de, D: Deserializer<'de>>(d: D) -> Result<Version, D::Error>
|
||||||
mod test {
|
mod test {
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
use crate::{planner::BuiltinPlanner, InstallPlan};
|
use crate::{planner::BuiltinPlanner, HarmonicError, InstallPlan};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ensure_version_allows_compatible() -> eyre::Result<()> {
|
async fn ensure_version_allows_compatible() -> Result<(), HarmonicError> {
|
||||||
let planner = BuiltinPlanner::default()
|
let planner = BuiltinPlanner::default().await?;
|
||||||
.await
|
|
||||||
.map_err(|e| eyre::eyre!(e))?;
|
|
||||||
let good_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
let good_version = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
||||||
let value = serde_json::json!({
|
let value = serde_json::json!({
|
||||||
"planner": planner.boxed(),
|
"planner": planner.boxed(),
|
||||||
|
@ -296,10 +304,8 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ensure_version_denies_incompatible() -> eyre::Result<()> {
|
async fn ensure_version_denies_incompatible() -> Result<(), HarmonicError> {
|
||||||
let planner = BuiltinPlanner::default()
|
let planner = BuiltinPlanner::default().await?;
|
||||||
.await
|
|
||||||
.map_err(|e| eyre::eyre!(e))?;
|
|
||||||
let bad_version = Version::parse("9999999999999.9999999999.99999999")?;
|
let bad_version = Version::parse("9999999999999.9999999999.99999999")?;
|
||||||
let value = serde_json::json!({
|
let value = serde_json::json!({
|
||||||
"planner": planner.boxed(),
|
"planner": planner.boxed(),
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Planners for Darwin based systems
|
||||||
|
|
||||||
mod multi;
|
mod multi;
|
||||||
|
|
||||||
pub use multi::DarwinMulti;
|
pub use multi::DarwinMulti;
|
||||||
|
|
|
@ -1,46 +1,63 @@
|
||||||
use std::{collections::HashMap, io::Cursor};
|
use std::{collections::HashMap, io::Cursor};
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
use clap::ArgAction;
|
use clap::ArgAction;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
action::{
|
action::{
|
||||||
common::{ConfigureNix, ProvisionNix},
|
common::{ConfigureNix, ProvisionNix},
|
||||||
darwin::{CreateApfsVolume, KickstartLaunchctlService},
|
darwin::{CreateNixVolume, KickstartLaunchctlService},
|
||||||
|
StatefulAction,
|
||||||
},
|
},
|
||||||
execute_command,
|
execute_command,
|
||||||
os::darwin::DiskUtilOutput,
|
os::darwin::DiskUtilOutput,
|
||||||
planner::{BuiltinPlannerError, Planner},
|
planner::{Planner, PlannerError},
|
||||||
Action, BuiltinPlanner, CommonSettings,
|
settings::CommonSettings,
|
||||||
|
settings::InstallSettingsError,
|
||||||
|
Action, BuiltinPlanner,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
|
/// A planner for MacOS (Darwin) multi-user installs
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
||||||
pub struct DarwinMulti {
|
pub struct DarwinMulti {
|
||||||
#[clap(flatten)]
|
#[cfg_attr(feature = "cli", clap(flatten))]
|
||||||
pub settings: CommonSettings,
|
pub settings: CommonSettings,
|
||||||
/// Force encryption on the volume
|
/// Force encryption on the volume
|
||||||
#[clap(
|
#[cfg_attr(
|
||||||
long,
|
feature = "cli",
|
||||||
action(ArgAction::Set),
|
clap(
|
||||||
default_value = "false",
|
long,
|
||||||
env = "HARMONIC_ENCRYPT"
|
action(ArgAction::Set),
|
||||||
|
default_value = "false",
|
||||||
|
env = "HARMONIC_ENCRYPT"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
pub encrypt: Option<bool>,
|
pub encrypt: Option<bool>,
|
||||||
/// Use a case sensitive volume
|
/// Use a case sensitive volume
|
||||||
#[clap(
|
#[cfg_attr(
|
||||||
long,
|
feature = "cli",
|
||||||
action(ArgAction::SetTrue),
|
clap(
|
||||||
default_value = "false",
|
long,
|
||||||
env = "HARMONIC_CASE_SENSITIVE"
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
env = "HARMONIC_CASE_SENSITIVE"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
pub case_sensitive: bool,
|
pub case_sensitive: bool,
|
||||||
#[clap(long, default_value = "Nix Store", env = "HARMONIC_VOLUME_LABEL")]
|
/// The label for the created APFS volume
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "cli",
|
||||||
|
clap(long, default_value = "Nix Store", env = "HARMONIC_VOLUME_LABEL")
|
||||||
|
)]
|
||||||
pub volume_label: String,
|
pub volume_label: String,
|
||||||
#[clap(long, env = "HARMONIC_ROOT_DISK")]
|
/// The root disk of the target
|
||||||
|
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_ROOT_DISK"))]
|
||||||
pub root_disk: Option<String>,
|
pub root_disk: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn default_root_disk() -> Result<String, BuiltinPlannerError> {
|
async fn default_root_disk() -> Result<String, PlannerError> {
|
||||||
let buf = execute_command(
|
let buf = execute_command(
|
||||||
Command::new("/usr/sbin/diskutil")
|
Command::new("/usr/sbin/diskutil")
|
||||||
.args(["info", "-plist", "/"])
|
.args(["info", "-plist", "/"])
|
||||||
|
@ -57,7 +74,7 @@ async fn default_root_disk() -> Result<String, BuiltinPlannerError> {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "darwin-multi")]
|
#[typetag::serde(name = "darwin-multi")]
|
||||||
impl Planner for DarwinMulti {
|
impl Planner for DarwinMulti {
|
||||||
async fn default() -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
|
async fn default() -> Result<Self, PlannerError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
settings: CommonSettings::default()?,
|
settings: CommonSettings::default()?,
|
||||||
root_disk: Some(default_root_disk().await?),
|
root_disk: Some(default_root_disk().await?),
|
||||||
|
@ -67,7 +84,7 @@ impl Planner for DarwinMulti {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn plan(&self) -> Result<Vec<Box<dyn Action>>, Box<dyn std::error::Error + Sync + Send>> {
|
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
|
||||||
let root_disk = match &self.root_disk {
|
let root_disk = match &self.root_disk {
|
||||||
root_disk @ Some(_) => root_disk.clone(),
|
root_disk @ Some(_) => root_disk.clone(),
|
||||||
None => {
|
None => {
|
||||||
|
@ -89,7 +106,8 @@ impl Planner for DarwinMulti {
|
||||||
Command::new("/usr/bin/fdesetup")
|
Command::new("/usr/bin/fdesetup")
|
||||||
.arg("isactive")
|
.arg("isactive")
|
||||||
.status()
|
.status()
|
||||||
.await?
|
.await
|
||||||
|
.map_err(|e| PlannerError::Custom(Box::new(e)))?
|
||||||
.code()
|
.code()
|
||||||
.map(|v| if v == 0 { false } else { true })
|
.map(|v| if v == 0 { false } else { true })
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
@ -102,24 +120,31 @@ impl Planner for DarwinMulti {
|
||||||
//
|
//
|
||||||
// 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
|
||||||
Box::new(
|
CreateNixVolume::plan(
|
||||||
CreateApfsVolume::plan(
|
root_disk.unwrap(), /* We just ensured it was populated */
|
||||||
root_disk.unwrap(), /* We just ensured it was populated */
|
self.volume_label.clone(),
|
||||||
self.volume_label.clone(),
|
false,
|
||||||
false,
|
encrypt,
|
||||||
encrypt,
|
)
|
||||||
)
|
.await
|
||||||
.await?,
|
.map_err(PlannerError::Action)?
|
||||||
),
|
.boxed(),
|
||||||
Box::new(ProvisionNix::plan(&self.settings).await?),
|
ProvisionNix::plan(&self.settings)
|
||||||
Box::new(ConfigureNix::plan(&self.settings).await?),
|
.await
|
||||||
Box::new(KickstartLaunchctlService::plan("system/org.nixos.nix-daemon".into()).await?),
|
.map_err(PlannerError::Action)?
|
||||||
|
.boxed(),
|
||||||
|
ConfigureNix::plan(&self.settings)
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?
|
||||||
|
.boxed(),
|
||||||
|
KickstartLaunchctlService::plan("system/org.nixos.nix-daemon".into())
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?
|
||||||
|
.boxed(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings(
|
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||||
&self,
|
|
||||||
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let Self {
|
let Self {
|
||||||
settings,
|
settings,
|
||||||
encrypt,
|
encrypt,
|
||||||
|
@ -129,7 +154,7 @@ impl Planner for DarwinMulti {
|
||||||
} = self;
|
} = self;
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
|
|
||||||
map.extend(settings.describe()?.into_iter());
|
map.extend(settings.settings()?.into_iter());
|
||||||
map.insert("volume_encrypt".into(), serde_json::to_value(encrypt)?);
|
map.insert("volume_encrypt".into(), serde_json::to_value(encrypt)?);
|
||||||
map.insert("volume_label".into(), serde_json::to_value(volume_label)?);
|
map.insert("volume_label".into(), serde_json::to_value(volume_label)?);
|
||||||
map.insert("root_disk".into(), serde_json::to_value(root_disk)?);
|
map.insert("root_disk".into(), serde_json::to_value(root_disk)?);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Planners for Linux based systems
|
||||||
|
|
||||||
mod multi;
|
mod multi;
|
||||||
|
|
||||||
pub use multi::LinuxMulti;
|
pub use multi::LinuxMulti;
|
||||||
|
|
|
@ -2,32 +2,37 @@ use crate::{
|
||||||
action::{
|
action::{
|
||||||
base::CreateDirectory,
|
base::CreateDirectory,
|
||||||
common::{ConfigureNix, ProvisionNix},
|
common::{ConfigureNix, ProvisionNix},
|
||||||
|
StatefulAction,
|
||||||
},
|
},
|
||||||
planner::Planner,
|
planner::{Planner, PlannerError},
|
||||||
Action, BuiltinPlanner, CommonSettings,
|
settings::CommonSettings,
|
||||||
|
settings::InstallSettingsError,
|
||||||
|
Action, BuiltinPlanner,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, path::Path};
|
use std::{collections::HashMap, path::Path};
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
|
/// A planner for Linux multi-user installs
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
||||||
pub struct LinuxMulti {
|
pub struct LinuxMulti {
|
||||||
#[clap(flatten)]
|
#[cfg_attr(feature = "cli", clap(flatten))]
|
||||||
pub settings: CommonSettings,
|
pub settings: CommonSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "linux-multi")]
|
#[typetag::serde(name = "linux-multi")]
|
||||||
impl Planner for LinuxMulti {
|
impl Planner for LinuxMulti {
|
||||||
async fn default() -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
|
async fn default() -> Result<Self, PlannerError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
settings: CommonSettings::default()?,
|
settings: CommonSettings::default()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn plan(&self) -> Result<Vec<Box<dyn Action>>, Box<dyn std::error::Error + Sync + Send>> {
|
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
|
||||||
// If on NixOS, running `harmonic` is pointless
|
// If on NixOS, running `harmonic` is pointless
|
||||||
// NixOS always sets up this file as part of setting up /etc itself: https://github.com/NixOS/nixpkgs/blob/bdd39e5757d858bd6ea58ed65b4a2e52c8ed11ca/nixos/modules/system/etc/setup-etc.pl#L145
|
// NixOS always sets up this file as part of setting up /etc itself: https://github.com/NixOS/nixpkgs/blob/bdd39e5757d858bd6ea58ed65b4a2e52c8ed11ca/nixos/modules/system/etc/setup-etc.pl#L145
|
||||||
if Path::new("/etc/NIXOS").exists() {
|
if Path::new("/etc/NIXOS").exists() {
|
||||||
return Err(Error::NixOs.into());
|
return Err(PlannerError::Custom(Box::new(LinuxMultiError::NixOs)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, we don't try to repair the user's Nix install or anything special.
|
// For now, we don't try to repair the user's Nix install or anything special.
|
||||||
|
@ -37,35 +42,30 @@ impl Planner for LinuxMulti {
|
||||||
.status()
|
.status()
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Err(Error::NixExists.into());
|
return Err(PlannerError::Custom(Box::new(LinuxMultiError::NixExists)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
Box::new(
|
CreateDirectory::plan("/nix", None, None, 0o0755, true)
|
||||||
CreateDirectory::plan("/nix", None, None, 0o0755, true)
|
.await
|
||||||
.await
|
.map_err(PlannerError::Action)?
|
||||||
.map_err(|v| Error::Action(v.into()))?,
|
.boxed(),
|
||||||
),
|
ProvisionNix::plan(&self.settings.clone())
|
||||||
Box::new(
|
.await
|
||||||
ProvisionNix::plan(&self.settings.clone())
|
.map_err(PlannerError::Action)?
|
||||||
.await
|
.boxed(),
|
||||||
.map_err(|v| Error::Action(v.into()))?,
|
ConfigureNix::plan(&self.settings)
|
||||||
),
|
.await
|
||||||
Box::new(
|
.map_err(PlannerError::Action)?
|
||||||
ConfigureNix::plan(&self.settings)
|
.boxed(),
|
||||||
.await
|
|
||||||
.map_err(|v| Error::Action(v.into()))?,
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings(
|
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||||
&self,
|
|
||||||
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let Self { settings } = self;
|
let Self { settings } = self;
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
|
|
||||||
map.extend(settings.describe()?.into_iter());
|
map.extend(settings.settings()?.into_iter());
|
||||||
|
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ impl Into<BuiltinPlanner> for LinuxMulti {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
enum Error {
|
enum LinuxMultiError {
|
||||||
#[error("NixOS already has Nix installed")]
|
#[error("NixOS already has Nix installed")]
|
||||||
NixOs,
|
NixOs,
|
||||||
#[error("`nix` is already a valid command, so it is installed")]
|
#[error("`nix` is already a valid command, so it is installed")]
|
||||||
|
|
|
@ -1,21 +1,102 @@
|
||||||
|
/*! [`BuiltinPlanner`]s and traits to create new types which can be used to plan out an [`InstallPlan`]
|
||||||
|
|
||||||
|
It's a [`Planner`]s job to construct (if possible) a valid [`InstallPlan`] for the host. Some planners,
|
||||||
|
like [`LinuxMulti`](linux::LinuxMulti), are operating system specific. Others, like [`SteamDeck`](specific::SteamDeck), are device specific.
|
||||||
|
|
||||||
|
[`Planner`]s contain their planner specific settings, typically alongside a [`CommonSettings`][crate::settings::CommonSettings].
|
||||||
|
|
||||||
|
[`BuiltinPlanner::default()`] offers a way to get the default builtin planner for a given host.
|
||||||
|
|
||||||
|
Custom Planners can also be used to create a platform, project, or organization specific install.
|
||||||
|
|
||||||
|
A custom [`Planner`] can be created:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use std::{error::Error, collections::HashMap};
|
||||||
|
use harmonic::{
|
||||||
|
InstallPlan,
|
||||||
|
settings::{CommonSettings, InstallSettingsError},
|
||||||
|
planner::{Planner, PlannerError, specific::SteamDeck},
|
||||||
|
action::{Action, StatefulAction, linux::StartSystemdUnit},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct MyPlanner {
|
||||||
|
pub common: CommonSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
#[typetag::serde(name = "my-planner")]
|
||||||
|
impl Planner for MyPlanner {
|
||||||
|
async fn default() -> Result<Self, PlannerError> {
|
||||||
|
Ok(Self {
|
||||||
|
common: CommonSettings::default()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
|
||||||
|
Ok(vec![
|
||||||
|
// ...
|
||||||
|
|
||||||
|
StartSystemdUnit::plan("nix-daemon.socket".into())
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?.boxed(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||||
|
let Self { common } = self;
|
||||||
|
let mut map = std::collections::HashMap::default();
|
||||||
|
|
||||||
|
map.extend(common.settings()?.into_iter());
|
||||||
|
|
||||||
|
Ok(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# async fn custom_planner_install() -> color_eyre::Result<()> {
|
||||||
|
let planner = MyPlanner::default().await?;
|
||||||
|
let mut plan = InstallPlan::plan(planner).await?;
|
||||||
|
match plan.install(None).await {
|
||||||
|
Ok(()) => tracing::info!("Done"),
|
||||||
|
Err(e) => {
|
||||||
|
match e.source() {
|
||||||
|
Some(source) => tracing::error!("{e}: {}", source),
|
||||||
|
None => tracing::error!("{e}"),
|
||||||
|
};
|
||||||
|
plan.uninstall(None).await?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
# Ok(())
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
*/
|
||||||
pub mod darwin;
|
pub mod darwin;
|
||||||
pub mod linux;
|
pub mod linux;
|
||||||
pub mod specific;
|
pub mod specific;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{settings::InstallSettingsError, Action, BoxableError};
|
use crate::{
|
||||||
|
action::StatefulAction, settings::InstallSettingsError, Action, HarmonicError, InstallPlan,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Something which can be used to plan out an [`InstallPlan`]
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(tag = "planner")]
|
#[typetag::serde(tag = "planner")]
|
||||||
pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
|
pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
|
||||||
async fn default() -> Result<Self, Box<dyn std::error::Error + Sync + Send>>
|
/// Instantiate the planner with default settings, if possible
|
||||||
|
async fn default() -> Result<Self, PlannerError>
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
async fn plan(&self) -> Result<Vec<Box<dyn Action>>, Box<dyn std::error::Error + Sync + Send>>;
|
/// Plan out the [`Action`]s for an [`InstallPlan`]
|
||||||
fn settings(
|
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError>;
|
||||||
&self,
|
/// The settings being used by the planner
|
||||||
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>>;
|
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError>;
|
||||||
|
/// A boxed, type erased planner
|
||||||
fn boxed(self) -> Box<dyn Planner>
|
fn boxed(self) -> Box<dyn Planner>
|
||||||
where
|
where
|
||||||
Self: Sized + 'static,
|
Self: Sized + 'static,
|
||||||
|
@ -26,15 +107,21 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
|
||||||
|
|
||||||
dyn_clone::clone_trait_object!(Planner);
|
dyn_clone::clone_trait_object!(Planner);
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Subcommand, serde::Serialize, serde::Deserialize)]
|
/// Planners built into this crate
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "cli", derive(clap::Subcommand))]
|
||||||
pub enum BuiltinPlanner {
|
pub enum BuiltinPlanner {
|
||||||
|
/// A standard Linux multi-user install
|
||||||
LinuxMulti(linux::LinuxMulti),
|
LinuxMulti(linux::LinuxMulti),
|
||||||
|
/// A standard MacOS (Darwin) multi-user install
|
||||||
DarwinMulti(darwin::DarwinMulti),
|
DarwinMulti(darwin::DarwinMulti),
|
||||||
|
/// An install suitable for the Valve Steam Deck console
|
||||||
SteamDeck(specific::SteamDeck),
|
SteamDeck(specific::SteamDeck),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuiltinPlanner {
|
impl BuiltinPlanner {
|
||||||
pub async fn default() -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
|
/// Heuristically determine the default planner for the target system
|
||||||
|
pub async fn default() -> Result<Self, PlannerError> {
|
||||||
use target_lexicon::{Architecture, OperatingSystem};
|
use target_lexicon::{Architecture, OperatingSystem};
|
||||||
match (Architecture::host(), OperatingSystem::host()) {
|
match (Architecture::host(), OperatingSystem::host()) {
|
||||||
(Architecture::X86_64, OperatingSystem::Linux) => {
|
(Architecture::X86_64, OperatingSystem::Linux) => {
|
||||||
|
@ -51,17 +138,15 @@ impl BuiltinPlanner {
|
||||||
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
|
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
|
||||||
Ok(Self::DarwinMulti(darwin::DarwinMulti::default().await?))
|
Ok(Self::DarwinMulti(darwin::DarwinMulti::default().await?))
|
||||||
},
|
},
|
||||||
_ => Err(BuiltinPlannerError::UnsupportedArchitecture(target_lexicon::HOST).boxed()),
|
_ => Err(PlannerError::UnsupportedArchitecture(target_lexicon::HOST)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn plan(
|
pub async fn plan(self) -> Result<InstallPlan, HarmonicError> {
|
||||||
self,
|
|
||||||
) -> Result<Vec<Box<dyn Action>>, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
match self {
|
match self {
|
||||||
BuiltinPlanner::LinuxMulti(planner) => planner.plan().await,
|
BuiltinPlanner::LinuxMulti(planner) => InstallPlan::plan(planner).await,
|
||||||
BuiltinPlanner::DarwinMulti(planner) => planner.plan().await,
|
BuiltinPlanner::DarwinMulti(planner) => InstallPlan::plan(planner).await,
|
||||||
BuiltinPlanner::SteamDeck(planner) => planner.plan().await,
|
BuiltinPlanner::SteamDeck(planner) => InstallPlan::plan(planner).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn boxed(self) -> Box<dyn Planner> {
|
pub fn boxed(self) -> Box<dyn Planner> {
|
||||||
|
@ -73,18 +158,22 @@ impl BuiltinPlanner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error originating from a [`Planner`]
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum BuiltinPlannerError {
|
pub enum PlannerError {
|
||||||
|
/// Harmonic does not have a default planner for the target architecture right now
|
||||||
#[error("Harmonic does not have a default planner for the `{0}` architecture right now, pass a specific archetype")]
|
#[error("Harmonic does not have a default planner for the `{0}` architecture right now, pass a specific archetype")]
|
||||||
UnsupportedArchitecture(target_lexicon::Triple),
|
UnsupportedArchitecture(target_lexicon::Triple),
|
||||||
|
/// Error executing action
|
||||||
#[error("Error executing action")]
|
#[error("Error executing action")]
|
||||||
ActionError(
|
Action(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||||
#[source]
|
/// An [`InstallSettingsError`]
|
||||||
#[from]
|
|
||||||
Box<dyn std::error::Error + Send + Sync>,
|
|
||||||
),
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
InstallSettings(#[from] InstallSettingsError),
|
InstallSettings(#[from] InstallSettingsError),
|
||||||
|
/// A MacOS (Darwin) plist related error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Plist(#[from] plist::Error),
|
Plist(#[from] plist::Error),
|
||||||
|
/// Custom planner error
|
||||||
|
#[error("Custom planner error")]
|
||||||
|
Custom(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,42 +5,57 @@ use crate::{
|
||||||
base::CreateDirectory,
|
base::CreateDirectory,
|
||||||
common::ProvisionNix,
|
common::ProvisionNix,
|
||||||
linux::{CreateSystemdSysext, StartSystemdUnit},
|
linux::{CreateSystemdSysext, StartSystemdUnit},
|
||||||
|
StatefulAction,
|
||||||
},
|
},
|
||||||
planner::Planner,
|
planner::{Planner, PlannerError},
|
||||||
Action, BuiltinPlanner, CommonSettings,
|
settings::CommonSettings,
|
||||||
|
settings::InstallSettingsError,
|
||||||
|
Action, BuiltinPlanner,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
|
/// A planner suitable for Valve Steam Deck consoles
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
||||||
pub struct SteamDeck {
|
pub struct SteamDeck {
|
||||||
#[clap(flatten)]
|
#[cfg_attr(feature = "cli", clap(flatten))]
|
||||||
pub settings: CommonSettings,
|
pub settings: CommonSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
#[typetag::serde(name = "steam-deck")]
|
#[typetag::serde(name = "steam-deck")]
|
||||||
impl Planner for SteamDeck {
|
impl Planner for SteamDeck {
|
||||||
async fn default() -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
|
async fn default() -> Result<Self, PlannerError> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
settings: CommonSettings::default()?,
|
settings: CommonSettings::default()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn plan(&self) -> Result<Vec<Box<dyn Action>>, Box<dyn std::error::Error + Sync + Send>> {
|
async fn plan(&self) -> Result<Vec<StatefulAction<Box<dyn Action>>>, PlannerError> {
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
Box::new(CreateSystemdSysext::plan("/var/lib/extensions/nix").await?),
|
CreateSystemdSysext::plan("/var/lib/extensions/nix")
|
||||||
Box::new(CreateDirectory::plan("/nix", None, None, 0o0755, true).await?),
|
.await
|
||||||
Box::new(ProvisionNix::plan(&self.settings.clone()).await?),
|
.map_err(PlannerError::Action)?
|
||||||
Box::new(StartSystemdUnit::plan("nix-daemon.socket".into()).await?),
|
.boxed(),
|
||||||
|
CreateDirectory::plan("/nix", None, None, 0o0755, true)
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?
|
||||||
|
.boxed(),
|
||||||
|
ProvisionNix::plan(&self.settings.clone())
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?
|
||||||
|
.boxed(),
|
||||||
|
StartSystemdUnit::plan("nix-daemon.socket".into())
|
||||||
|
.await
|
||||||
|
.map_err(PlannerError::Action)?
|
||||||
|
.boxed(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings(
|
fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||||
&self,
|
|
||||||
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let Self { settings } = self;
|
let Self { settings } = self;
|
||||||
let mut map = HashMap::default();
|
let mut map = HashMap::default();
|
||||||
|
|
||||||
map.extend(settings.describe()?.into_iter());
|
map.extend(settings.settings()?.into_iter());
|
||||||
|
|
||||||
Ok(map)
|
Ok(map)
|
||||||
}
|
}
|
||||||
|
|
168
src/settings.rs
168
src/settings.rs
|
@ -1,105 +1,151 @@
|
||||||
|
/*! Configurable knobs and their related errors
|
||||||
|
*/
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
use clap::ArgAction;
|
use clap::ArgAction;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::channel_value::ChannelValue;
|
use crate::channel_value::ChannelValue;
|
||||||
|
|
||||||
|
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux x86_64
|
||||||
pub const NIX_X64_64_LINUX_URL: &str =
|
pub const NIX_X64_64_LINUX_URL: &str =
|
||||||
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz";
|
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz";
|
||||||
|
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Linux aarch64
|
||||||
pub const NIX_AARCH64_LINUX_URL: &str =
|
pub const NIX_AARCH64_LINUX_URL: &str =
|
||||||
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-linux.tar.xz";
|
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-linux.tar.xz";
|
||||||
|
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin x86_64
|
||||||
pub const NIX_X64_64_DARWIN_URL: &str =
|
pub const NIX_X64_64_DARWIN_URL: &str =
|
||||||
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-darwin.tar.xz";
|
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-darwin.tar.xz";
|
||||||
|
/// Default [`nix_package_url`](CommonSettings::nix_package_url) for Darwin aarch64
|
||||||
pub const NIX_AARCH64_DARWIN_URL: &str =
|
pub const NIX_AARCH64_DARWIN_URL: &str =
|
||||||
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-darwin.tar.xz";
|
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-darwin.tar.xz";
|
||||||
|
|
||||||
|
/** Common settings used by all [`BuiltinPlanner`](crate::planner::BuiltinPlanner)s
|
||||||
|
|
||||||
|
Settings which only apply to certain [`Planner`](crate::planner::Planner)s should be located in the planner.
|
||||||
|
|
||||||
|
*/
|
||||||
#[serde_with::serde_as]
|
#[serde_with::serde_as]
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, clap::Parser)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "cli", derive(clap::Parser))]
|
||||||
pub struct CommonSettings {
|
pub struct CommonSettings {
|
||||||
/// Channel(s) to add by default, pass multiple times for multiple channels
|
/// Channel(s) to add
|
||||||
#[clap(
|
#[cfg_attr(
|
||||||
|
feature = "cli",clap(
|
||||||
long,
|
long,
|
||||||
value_parser,
|
value_parser,
|
||||||
name = "channel",
|
name = "channel",
|
||||||
action = clap::ArgAction::Append,
|
action = clap::ArgAction::Append,
|
||||||
env = "HARMONIC_CHANNEL",
|
env = "HARMONIC_CHANNEL",
|
||||||
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
|
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
|
||||||
)]
|
))]
|
||||||
pub channels: Vec<ChannelValue>,
|
pub(crate) channels: Vec<ChannelValue>,
|
||||||
|
|
||||||
/// Modify the user profile to automatically load nix
|
/// Modify the user profile to automatically load nix
|
||||||
#[clap(
|
#[cfg_attr(
|
||||||
long,
|
feature = "cli",
|
||||||
action(ArgAction::SetFalse),
|
clap(
|
||||||
default_value = "true",
|
long,
|
||||||
global = true,
|
action(ArgAction::SetFalse),
|
||||||
env = "HARMONIC_NO_MODIFY_PROFILE",
|
default_value = "true",
|
||||||
name = "no-modify-profile"
|
global = true,
|
||||||
|
env = "HARMONIC_NO_MODIFY_PROFILE",
|
||||||
|
name = "no-modify-profile"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
pub modify_profile: bool,
|
pub(crate) modify_profile: bool,
|
||||||
|
|
||||||
/// Number of build users to create
|
/// Number of build users to create
|
||||||
#[clap(long, default_value = "32", env = "HARMONIC_DAEMON_USER_COUNT")]
|
|
||||||
pub daemon_user_count: usize,
|
|
||||||
|
|
||||||
#[clap(long, default_value = "nixbld", env = "HARMONIC_NIX_BUILD_GROUP_NAME")]
|
|
||||||
pub nix_build_group_name: String,
|
|
||||||
|
|
||||||
#[clap(long, default_value_t = 3000, env = "HARMONIC_NIX_BUILD_GROUP_ID")]
|
|
||||||
pub nix_build_group_id: usize,
|
|
||||||
|
|
||||||
#[clap(long, env = "HARMONIC_NIX_BUILD_USER_PREFIX")]
|
|
||||||
#[cfg_attr(target_os = "macos", clap(default_value = "_nixbld"))]
|
|
||||||
#[cfg_attr(target_os = "linux", clap(default_value = "nixbld"))]
|
|
||||||
pub nix_build_user_prefix: String,
|
|
||||||
|
|
||||||
#[clap(long, env = "HARMONIC_NIX_BUILD_USER_ID_BASE")]
|
|
||||||
#[cfg_attr(target_os = "macos", clap(default_value_t = 300))]
|
|
||||||
#[cfg_attr(target_os = "linux", clap(default_value_t = 3000))]
|
|
||||||
pub nix_build_user_id_base: usize,
|
|
||||||
|
|
||||||
#[clap(long, env = "HARMONIC_NIX_PACKAGE_URL")]
|
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
all(target_os = "macos", target_arch = "x86_64"),
|
feature = "cli",
|
||||||
|
clap(long, default_value = "32", env = "HARMONIC_DAEMON_USER_COUNT")
|
||||||
|
)]
|
||||||
|
pub(crate) daemon_user_count: usize,
|
||||||
|
|
||||||
|
/// The Nix build group name
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "cli",
|
||||||
|
clap(long, default_value = "nixbld", env = "HARMONIC_NIX_BUILD_GROUP_NAME")
|
||||||
|
)]
|
||||||
|
pub(crate) nix_build_group_name: String,
|
||||||
|
|
||||||
|
/// The Nix build group GID
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "cli",
|
||||||
|
clap(long, default_value_t = 3000, env = "HARMONIC_NIX_BUILD_GROUP_ID")
|
||||||
|
)]
|
||||||
|
pub(crate) nix_build_group_id: usize,
|
||||||
|
|
||||||
|
/// The Nix build user prefix (user numbers will be postfixed)
|
||||||
|
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_NIX_BUILD_USER_PREFIX"))]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(target_os = "macos", feature = "cli"),
|
||||||
|
clap(default_value = "_nixbld")
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(target_os = "linux", feature = "cli"),
|
||||||
|
clap(default_value = "nixbld")
|
||||||
|
)]
|
||||||
|
pub(crate) nix_build_user_prefix: String,
|
||||||
|
|
||||||
|
/// The Nix build user base UID (ascending)
|
||||||
|
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_NIX_BUILD_USER_ID_BASE"))]
|
||||||
|
#[cfg_attr(all(target_os = "macos", feature = "cli"), clap(default_value_t = 300))]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(target_os = "linux", feature = "cli"),
|
||||||
|
clap(default_value_t = 3000)
|
||||||
|
)]
|
||||||
|
pub(crate) nix_build_user_id_base: usize,
|
||||||
|
|
||||||
|
/// The Nix package URL
|
||||||
|
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_NIX_PACKAGE_URL"))]
|
||||||
|
#[cfg_attr(
|
||||||
|
all(target_os = "macos", target_arch = "x86_64", feature = "cli"),
|
||||||
clap(
|
clap(
|
||||||
default_value = NIX_X64_64_DARWIN_URL,
|
default_value = NIX_X64_64_DARWIN_URL,
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
all(target_os = "macos", target_arch = "aarch64"),
|
all(target_os = "macos", target_arch = "aarch64", feature = "cli"),
|
||||||
clap(
|
clap(
|
||||||
default_value = NIX_AARCH64_DARWIN_URL,
|
default_value = NIX_AARCH64_DARWIN_URL,
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
all(target_os = "linux", target_arch = "x86_64"),
|
all(target_os = "linux", target_arch = "x86_64", feature = "cli"),
|
||||||
clap(
|
clap(
|
||||||
default_value = NIX_X64_64_LINUX_URL,
|
default_value = NIX_X64_64_LINUX_URL,
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
all(target_os = "linux", target_arch = "aarch64"),
|
all(target_os = "linux", target_arch = "aarch64", feature = "cli"),
|
||||||
clap(
|
clap(
|
||||||
default_value = NIX_AARCH64_LINUX_URL,
|
default_value = NIX_AARCH64_LINUX_URL,
|
||||||
)
|
)
|
||||||
)]
|
)]
|
||||||
pub nix_package_url: Url,
|
pub(crate) nix_package_url: Url,
|
||||||
|
|
||||||
#[clap(long, env = "HARMONIC_EXTRA_CONF")]
|
/// Extra configuration lines for `/etc/nix.conf`
|
||||||
pub extra_conf: Option<String>,
|
#[cfg_attr(feature = "cli", clap(long, env = "HARMONIC_EXTRA_CONF"))]
|
||||||
|
pub(crate) extra_conf: Option<String>,
|
||||||
|
|
||||||
#[clap(
|
/// If Harmonic should forcibly recreate files it finds existing
|
||||||
long,
|
#[cfg_attr(
|
||||||
action(ArgAction::SetTrue),
|
feature = "cli",
|
||||||
default_value = "false",
|
clap(
|
||||||
global = true,
|
long,
|
||||||
env = "HARMONIC_FORCE"
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
global = true,
|
||||||
|
env = "HARMONIC_FORCE"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
pub force: bool,
|
pub(crate) force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommonSettings {
|
impl CommonSettings {
|
||||||
|
/// The default settings for the given Architecture & Operating System
|
||||||
pub fn default() -> Result<Self, InstallSettingsError> {
|
pub fn default() -> Result<Self, InstallSettingsError> {
|
||||||
let url;
|
let url;
|
||||||
let nix_build_user_prefix;
|
let nix_build_user_prefix;
|
||||||
|
@ -154,9 +200,8 @@ impl CommonSettings {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn describe(
|
/// A listing of the settings, suitable for [`Planner::settings`](crate::planner::Planner::settings)
|
||||||
&self,
|
pub fn settings(&self) -> Result<HashMap<String, serde_json::Value>, InstallSettingsError> {
|
||||||
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
|
|
||||||
let Self {
|
let Self {
|
||||||
channels,
|
channels,
|
||||||
modify_profile,
|
modify_profile,
|
||||||
|
@ -217,62 +262,85 @@ impl CommonSettings {
|
||||||
|
|
||||||
// Builder Pattern
|
// Builder Pattern
|
||||||
impl CommonSettings {
|
impl CommonSettings {
|
||||||
|
/// Number of build users to create
|
||||||
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
|
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
|
||||||
self.daemon_user_count = count;
|
self.daemon_user_count = count;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Channel(s) to add
|
||||||
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
||||||
self.channels = channels.into_iter().map(Into::into).collect();
|
self.channels = channels.into_iter().map(Into::into).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Modify the user profile to automatically load nix
|
||||||
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
|
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
|
||||||
self.modify_profile = toggle;
|
self.modify_profile = toggle;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Nix build group name
|
||||||
pub fn nix_build_group_name(&mut self, val: String) -> &mut Self {
|
pub fn nix_build_group_name(&mut self, val: String) -> &mut Self {
|
||||||
self.nix_build_group_name = val;
|
self.nix_build_group_name = val;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Nix build group GID
|
||||||
pub fn nix_build_group_id(&mut self, count: usize) -> &mut Self {
|
pub fn nix_build_group_id(&mut self, count: usize) -> &mut Self {
|
||||||
self.nix_build_group_id = count;
|
self.nix_build_group_id = count;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Nix build user prefix (user numbers will be postfixed)
|
||||||
pub fn nix_build_user_prefix(&mut self, val: String) -> &mut Self {
|
pub fn nix_build_user_prefix(&mut self, val: String) -> &mut Self {
|
||||||
self.nix_build_user_prefix = val;
|
self.nix_build_user_prefix = val;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Nix build user base UID (ascending)
|
||||||
pub fn nix_build_user_id_base(&mut self, count: usize) -> &mut Self {
|
pub fn nix_build_user_id_base(&mut self, count: usize) -> &mut Self {
|
||||||
self.nix_build_user_id_base = count;
|
self.nix_build_user_id_base = count;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The Nix package URL
|
||||||
pub fn nix_package_url(&mut self, url: Url) -> &mut Self {
|
pub fn nix_package_url(&mut self, url: Url) -> &mut Self {
|
||||||
self.nix_package_url = url;
|
self.nix_package_url = url;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extra configuration lines for `/etc/nix.conf`
|
||||||
pub fn extra_conf(&mut self, extra_conf: Option<String>) -> &mut Self {
|
pub fn extra_conf(&mut self, extra_conf: Option<String>) -> &mut Self {
|
||||||
self.extra_conf = extra_conf;
|
self.extra_conf = extra_conf;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If Harmonic should forcibly recreate files it finds existing
|
||||||
pub fn force(&mut self, force: bool) -> &mut Self {
|
pub fn force(&mut self, force: bool) -> &mut Self {
|
||||||
self.force = force;
|
self.force = force;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An error originating from a [`Planner::settings`](crate::planner::Planner::settings)
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum InstallSettingsError {
|
pub enum InstallSettingsError {
|
||||||
|
/// Harmonic does not support the architecture right now
|
||||||
#[error("Harmonic does not support the `{0}` architecture right now")]
|
#[error("Harmonic does not support the `{0}` architecture right now")]
|
||||||
UnsupportedArchitecture(target_lexicon::Triple),
|
UnsupportedArchitecture(target_lexicon::Triple),
|
||||||
|
/// Parsing URL
|
||||||
#[error("Parsing URL")]
|
#[error("Parsing URL")]
|
||||||
Parse(
|
Parse(
|
||||||
#[source]
|
#[source]
|
||||||
#[from]
|
#[from]
|
||||||
url::ParseError,
|
url::ParseError,
|
||||||
),
|
),
|
||||||
|
/// JSON serialization or deserialization error
|
||||||
|
#[error("JSON serialization or deserialization error")]
|
||||||
|
SerdeJson(
|
||||||
|
#[source]
|
||||||
|
#[from]
|
||||||
|
serde_json::Error,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue