Working spike out

This commit is contained in:
Ana Hobden 2022-09-09 11:43:35 -07:00
parent fe966932ed
commit fddc0dd302
10 changed files with 789 additions and 363 deletions

393
Cargo.lock generated
View file

@ -37,19 +37,6 @@ dependencies = [
"futures-core",
]
[[package]]
name = "async-compression"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345fd392ab01f746c717b1357165b76f0b67a60192007b234058c9045fdcf695"
dependencies = [
"futures-core",
"futures-io",
"memchr",
"pin-project-lite",
"xz2",
]
[[package]]
name = "async-executor"
version = "1.4.1"
@ -235,6 +222,22 @@ version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
dependencies = [
"byteorder",
"iovec",
]
[[package]]
name = "bytes"
version = "1.2.1"
@ -334,22 +337,6 @@ dependencies = [
"cache-padded",
]
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
@ -448,21 +435,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@ -473,6 +445,18 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "futures"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678"
[[package]]
name = "futures"
version = "0.3.24"
@ -583,6 +567,12 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "glob"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gloo-timers"
version = "0.2.4"
@ -601,7 +591,7 @@ version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be"
dependencies = [
"bytes",
"bytes 1.2.1",
"fnv",
"futures-core",
"futures-sink",
@ -618,24 +608,31 @@ dependencies = [
name = "harmonic"
version = "0.0.1"
dependencies = [
"async-compression",
"async-tar",
"async-trait",
"atty",
"bytes 1.2.1",
"clap",
"color-eyre",
"crossterm",
"eyre",
"futures",
"futures 0.3.24",
"glob",
"nix",
"owo-colors",
"reqwest",
"tar",
"target-lexicon",
"tempdir",
"thiserror",
"tokio",
"tokio-util",
"tracing",
"tracing-error",
"tracing-subscriber",
"valuable",
"walkdir",
"xz2",
]
[[package]]
@ -665,7 +662,7 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
dependencies = [
"bytes",
"bytes 1.2.1",
"fnv",
"itoa",
]
@ -676,7 +673,7 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [
"bytes",
"bytes 1.2.1",
"http",
"pin-project-lite",
]
@ -699,7 +696,7 @@ version = "0.14.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac"
dependencies = [
"bytes",
"bytes 1.2.1",
"futures-channel",
"futures-core",
"futures-util",
@ -718,16 +715,16 @@ dependencies = [
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
name = "hyper-rustls"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
dependencies = [
"bytes",
"http",
"hyper",
"native-tls",
"rustls",
"tokio",
"tokio-native-tls",
"tokio-rustls",
]
[[package]]
@ -766,6 +763,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
"libc",
]
[[package]]
name = "ipnet"
version = "2.5.0"
@ -894,21 +900,15 @@ dependencies = [
]
[[package]]
name = "native-tls"
version = "0.2.10"
name = "nix"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb"
dependencies = [
"lazy_static",
"autocfg",
"bitflags",
"cfg-if",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
@ -936,51 +936,6 @@ version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]]
name = "openssl"
version = "0.10.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "6.3.0"
@ -1125,6 +1080,43 @@ dependencies = [
"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]]
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]]
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]]
name = "redox_syscall"
version = "0.2.16"
@ -1174,7 +1166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92"
dependencies = [
"base64",
"bytes",
"bytes 1.2.1",
"encoding_rs",
"futures-core",
"futures-util",
@ -1182,35 +1174,73 @@ dependencies = [
"http",
"http-body",
"hyper",
"hyper-tls",
"hyper-rustls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustls"
version = "0.20.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
dependencies = [
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
dependencies = [
"base64",
]
[[package]]
name = "ryu"
version = "1.0.11"
@ -1218,13 +1248,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "schannel"
version = "0.1.20"
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"lazy_static",
"windows-sys",
"winapi-util",
]
[[package]]
@ -1234,26 +1263,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.7.0"
name = "sct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c"
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
dependencies = [
"core-foundation-sys",
"libc",
"ring",
"untrusted",
]
[[package]]
@ -1349,6 +1365,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "strsim"
version = "0.10.0"
@ -1376,6 +1398,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "tar"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "target-lexicon"
version = "0.12.4"
@ -1383,17 +1416,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1"
[[package]]
name = "tempfile"
version = "3.3.0"
name = "tempdir"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
"cfg-if",
"fastrand",
"libc",
"redox_syscall",
"rand",
"remove_dir_all",
"winapi",
]
[[package]]
@ -1462,7 +1491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42"
dependencies = [
"autocfg",
"bytes",
"bytes 1.2.1",
"libc",
"memchr",
"mio",
@ -1476,6 +1505,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "tokio-io"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes 0.4.12",
"futures 0.1.31",
"log",
]
[[package]]
name = "tokio-macros"
version = "1.8.0"
@ -1488,13 +1528,14 @@ dependencies = [
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
name = "tokio-rustls"
version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
"native-tls",
"rustls",
"tokio",
"webpki",
]
[[package]]
@ -1503,7 +1544,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
dependencies = [
"bytes",
"bytes 1.2.1",
"futures-core",
"futures-sink",
"pin-project-lite",
@ -1631,6 +1672,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.2.2"
@ -1683,12 +1730,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
@ -1701,6 +1742,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.0"
@ -1793,6 +1845,25 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
dependencies = [
"webpki",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
@ -1900,5 +1971,7 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
dependencies = [
"futures 0.1.31",
"lzma-sys",
"tokio-io",
]

View file

@ -29,3 +29,5 @@ glob = "0.3.0"
xz2 = { version = "0.1.7", features = ["static", "tokio"] }
bytes = "1.2.1"
tar = "0.4.38"
nix = { version = "0.25.0", features = ["user", "fs"], default-features = false }
walkdir = "2.3.2"

View file

@ -109,6 +109,7 @@
]);
doCheck = true;
RUSTFLAGS = "--cfg tracing_unstable";
override = { preBuild ? "", ... }: {
preBuild = preBuild + ''

View file

@ -0,0 +1,37 @@
use reqwest::Url;
#[derive(Debug, Clone)]
pub struct ChannelValue(pub String, pub Url);
impl clap::builder::ValueParserFactory for ChannelValue {
type Parser = ChannelValueParser;
fn value_parser() -> Self::Parser {
ChannelValueParser
}
}
#[derive(Clone, Debug)]
pub struct ChannelValueParser;
impl clap::builder::TypedValueParser for ChannelValueParser {
type Value = ChannelValue;
fn parse_ref(
&self,
_cmd: &clap::Command,
_arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let buf = value.to_str().ok_or_else(|| {
clap::Error::raw(clap::ErrorKind::InvalidValue, "Should be all UTF-8")
})?;
let (name, url) = buf.split_once('=').ok_or_else(|| {
clap::Error::raw(
clap::ErrorKind::InvalidValue,
"Should be formatted `name=url`",
)
})?;
let name = name.to_owned();
let url = url.parse().unwrap();
Ok(ChannelValue(name, url))
}
}

View file

@ -6,7 +6,7 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte
use valuable::Valuable;
#[derive(clap::Args, Debug, Valuable)]
pub(crate) struct Instrumentation {
pub struct Instrumentation {
/// Enable debug logs, -vv for trace
#[clap(
short = 'v',

View file

@ -1,2 +1,4 @@
mod instrumentation;
pub(crate) use instrumentation::Instrumentation;
pub use instrumentation::Instrumentation;
mod channel_value;
pub use channel_value::ChannelValue;

View file

@ -1,10 +1,9 @@
pub(crate) mod arg;
pub(crate) mod subcommand;
use crate::interaction;
use clap::Parser;
use crate::{cli::arg::ChannelValue, interaction};
use clap::{ArgAction, Parser};
use harmonic::Harmonic;
use reqwest::Url;
use std::process::ExitCode;
#[async_trait::async_trait]
@ -15,10 +14,16 @@ pub(crate) trait CommandExecute {
#[derive(Debug, Parser)]
#[clap(version)]
pub(crate) struct HarmonicCli {
#[clap(long, action(ArgAction::SetTrue), default_value = "false")]
pub(crate) dry_run: bool,
#[clap(flatten)]
pub(crate) instrumentation: arg::Instrumentation,
#[clap(long, default_value = "https://nixos.org/channels/nixpkgs-unstable")]
pub(crate) channels: Vec<Url>,
#[clap(
long,
value_parser,
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable"
)]
pub(crate) channels: Vec<arg::ChannelValue>,
#[clap(long)]
pub(crate) no_modify_profile: bool,
#[clap(long, default_value = "32")]
@ -30,12 +35,14 @@ pub(crate) struct HarmonicCli {
#[async_trait::async_trait]
impl CommandExecute for HarmonicCli {
#[tracing::instrument(skip_all, fields(
channels = %self.channels.iter().map(ToString::to_string).collect::<Vec<_>>().join(", "),
channels = %self.channels.iter().map(|ChannelValue(name, url)| format!("{name} {url}")).collect::<Vec<_>>().join(", "),
daemon_user_count = %self.daemon_user_count,
no_modify_profile = %self.no_modify_profile,
dry_run = %self.dry_run,
))]
async fn execute(self) -> eyre::Result<ExitCode> {
let Self {
dry_run,
instrumentation: _,
daemon_user_count,
channels,
@ -50,8 +57,13 @@ impl CommandExecute for HarmonicCli {
let mut harmonic = Harmonic::default();
harmonic.dry_run(dry_run);
harmonic.daemon_user_count(daemon_user_count);
harmonic.channels(channels);
harmonic.channels(
channels
.into_iter()
.map(|ChannelValue(name, url)| (name, url)),
);
harmonic.modify_profile(!no_modify_profile);
if !interaction::confirm("Are you ready to continue?").await? {
@ -67,6 +79,8 @@ impl CommandExecute for HarmonicCli {
harmonic.setup_default_profile().await?;
harmonic.place_nix_configuration().await?;
harmonic.configure_nix_daemon_service().await?;
Ok(ExitCode::SUCCESS)
}
}

View file

@ -1,45 +1,29 @@
#[derive(thiserror::Error, Debug)]
pub enum HarmonicError {
#[error("Downloading Nix")]
DownloadingNix(#[from] reqwest::Error),
#[error("Unpacking Nix")]
UnpackingNix(std::io::Error),
#[error("Running `groupadd`")]
GroupAddSpawn(std::io::Error),
#[error("`groupadd` returned failure")]
GroupAddFailure(std::process::ExitStatus),
#[error("Running `useradd`")]
UserAddSpawn(std::io::Error),
#[error("`useradd` returned failure")]
UserAddFailure(std::process::ExitStatus),
#[error("Creating directory")]
CreateDirectory(std::io::Error),
#[error("Placing channel configuration")]
PlaceChannelConfiguration(std::io::Error),
#[error("Opening file `{0}`")]
OpeningFile(std::path::PathBuf, std::io::Error),
#[error("Writing to file `{0}`")]
WritingFile(std::path::PathBuf, std::io::Error),
#[error("Getting tempdir")]
GettingTempDir(std::io::Error),
#[error("Installing fetched Nix into the new store")]
InstallNixIntoStore(std::io::Error),
#[error("Installing fetched nss-cacert into the new store")]
InstallNssCacertIntoStore(std::io::Error),
#[error("Updating the Nix channel")]
UpdatingNixChannel(std::io::Error),
#[error("Globbing pattern error")]
GlobPatternError(glob::PatternError),
#[error("Could not find nss-cacert")]
#[error("Reqest error")]
Reqwest(#[from] reqwest::Error),
#[error("Unarchiving error")]
Unarchive(std::io::Error),
#[error("Getting temporary directory")]
TempDir(std::io::Error),
#[error("Glob pattern error")]
GlobPatternError(#[from] glob::PatternError),
#[error("Glob globbing error")]
GlobGlobError(#[from] glob::GlobError),
#[error("Symlinking from `{0}` to `{1}`")]
Symlink(std::path::PathBuf, std::path::PathBuf, std::io::Error),
#[error("Renaming from `{0}` to `{1}`")]
Rename(std::path::PathBuf, std::path::PathBuf, std::io::Error),
#[error("Unarchived Nix store did not appear to include a `nss-cacert` location")]
NoNssCacert,
#[error("Creating /etc/nix/nix.conf")]
CreatingNixConf(std::io::Error),
#[error("No supported init syustem found")]
#[error("No supported init system found")]
InitNotSupported,
#[error("Linking `{0}` to `{1}`")]
Linking(std::path::PathBuf, std::path::PathBuf, std::io::Error),
#[error("Running `systemd-tmpfiles`")]
SystemdTmpfiles(std::io::Error),
#[error("Creating directory `{0}`")]
CreateDirectory(std::path::PathBuf, std::io::Error),
#[error("Walking directory `{0}`")]
WalkDirectory(std::path::PathBuf, walkdir::Error),
#[error("Setting permissions `{0}`")]
SetPermissions(std::path::PathBuf, std::io::Error),
#[error("Command `{0}` failed to execute")]
CommandFailedExec(String, std::io::Error),
// TODO(@Hoverbear): This should capture the stdout.
@ -47,4 +31,20 @@ pub enum HarmonicError {
CommandFailedStatus(String),
#[error("Join error")]
JoinError(#[from] tokio::task::JoinError),
#[error("Opening file `{0}` for writing")]
OpenFile(std::path::PathBuf, std::io::Error),
#[error("Opening file `{0}` for writing")]
WriteFile(std::path::PathBuf, std::io::Error),
#[error("Seeking file `{0}` for writing")]
SeekFile(std::path::PathBuf, std::io::Error),
#[error("Changing ownership of `{0}`")]
Chown(std::path::PathBuf, nix::errno::Errno),
#[error("Getting uid for user `{0}`")]
UserId(String, nix::errno::Errno),
#[error("Getting user `{0}`")]
NoUser(String),
#[error("Getting gid for group `{0}`")]
GroupId(String, nix::errno::Errno),
#[error("Getting group `{0}`")]
NoGroup(String),
}

View file

@ -10,7 +10,7 @@ pub(crate) async fn confirm(question: impl AsRef<str>) -> eyre::Result<bool> {
"\
{question}\n\
\n\
{are_you_sure} ({yes}/{no})\
{are_you_sure} ({yes}/{no}): \
",
question = question.as_ref(),
are_you_sure = "Are you sure?".bright_white().bold(),

View file

@ -1,6 +1,8 @@
mod error;
use std::{
ffi::OsStr,
fs::Permissions,
io::SeekFrom,
os::unix::fs::PermissionsExt,
path::{Path, PathBuf},
process::ExitStatus,
@ -16,9 +18,9 @@ pub use nixos::NixOs;
use bytes::Buf;
use glob::glob;
use reqwest::Url;
use tempdir::TempDir;
use tokio::{
fs::{create_dir, create_dir_all, set_permissions, symlink, OpenOptions},
io::AsyncWriteExt,
io::{AsyncSeekExt, AsyncWriteExt},
process::Command,
task::spawn_blocking,
};
@ -26,8 +28,9 @@ use tokio::{
// This uses a Rust builder pattern
#[derive(Debug)]
pub struct Harmonic {
dry_run: bool,
daemon_user_count: usize,
channels: Vec<Url>,
channels: Vec<(String, Url)>,
modify_profile: bool,
nix_build_group_name: String,
nix_build_group_id: usize,
@ -36,12 +39,16 @@ pub struct Harmonic {
}
impl Harmonic {
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
self.dry_run = dry_run;
self
}
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
self.daemon_user_count = count;
self
}
pub fn channels(&mut self, channels: impl IntoIterator<Item = Url>) -> &mut Self {
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
self.channels = channels.into_iter().collect();
self
}
@ -53,61 +60,64 @@ impl Harmonic {
}
impl Harmonic {
#[tracing::instrument(skip_all)]
pub async fn fetch_nix(&self) -> Result<(), HarmonicError> {
// TODO(@hoverbear): architecture specific download
// TODO(@hoverbear): hash check
let res = reqwest::get(
// TODO(@hoverbear): custom url
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?;
fetch_url_and_unpack_xz(
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz",
tempdir.path(),
self.dry_run,
)
.await
.map_err(HarmonicError::DownloadingNix)?;
let bytes = res.bytes().await.map_err(HarmonicError::DownloadingNix)?;
// TODO(@Hoverbear): Pick directory
let handle: Result<(), HarmonicError> = spawn_blocking(|| {
let decoder = xz2::read::XzDecoder::new(bytes.reader());
let mut archive = tar::Archive::new(decoder);
let destination = "/nix/install";
archive
.unpack(destination)
.map_err(HarmonicError::UnpackingNix)?;
tracing::debug!(%destination, "Downloaded & extracted Nix");
Ok(())
})
.await?;
handle?;
let found_nix_path = if !self.dry_run {
// TODO(@Hoverbear): I would like to make this less awful
let found_nix_paths = glob::glob(&format!("{}/nix-*", tempdir.path().display()))?
.collect::<Result<Vec<_>, _>>()?;
assert_eq!(
found_nix_paths.len(),
1,
"Did not expect to find multiple nix paths, please report this"
);
found_nix_paths.into_iter().next().unwrap()
} else {
PathBuf::from("/nix/nix-*")
};
rename(found_nix_path.join("store"), "/nix/store", self.dry_run).await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn create_group(&self) -> Result<(), HarmonicError> {
let status = Command::new("groupadd")
.arg("-g")
.arg(self.nix_build_group_id.to_string())
.arg("--system")
.arg(&self.nix_build_group_name)
.status()
.await
.map_err(HarmonicError::GroupAddSpawn)?;
if !status.success() {
Err(HarmonicError::GroupAddFailure(status))
} else {
Ok(())
}
execute_command(
Command::new("groupadd")
.arg("-g")
.arg(self.nix_build_group_id.to_string())
.arg("--system")
.arg(&self.nix_build_group_name),
self.dry_run,
)
.await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn create_users(&self) -> Result<(), HarmonicError> {
for index in 1..=self.daemon_user_count {
let user_name = format!("{}{index}", self.nix_build_user_prefix);
let user_id = self.nix_build_user_id_base + index;
let status = Command::new("useradd")
.args([
execute_command(
Command::new("useradd").args([
"--home-dir",
"/var/empty",
"--comment",
&format!("\"Nix build user {user_id}\""),
"--gid",
&self.nix_build_group_id.to_string(),
&self.nix_build_group_name.to_string(),
"--groups",
&self.nix_build_group_name.to_string(),
"--no-user-group",
@ -119,24 +129,33 @@ impl Harmonic {
"--password",
"\"!\"",
&user_name.to_string(),
])
.status()
.await
.map_err(HarmonicError::UserAddSpawn)?;
if !status.success() {
return Err(HarmonicError::UserAddFailure(status));
}
]),
self.dry_run,
)
.await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn create_directories(&self) -> Result<(), HarmonicError> {
let permissions = Permissions::from_mode(0o755);
let paths = [
create_directory("/nix", self.dry_run).await?;
set_permissions(
"/nix",
None,
Some("root".to_string()),
Some(self.nix_build_group_name.clone()),
self.dry_run,
)
.await?;
let permissions = Permissions::from_mode(0o0755);
let paths = [
"/nix/var",
"/nix/var/log",
"/nix/var/log/nix",
"/nix/var/log/nix/drvs",
"/nix/var/nix",
"/nix/var/nix/db",
"/nix/var/nix/gcroots",
"/nix/var/nix/gcroots/per-user",
@ -146,35 +165,54 @@ impl Harmonic {
"/nix/var/nix/userpool",
"/nix/var/nix/daemon-socket",
];
for path in paths {
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
create_dir_with_permissions(path, permissions.clone())
.await
.map_err(HarmonicError::CreateDirectory)?;
create_directory(path, self.dry_run).await?;
set_permissions(path, Some(permissions.clone()), None, None, self.dry_run).await?;
}
create_directory("/nix/store", self.dry_run).await?;
set_permissions(
"/nix/store",
Some(Permissions::from_mode(0o1775)),
None,
Some(self.nix_build_group_name.clone()),
self.dry_run,
)
.await?;
create_directory("/etc/nix", self.dry_run).await?;
set_permissions(
"/etc/nix",
Some(Permissions::from_mode(0o0555)),
None,
None,
self.dry_run,
)
.await?;
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn place_channel_configuration(&self) -> Result<(), HarmonicError> {
let mut file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open("/root/.nix-channels") // TODO(@hoverbear): We should figure out the actual root dir
.await
.map_err(HarmonicError::PlaceChannelConfiguration)?;
let buf = self
.channels
.iter()
.map(ToString::to_string)
.map(|(name, url)| format!("{} {}", url, name))
.collect::<Vec<_>>()
.join("\n");
file.write_all(buf.as_bytes())
.await
.map_err(HarmonicError::PlaceChannelConfiguration)?;
Ok(())
create_file_if_not_exists("/root/.nix-channels", buf, self.dry_run).await?;
set_permissions(
"/root/.nix-channels",
Some(Permissions::from_mode(0o0664)),
None,
None,
self.dry_run,
)
.await
}
#[tracing::instrument(skip_all)]
pub async fn configure_shell_profile(&self) -> Result<(), HarmonicError> {
const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc",
@ -191,86 +229,105 @@ impl Harmonic {
# Nix\n\
if [ -e '{PROFILE_NIX_FILE}' ]; then\n\
. '{PROFILE_NIX_FILE}'\n\
fi\n
fi\n\
# End Nix\n
\n",
);
if path.exists() {
// TODO(@Hoverbear): Backup
// TODO(@Hoverbear): See if the line already exists, skip setting it
tracing::trace!("TODO");
} else if let Some(parent) = path.parent() {
create_dir_all(parent).await.unwrap()
}
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(profile_target)
.await
.map_err(|e| HarmonicError::OpeningFile(path.to_owned(), e))?;
file.write_all(buf.as_bytes())
.await
.map_err(|e| HarmonicError::WritingFile(path.to_owned(), e))?;
create_or_append_file(path, buf, self.dry_run).await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn setup_default_profile(&self) -> Result<(), HarmonicError> {
Command::new("/nix/install/bin/nix-env")
.arg("-i")
.arg("/nix/install")
.status()
.await
.map_err(HarmonicError::InstallNixIntoStore)?;
// Find an `nss-cacert` package, add it too.
let mut found_nss_ca_cert = None;
for entry in
glob("/nix/install/store/*-nss-cacert").map_err(HarmonicError::GlobPatternError)?
{
match entry {
Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique
found_nss_ca_cert = Some(path);
break;
}
Err(_) => continue, /* Ignore it */
};
}
if let Some(nss_ca_cert) = found_nss_ca_cert {
let status = Command::new("/nix/install/bin/nix-env")
.arg("-i")
.arg(&nss_ca_cert)
.status()
.await
.map_err(HarmonicError::InstallNssCacertIntoStore)?;
if !status.success() {
// TODO(@Hoverbear): report
// Find an `nix` package
let nix_pkg_glob = "/nix/store/*-nix-*";
let found_nix_pkg = if !self.dry_run {
let mut found_pkg = None;
for entry in glob(nix_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
match entry {
Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique
found_pkg = Some(path);
break;
}
Err(_) => continue, /* Ignore it */
};
}
std::env::set_var("NIX_SSL_CERT_FILE", &nss_ca_cert);
found_pkg
} else {
// This is a mock for dry running.
Some(PathBuf::from(nix_pkg_glob))
};
let nix_pkg = if let Some(nix_pkg) = found_nix_pkg {
nix_pkg
} else {
return Err(HarmonicError::NoNssCacert);
}
};
execute_command(
Command::new(nix_pkg.join("bin/nix-env"))
.arg("-i")
.arg(&nix_pkg),
self.dry_run,
)
.await?;
// Find an `nss-cacert` package, add it too.
let nss_ca_cert_pkg_glob = "/nix/store/*-nss-cacert-*";
let found_nss_ca_cert_pkg = if !self.dry_run {
let mut found_pkg = None;
for entry in glob(nss_ca_cert_pkg_glob).map_err(HarmonicError::GlobPatternError)? {
match entry {
Ok(path) => {
// TODO(@Hoverbear): Should probably ensure is unique
found_pkg = Some(path);
break;
}
Err(_) => continue, /* Ignore it */
};
}
found_pkg
} else {
// This is a mock for dry running.
Some(PathBuf::from(nss_ca_cert_pkg_glob))
};
if let Some(nss_ca_cert_pkg) = found_nss_ca_cert_pkg {
execute_command(
Command::new(nix_pkg.join("bin/nix-env"))
.arg("-i")
.arg(&nss_ca_cert_pkg),
self.dry_run,
)
.await?;
set_env(
"NIX_SSL_CERT_FILE",
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
self.dry_run,
);
nss_ca_cert_pkg
} else {
return Err(HarmonicError::NoNssCacert);
};
if !self.channels.is_empty() {
status_failure_as_error(
Command::new("/nix/install/bin/nix-channel")
execute_command(
Command::new(nix_pkg.join("bin/nix-channel"))
.arg("--update")
.arg("nixpkgs"),
.arg("nixpkgs")
.env(
"NIX_SSL_CERT_FILE",
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
),
self.dry_run,
)
.await?;
}
Ok(())
}
#[tracing::instrument(skip_all)]
pub async fn place_nix_configuration(&self) -> Result<(), HarmonicError> {
let mut nix_conf = OpenOptions::new()
.create_new(true)
.write(true)
.read(true)
.open("/etc/nix/nix.conf")
.await
.map_err(HarmonicError::CreatingNixConf)?;
let buf = format!(
"\
{extra_conf}\n\
@ -279,14 +336,10 @@ impl Harmonic {
extra_conf = "", // TODO(@Hoverbear): populate me
build_group_name = self.nix_build_group_name,
);
nix_conf
.write_all(buf.as_bytes())
.await
.map_err(HarmonicError::CreatingNixConf)?;
Ok(())
create_file_if_not_exists("/etc/nix/nix.conf", buf, self.dry_run).await
}
#[tracing::instrument(skip_all)]
pub async fn configure_nix_daemon_service(&self) -> Result<(), HarmonicError> {
if Path::new("/run/systemd/system").exists() {
const SERVICE_SRC: &str =
@ -299,30 +352,38 @@ impl Harmonic {
"/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-daemon.conf";
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
symlink(TMPFILES_SRC, TMPFILES_DEST).await.map_err(|e| {
HarmonicError::Linking(PathBuf::from(TMPFILES_SRC), PathBuf::from(TMPFILES_DEST), e)
})?;
status_failure_as_error(
symlink(TMPFILES_SRC, TMPFILES_DEST, self.dry_run).await?;
execute_command(
Command::new("systemd-tmpfiles")
.arg("--create")
.arg("--prefix=/nix/var/nix"),
self.dry_run,
)
.await?;
execute_command(
Command::new("systemctl").arg("link").arg(SERVICE_SRC),
self.dry_run,
)
.await?;
execute_command(
Command::new("systemctl").arg("enable").arg(SOCKET_SRC),
self.dry_run,
)
.await?;
status_failure_as_error(Command::new("systemctl").arg("link").arg(SERVICE_SRC)).await?;
status_failure_as_error(Command::new("systemctl").arg("enable").arg(SOCKET_SRC))
.await?;
// TODO(@Hoverbear): Handle proxy vars
status_failure_as_error(Command::new("systemctl").arg("daemon-reload")).await?;
status_failure_as_error(
execute_command(Command::new("systemctl").arg("daemon-reload"), self.dry_run).await?;
execute_command(
Command::new("systemctl")
.arg("start")
.arg("nix-daemon.socket"),
self.dry_run,
)
.await?;
status_failure_as_error(
execute_command(
Command::new("systemctl")
.arg("restart")
.arg("nix-daemon.service"),
self.dry_run,
)
.await?;
} else {
@ -335,9 +396,13 @@ impl Harmonic {
impl Default for Harmonic {
fn default() -> Self {
Self {
channels: vec!["https://nixos.org/channels/nixpkgs-unstable"
.parse::<Url>()
.unwrap()],
dry_run: true,
channels: vec![(
"nixpkgs".to_string(),
"https://nixos.org/channels/nixpkgs-unstable"
.parse::<Url>()
.unwrap(),
)],
daemon_user_count: 32,
modify_profile: true,
nix_build_group_name: String::from("nixbld"),
@ -348,24 +413,256 @@ impl Default for Harmonic {
}
}
async fn create_dir_with_permissions(
#[tracing::instrument(skip_all, fields(
path = %path.as_ref().display(),
permissions = tracing::field::valuable(&permissions.clone().map(|v| format!("{:#o}", v.mode()))),
owner = tracing::field::valuable(&owner),
group = tracing::field::valuable(&group),
))]
async fn set_permissions(
path: impl AsRef<Path>,
permissions: Permissions,
) -> Result<(), std::io::Error> {
let path = path.as_ref();
create_dir(path).await?;
set_permissions(path, permissions).await?;
permissions: Option<Permissions>,
owner: Option<String>,
group: Option<String>,
dry_run: bool,
) -> Result<(), HarmonicError> {
use nix::unistd::{chown, Group, User};
use walkdir::WalkDir;
if !dry_run {
let path = path.as_ref();
let uid = if let Some(owner) = owner {
let uid = User::from_name(owner.as_str())
.map_err(|e| HarmonicError::UserId(owner.clone(), e))?
.ok_or(HarmonicError::NoUser(owner))?
.uid;
Some(uid)
} else {
None
};
let gid = if let Some(group) = group {
let gid = Group::from_name(group.as_str())
.map_err(|e| HarmonicError::GroupId(group.clone(), e))?
.ok_or(HarmonicError::NoGroup(group))?
.gid;
Some(gid)
} else {
None
};
for child in WalkDir::new(path) {
let entry = child.map_err(|e| HarmonicError::WalkDirectory(path.to_owned(), e))?;
if let Some(ref perms) = permissions {
tokio::fs::set_permissions(path, perms.clone())
.await
.map_err(|e| HarmonicError::SetPermissions(path.to_owned(), e))?;
}
chown(entry.path(), uid, gid)
.map_err(|e| HarmonicError::Chown(entry.path().to_owned(), e))?;
}
} else {
tracing::info!("Dry run: Would recursively set permissions/ownership");
}
Ok(())
}
async fn status_failure_as_error(command: &mut Command) -> Result<ExitStatus, HarmonicError> {
let command_str = format!("{:?}", command.as_std());
let status = command
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
match status.success() {
true => Ok(status),
false => Err(HarmonicError::CommandFailedStatus(command_str)),
#[tracing::instrument(skip_all, fields(
path = %path.as_ref().display(),
))]
async fn create_directory(path: impl AsRef<Path>, dry_run: bool) -> Result<(), HarmonicError> {
use tokio::fs::create_dir;
if !dry_run {
let path = path.as_ref();
create_dir(path)
.await
.map_err(|e| HarmonicError::CreateDirectory(path.to_owned(), e))?;
} else {
tracing::info!("Dry run: Would create directory");
}
Ok(())
}
#[tracing::instrument(skip_all, fields(command = %format!("{:?}", command.as_std())))]
async fn execute_command(
command: &mut Command,
dry_run: bool,
) -> Result<ExitStatus, HarmonicError> {
if !dry_run {
let command_str = format!("{:?}", command.as_std());
let status = command
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(command_str.clone(), e))?;
match status.success() {
true => Ok(status),
false => Err(HarmonicError::CommandFailedStatus(command_str)),
}
} else {
tracing::info!("Dry run: Would execute");
// You cannot conjure "good" exit status in Rust without breaking the rules
// So... we conjure one from `true`
Command::new("true")
.status()
.await
.map_err(|e| HarmonicError::CommandFailedExec(String::from("true"), e))
}
}
#[tracing::instrument(skip_all, fields(
path = %path.as_ref().display(),
buf = %format!("```{}```", buf.as_ref()),
))]
async fn create_or_append_file(
path: impl AsRef<Path>,
buf: impl AsRef<str>,
dry_run: bool,
) -> Result<(), HarmonicError> {
use tokio::fs::{create_dir_all, OpenOptions};
let path = path.as_ref();
let buf = buf.as_ref();
if !dry_run {
if let Some(parent) = path.parent() {
create_dir_all(parent)
.await
.map_err(|e| HarmonicError::CreateDirectory(parent.to_owned(), e))?;
}
let mut file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(&path)
.await
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
file.seek(SeekFrom::End(0))
.await
.map_err(|e| HarmonicError::SeekFile(path.to_owned(), e))?;
file.write_all(buf.as_bytes())
.await
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
} else {
tracing::info!("Dry run: Would create or append");
}
Ok(())
}
#[tracing::instrument(skip_all, fields(
path = %path.as_ref().display(),
buf = %format!("`{}`", buf.as_ref()),
))]
async fn create_file_if_not_exists(
path: impl AsRef<Path>,
buf: impl AsRef<str>,
dry_run: bool,
) -> Result<(), HarmonicError> {
use tokio::fs::{create_dir_all, OpenOptions};
let path = path.as_ref();
let buf = buf.as_ref();
if !dry_run {
if let Some(parent) = path.parent() {
create_dir_all(parent)
.await
.map_err(|e| HarmonicError::CreateDirectory(parent.to_owned(), e))?;
}
let mut file = OpenOptions::new()
.create(true)
.write(true)
.read(true)
.open(&path)
.await
.map_err(|e| HarmonicError::OpenFile(path.to_owned(), e))?;
file.write_all(buf.as_bytes())
.await
.map_err(|e| HarmonicError::WriteFile(path.to_owned(), e))?;
} else {
tracing::info!("Dry run: Would create (or error if exists)");
}
Ok(())
}
#[tracing::instrument(skip_all, fields(
src = %src.as_ref().display(),
dest = %dest.as_ref().display(),
))]
async fn symlink(
src: impl AsRef<Path>,
dest: impl AsRef<Path>,
dry_run: bool,
) -> Result<(), HarmonicError> {
let src = src.as_ref();
let dest = dest.as_ref();
if !dry_run {
tokio::fs::symlink(src, dest)
.await
.map_err(|e| HarmonicError::Symlink(src.to_owned(), dest.to_owned(), e))?;
} else {
tracing::info!("Dry run: Would symlink",);
}
Ok(())
}
#[tracing::instrument(skip_all, fields(
src = %src.as_ref().display(),
dest = %dest.as_ref().display(),
))]
async fn rename(
src: impl AsRef<Path>,
dest: impl AsRef<Path>,
dry_run: bool,
) -> Result<(), HarmonicError> {
let src = src.as_ref();
let dest = dest.as_ref();
if !dry_run {
tokio::fs::rename(src, dest)
.await
.map_err(|e| HarmonicError::Rename(src.to_owned(), dest.to_owned(), e))?;
} else {
tracing::info!("Dry run: Would rename",);
}
Ok(())
}
#[tracing::instrument(skip_all, fields(
url = %url.as_ref(),
dest = %dest.as_ref().display(),
))]
async fn fetch_url_and_unpack_xz(
url: impl AsRef<str>,
dest: impl AsRef<Path>,
dry_run: bool,
) -> Result<(), HarmonicError> {
let url = url.as_ref();
let dest = dest.as_ref().to_owned();
if !dry_run {
let res = reqwest::get(url).await.map_err(HarmonicError::Reqwest)?;
let bytes = res.bytes().await.map_err(HarmonicError::Reqwest)?;
// TODO(@Hoverbear): Pick directory
let handle: Result<(), HarmonicError> = spawn_blocking(move || {
let decoder = xz2::read::XzDecoder::new(bytes.reader());
let mut archive = tar::Archive::new(decoder);
archive.unpack(&dest).map_err(HarmonicError::Unarchive)?;
tracing::debug!(dest = %dest.display(), "Downloaded & extracted Nix");
Ok(())
})
.await?;
handle?;
} else {
tracing::info!("Dry run: Would fetch and unpack xz tarball");
}
Ok(())
}
#[tracing::instrument(skip_all, fields(
k = %k.as_ref().to_string_lossy(),
v = %v.as_ref().to_string_lossy(),
))]
fn set_env(k: impl AsRef<OsStr>, v: impl AsRef<OsStr>, dry_run: bool) {
if !dry_run {
std::env::set_var(k.as_ref(), v.as_ref());
} else {
tracing::info!("Dry run: Would set env");
}
}