2022-09-02 23:03:57 +00:00
|
|
|
mod error;
|
2022-09-14 22:18:13 +00:00
|
|
|
mod actions;
|
|
|
|
mod plan;
|
|
|
|
mod settings;
|
|
|
|
|
2022-09-08 00:13:06 +00:00
|
|
|
use std::{
|
2022-09-09 18:43:35 +00:00
|
|
|
ffi::OsStr,
|
2022-09-08 00:13:06 +00:00
|
|
|
fs::Permissions,
|
2022-09-09 18:43:35 +00:00
|
|
|
io::SeekFrom,
|
2022-09-08 00:13:06 +00:00
|
|
|
os::unix::fs::PermissionsExt,
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
process::ExitStatus,
|
|
|
|
};
|
2022-09-06 19:48:37 +00:00
|
|
|
|
|
|
|
pub use error::HarmonicError;
|
2022-09-15 17:29:22 +00:00
|
|
|
pub use plan::InstallPlan;
|
|
|
|
pub use settings::InstallSettings;
|
2022-09-06 19:48:37 +00:00
|
|
|
|
2022-09-08 00:13:06 +00:00
|
|
|
use bytes::Buf;
|
|
|
|
use glob::glob;
|
2022-09-02 23:03:57 +00:00
|
|
|
use reqwest::Url;
|
2022-09-09 18:43:35 +00:00
|
|
|
use tempdir::TempDir;
|
2022-09-06 19:48:37 +00:00
|
|
|
use tokio::{
|
2022-09-09 18:43:35 +00:00
|
|
|
io::{AsyncSeekExt, AsyncWriteExt},
|
2022-09-06 19:48:37 +00:00
|
|
|
process::Command,
|
2022-09-08 00:13:06 +00:00
|
|
|
task::spawn_blocking,
|
2022-09-06 19:48:37 +00:00
|
|
|
};
|
2022-09-02 23:03:57 +00:00
|
|
|
|
|
|
|
// This uses a Rust builder pattern
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Harmonic {
|
2022-09-09 18:43:35 +00:00
|
|
|
dry_run: bool,
|
2022-09-02 23:03:57 +00:00
|
|
|
daemon_user_count: usize,
|
2022-09-09 18:43:35 +00:00
|
|
|
channels: Vec<(String, Url)>,
|
2022-09-02 23:03:57 +00:00
|
|
|
modify_profile: bool,
|
2022-09-06 19:48:37 +00:00
|
|
|
nix_build_group_name: String,
|
|
|
|
nix_build_group_id: usize,
|
|
|
|
nix_build_user_prefix: String,
|
|
|
|
nix_build_user_id_base: usize,
|
2022-09-02 23:03:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Harmonic {
|
2022-09-09 18:43:35 +00:00
|
|
|
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
|
|
|
|
self.dry_run = dry_run;
|
|
|
|
self
|
|
|
|
}
|
2022-09-02 23:03:57 +00:00
|
|
|
pub fn daemon_user_count(&mut self, count: usize) -> &mut Self {
|
|
|
|
self.daemon_user_count = count;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
2022-09-02 23:03:57 +00:00
|
|
|
self.channels = channels.into_iter().collect();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn modify_profile(&mut self, toggle: bool) -> &mut Self {
|
|
|
|
self.modify_profile = toggle;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Harmonic {
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-06 19:48:37 +00:00
|
|
|
pub async fn fetch_nix(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:28:33 +00:00
|
|
|
tracing::info!("Fetching nix");
|
2022-09-02 23:03:57 +00:00
|
|
|
// TODO(@hoverbear): architecture specific download
|
|
|
|
// TODO(@hoverbear): hash check
|
2022-09-09 18:43:35 +00:00
|
|
|
// TODO(@hoverbear): custom url
|
|
|
|
let tempdir = TempDir::new("nix").map_err(HarmonicError::TempDir)?;
|
|
|
|
fetch_url_and_unpack_xz(
|
2022-09-02 23:03:57 +00:00
|
|
|
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz",
|
2022-09-09 18:43:35 +00:00
|
|
|
tempdir.path(),
|
|
|
|
self.dry_run,
|
2022-09-02 23:03:57 +00:00
|
|
|
)
|
2022-09-08 00:13:06 +00:00
|
|
|
.await?;
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
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?;
|
2022-09-08 00:13:06 +00:00
|
|
|
|
2022-09-02 23:03:57 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-06 19:48:37 +00:00
|
|
|
pub async fn create_group(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:28:33 +00:00
|
|
|
tracing::info!("Creating group");
|
2022-09-09 18:43:35 +00:00
|
|
|
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(())
|
2022-09-06 19:48:37 +00:00
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-06 19:48:37 +00:00
|
|
|
pub async fn create_users(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:28:33 +00:00
|
|
|
tracing::info!("Creating users");
|
2022-09-06 19:48:37 +00:00
|
|
|
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;
|
2022-09-09 18:43:35 +00:00
|
|
|
execute_command(
|
|
|
|
Command::new("useradd").args([
|
2022-09-06 19:48:37 +00:00
|
|
|
"--home-dir",
|
|
|
|
"/var/empty",
|
|
|
|
"--comment",
|
|
|
|
&format!("\"Nix build user {user_id}\""),
|
|
|
|
"--gid",
|
2022-09-09 18:43:35 +00:00
|
|
|
&self.nix_build_group_name.to_string(),
|
2022-09-06 19:48:37 +00:00
|
|
|
"--groups",
|
|
|
|
&self.nix_build_group_name.to_string(),
|
|
|
|
"--no-user-group",
|
|
|
|
"--system",
|
|
|
|
"--shell",
|
|
|
|
"/sbin/nologin",
|
|
|
|
"--uid",
|
|
|
|
&user_id.to_string(),
|
|
|
|
"--password",
|
|
|
|
"\"!\"",
|
|
|
|
&user_name.to_string(),
|
2022-09-09 18:43:35 +00:00
|
|
|
]),
|
|
|
|
self.dry_run,
|
|
|
|
)
|
|
|
|
.await?;
|
2022-09-06 19:48:37 +00:00
|
|
|
}
|
2022-09-02 23:03:57 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2022-09-09 18:43:35 +00:00
|
|
|
|
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-06 19:48:37 +00:00
|
|
|
pub async fn create_directories(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:28:33 +00:00
|
|
|
tracing::info!("Creating directories");
|
2022-09-09 18:43:35 +00:00
|
|
|
create_directory("/nix", self.dry_run).await?;
|
|
|
|
set_permissions(
|
2022-09-06 19:48:37 +00:00
|
|
|
"/nix",
|
2022-09-09 18:43:35 +00:00
|
|
|
None,
|
|
|
|
Some("root".to_string()),
|
|
|
|
Some(self.nix_build_group_name.clone()),
|
|
|
|
self.dry_run,
|
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
let permissions = Permissions::from_mode(0o0755);
|
|
|
|
let paths = [
|
2022-09-06 19:48:37 +00:00
|
|
|
"/nix/var",
|
|
|
|
"/nix/var/log",
|
|
|
|
"/nix/var/log/nix",
|
|
|
|
"/nix/var/log/nix/drvs",
|
2022-09-09 18:43:35 +00:00
|
|
|
"/nix/var/nix",
|
2022-09-06 19:48:37 +00:00
|
|
|
"/nix/var/nix/db",
|
|
|
|
"/nix/var/nix/gcroots",
|
|
|
|
"/nix/var/nix/gcroots/per-user",
|
|
|
|
"/nix/var/nix/profiles",
|
|
|
|
"/nix/var/nix/profiles/per-user",
|
|
|
|
"/nix/var/nix/temproots",
|
|
|
|
"/nix/var/nix/userpool",
|
|
|
|
"/nix/var/nix/daemon-socket",
|
|
|
|
];
|
2022-09-09 18:43:35 +00:00
|
|
|
|
2022-09-06 19:48:37 +00:00
|
|
|
for path in paths {
|
|
|
|
// We use `create_dir` over `create_dir_all` to ensure we always set permissions right
|
2022-09-09 18:43:35 +00:00
|
|
|
create_directory(path, self.dry_run).await?;
|
|
|
|
set_permissions(path, Some(permissions.clone()), None, None, self.dry_run).await?;
|
2022-09-06 19:48:37 +00:00
|
|
|
}
|
2022-09-09 18:43:35 +00:00
|
|
|
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?;
|
2022-09-06 19:48:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-06 19:48:37 +00:00
|
|
|
pub async fn place_channel_configuration(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:28:33 +00:00
|
|
|
tracing::info!("Placing channel configuration");
|
2022-09-06 19:48:37 +00:00
|
|
|
let buf = self
|
|
|
|
.channels
|
|
|
|
.iter()
|
2022-09-09 18:43:35 +00:00
|
|
|
.map(|(name, url)| format!("{} {}", url, name))
|
2022-09-06 19:48:37 +00:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
.join("\n");
|
2022-09-09 18:43:35 +00:00
|
|
|
|
|
|
|
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
|
2022-09-06 19:48:37 +00:00
|
|
|
}
|
2022-09-09 18:43:35 +00:00
|
|
|
|
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-06 19:48:37 +00:00
|
|
|
pub async fn configure_shell_profile(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:28:33 +00:00
|
|
|
tracing::info!("Configuring shell profile");
|
2022-09-08 00:13:06 +00:00
|
|
|
const PROFILE_TARGETS: &[&str] = &[
|
|
|
|
"/etc/bashrc",
|
|
|
|
"/etc/profile.d/nix.sh",
|
|
|
|
"/etc/zshrc",
|
|
|
|
"/etc/bash.bashrc",
|
|
|
|
"/etc/zsh/zshrc",
|
|
|
|
];
|
|
|
|
const PROFILE_NIX_FILE: &str = "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh";
|
|
|
|
for profile_target in PROFILE_TARGETS {
|
|
|
|
let path = Path::new(profile_target);
|
|
|
|
let buf = format!(
|
|
|
|
"\n\
|
|
|
|
# Nix\n\
|
|
|
|
if [ -e '{PROFILE_NIX_FILE}' ]; then\n\
|
|
|
|
. '{PROFILE_NIX_FILE}'\n\
|
2022-09-09 18:43:35 +00:00
|
|
|
fi\n\
|
2022-09-08 00:13:06 +00:00
|
|
|
# End Nix\n
|
|
|
|
\n",
|
|
|
|
);
|
2022-09-09 18:43:35 +00:00
|
|
|
create_or_append_file(path, buf, self.dry_run).await?;
|
2022-09-08 00:13:06 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-08 00:13:06 +00:00
|
|
|
pub async fn setup_default_profile(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:39:16 +00:00
|
|
|
tracing::info!("Setting up default profile");
|
2022-09-09 18:43:35 +00:00
|
|
|
// 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 */
|
|
|
|
};
|
|
|
|
}
|
|
|
|
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"))
|
2022-09-08 00:13:06 +00:00
|
|
|
.arg("-i")
|
2022-09-09 18:43:35 +00:00
|
|
|
.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 */
|
|
|
|
};
|
2022-09-08 00:13:06 +00:00
|
|
|
}
|
2022-09-09 18:43:35 +00:00
|
|
|
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
|
2022-09-08 00:13:06 +00:00
|
|
|
} else {
|
|
|
|
return Err(HarmonicError::NoNssCacert);
|
2022-09-09 18:43:35 +00:00
|
|
|
};
|
2022-09-08 00:13:06 +00:00
|
|
|
if !self.channels.is_empty() {
|
2022-09-09 18:43:35 +00:00
|
|
|
execute_command(
|
|
|
|
Command::new(nix_pkg.join("bin/nix-channel"))
|
2022-09-08 00:13:06 +00:00
|
|
|
.arg("--update")
|
2022-09-09 18:43:35 +00:00
|
|
|
.arg("nixpkgs")
|
|
|
|
.env(
|
|
|
|
"NIX_SSL_CERT_FILE",
|
|
|
|
"/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt",
|
|
|
|
),
|
|
|
|
self.dry_run,
|
2022-09-08 00:13:06 +00:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-08 00:13:06 +00:00
|
|
|
pub async fn place_nix_configuration(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:39:16 +00:00
|
|
|
tracing::info!("Placing nix configuration");
|
2022-09-08 00:13:06 +00:00
|
|
|
let buf = format!(
|
|
|
|
"\
|
|
|
|
{extra_conf}\n\
|
|
|
|
build-users-group = {build_group_name}\n\
|
|
|
|
",
|
|
|
|
extra_conf = "", // TODO(@Hoverbear): populate me
|
|
|
|
build_group_name = self.nix_build_group_name,
|
|
|
|
);
|
2022-09-09 18:43:35 +00:00
|
|
|
create_file_if_not_exists("/etc/nix/nix.conf", buf, self.dry_run).await
|
2022-09-08 00:13:06 +00:00
|
|
|
}
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all)]
|
2022-09-08 00:13:06 +00:00
|
|
|
pub async fn configure_nix_daemon_service(&self) -> Result<(), HarmonicError> {
|
2022-09-12 17:39:16 +00:00
|
|
|
tracing::info!("Configuring nix daemon service");
|
2022-09-08 00:13:06 +00:00
|
|
|
if Path::new("/run/systemd/system").exists() {
|
|
|
|
const SERVICE_SRC: &str =
|
|
|
|
"/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.service";
|
|
|
|
|
|
|
|
const SOCKET_SRC: &str =
|
|
|
|
"/nix/var/nix/profiles/default/lib/systemd/system/nix-daemon.socket";
|
|
|
|
|
|
|
|
const TMPFILES_SRC: &str =
|
|
|
|
"/nix/var/nix/profiles/default//lib/tmpfiles.d/nix-daemon.conf";
|
|
|
|
const TMPFILES_DEST: &str = "/etc/tmpfiles.d/nix-daemon.conf";
|
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
symlink(TMPFILES_SRC, TMPFILES_DEST, self.dry_run).await?;
|
|
|
|
execute_command(
|
2022-09-08 00:13:06 +00:00
|
|
|
Command::new("systemd-tmpfiles")
|
|
|
|
.arg("--create")
|
|
|
|
.arg("--prefix=/nix/var/nix"),
|
2022-09-09 18:43:35 +00:00
|
|
|
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,
|
2022-09-08 00:13:06 +00:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
// TODO(@Hoverbear): Handle proxy vars
|
2022-09-09 18:43:35 +00:00
|
|
|
execute_command(Command::new("systemctl").arg("daemon-reload"), self.dry_run).await?;
|
|
|
|
execute_command(
|
2022-09-08 00:13:06 +00:00
|
|
|
Command::new("systemctl")
|
|
|
|
.arg("start")
|
|
|
|
.arg("nix-daemon.socket"),
|
2022-09-09 18:43:35 +00:00
|
|
|
self.dry_run,
|
2022-09-08 00:13:06 +00:00
|
|
|
)
|
|
|
|
.await?;
|
2022-09-09 18:43:35 +00:00
|
|
|
execute_command(
|
2022-09-08 00:13:06 +00:00
|
|
|
Command::new("systemctl")
|
|
|
|
.arg("restart")
|
|
|
|
.arg("nix-daemon.service"),
|
2022-09-09 18:43:35 +00:00
|
|
|
self.dry_run,
|
2022-09-08 00:13:06 +00:00
|
|
|
)
|
|
|
|
.await?;
|
|
|
|
} else {
|
|
|
|
return Err(HarmonicError::InitNotSupported);
|
|
|
|
}
|
2022-09-02 23:03:57 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Harmonic {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2022-09-09 18:43:35 +00:00
|
|
|
dry_run: true,
|
|
|
|
channels: vec![(
|
|
|
|
"nixpkgs".to_string(),
|
|
|
|
"https://nixos.org/channels/nixpkgs-unstable"
|
|
|
|
.parse::<Url>()
|
|
|
|
.unwrap(),
|
|
|
|
)],
|
2022-09-02 23:03:57 +00:00
|
|
|
daemon_user_count: 32,
|
|
|
|
modify_profile: true,
|
2022-09-06 19:48:37 +00:00
|
|
|
nix_build_group_name: String::from("nixbld"),
|
|
|
|
nix_build_group_id: 30000,
|
|
|
|
nix_build_user_prefix: String::from("nixbld"),
|
|
|
|
nix_build_user_id_base: 30000,
|
2022-09-02 23:03:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-06 19:48:37 +00:00
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[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: 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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Setting permissions");
|
2022-09-09 18:43:35 +00:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Creating directory");
|
2022-09-09 18:43:35 +00:00
|
|
|
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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Executing");
|
2022-09-09 18:43:35 +00:00
|
|
|
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(
|
2022-09-06 19:48:37 +00:00
|
|
|
path: impl AsRef<Path>,
|
2022-09-09 18:43:35 +00:00
|
|
|
buf: impl AsRef<str>,
|
|
|
|
dry_run: bool,
|
|
|
|
) -> Result<(), HarmonicError> {
|
|
|
|
use tokio::fs::{create_dir_all, OpenOptions};
|
2022-09-06 19:48:37 +00:00
|
|
|
let path = path.as_ref();
|
2022-09-09 18:43:35 +00:00
|
|
|
let buf = buf.as_ref();
|
|
|
|
if !dry_run {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Creating or appending");
|
2022-09-09 18:43:35 +00:00
|
|
|
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");
|
|
|
|
}
|
2022-09-06 19:48:37 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2022-09-08 00:13:06 +00:00
|
|
|
|
2022-09-09 18:43:35 +00:00
|
|
|
#[tracing::instrument(skip_all, fields(
|
|
|
|
path = %path.as_ref().display(),
|
2022-09-09 18:50:08 +00:00
|
|
|
buf = %format!("```{}```", buf.as_ref()),
|
2022-09-09 18:43:35 +00:00
|
|
|
))]
|
|
|
|
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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Creating if not exists");
|
2022-09-09 18:43:35 +00:00
|
|
|
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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Symlinking");
|
2022-09-09 18:43:35 +00:00
|
|
|
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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Renaming");
|
2022-09-09 18:43:35 +00:00
|
|
|
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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Fetching url");
|
2022-09-09 18:43:35 +00:00
|
|
|
let res = reqwest::get(url).await.map_err(HarmonicError::Reqwest)?;
|
|
|
|
let bytes = res.bytes().await.map_err(HarmonicError::Reqwest)?;
|
|
|
|
// TODO(@Hoverbear): Pick directory
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Unpacking tar.xz");
|
2022-09-09 18:43:35 +00:00
|
|
|
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 {
|
2022-09-09 18:50:08 +00:00
|
|
|
tracing::trace!("Setting env");
|
2022-09-09 18:43:35 +00:00
|
|
|
std::env::set_var(k.as_ref(), v.as_ref());
|
|
|
|
} else {
|
|
|
|
tracing::info!("Dry run: Would set env");
|
2022-09-08 00:13:06 +00:00
|
|
|
}
|
|
|
|
}
|