forked from lix-project/lix-installer
Get plan/execute working
This commit is contained in:
parent
b04d26abf1
commit
0e843d3e5d
13 changed files with 335 additions and 73 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -621,6 +621,7 @@ dependencies = [
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tar",
|
"tar",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tempdir",
|
"tempdir",
|
||||||
|
|
|
@ -33,3 +33,4 @@ nix = { version = "0.25.0", features = ["user", "fs"], default-features = false
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
serde = { version = "1.0.144", features = ["derive"] }
|
serde = { version = "1.0.144", features = ["derive"] }
|
||||||
url = { version = "2.3.1", features = ["serde"] }
|
url = { version = "2.3.1", features = ["serde"] }
|
||||||
|
serde_json = "1.0.85"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{settings::InstallSettings, HarmonicError};
|
use crate::{settings::InstallSettings, HarmonicError};
|
||||||
|
|
||||||
use super::{Actionable, ActionReceipt, Revertable};
|
use super::{Actionable, ActionReceipt, Revertable, ActionDescription};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
@ -23,8 +23,17 @@ impl CreateUser {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateUser {
|
impl<'a> Actionable<'a> for CreateUser {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
todo!()
|
let name = &self.name;
|
||||||
|
let uid = &self.uid;
|
||||||
|
vec![
|
||||||
|
ActionDescription::new(
|
||||||
|
format!("Create user {name} with UID {uid}"),
|
||||||
|
vec![
|
||||||
|
format!("The nix daemon requires system users it can act as in order to build"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||||
|
@ -36,7 +45,7 @@ impl<'a> Actionable<'a> for CreateUser {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Revertable<'a> for CreateUserReceipt {
|
impl<'a> Revertable<'a> for CreateUserReceipt {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,14 @@ use tokio::task::JoinSet;
|
||||||
|
|
||||||
use crate::{settings::InstallSettings, HarmonicError};
|
use crate::{settings::InstallSettings, HarmonicError};
|
||||||
|
|
||||||
use super::{Actionable, CreateUser, ActionReceipt, create_user::CreateUserReceipt, Revertable};
|
use super::{Actionable, CreateUser, ActionReceipt, create_user::CreateUserReceipt, Revertable, ActionDescription};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct CreateUsers {
|
pub struct CreateUsers {
|
||||||
|
nix_build_user_prefix: String,
|
||||||
|
nix_build_user_id_base: usize,
|
||||||
|
daemon_user_count: usize,
|
||||||
children: Vec<CreateUser>,
|
children: Vec<CreateUser>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,24 +19,35 @@ pub struct CreateUsersReceipt {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateUsers {
|
impl CreateUsers {
|
||||||
pub fn plan(nix_build_user_prefix: &String, nix_build_user_id_base: usize, daemon_user_count: usize) -> Self {
|
pub fn plan(nix_build_user_prefix: String, nix_build_user_id_base: usize, daemon_user_count: usize) -> Self {
|
||||||
let children = (0..daemon_user_count).map(|count| CreateUser::plan(
|
let children = (0..daemon_user_count).map(|count| CreateUser::plan(
|
||||||
format!("{nix_build_user_prefix}{count}"),
|
format!("{nix_build_user_prefix}{count}"),
|
||||||
nix_build_user_id_base + count
|
nix_build_user_id_base + count
|
||||||
)).collect();
|
)).collect();
|
||||||
Self { children }
|
Self { nix_build_user_prefix, nix_build_user_id_base, daemon_user_count, children }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for CreateUsers {
|
impl<'a> Actionable<'a> for CreateUsers {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
todo!()
|
let nix_build_user_prefix = &self.nix_build_user_prefix;
|
||||||
|
let nix_build_user_id_base = &self.nix_build_user_id_base;
|
||||||
|
let daemon_user_count = &self.daemon_user_count;
|
||||||
|
vec![
|
||||||
|
ActionDescription::new(
|
||||||
|
format!("Create build users"),
|
||||||
|
vec![
|
||||||
|
format!("The nix daemon requires system users it can act as in order to build"),
|
||||||
|
format!("This action will create {daemon_user_count} users with prefix `{nix_build_user_prefix}` starting at uid `{nix_build_user_id_base}`"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||||
// TODO(@hoverbear): Abstract this, it will be common
|
// TODO(@hoverbear): Abstract this, it will be common
|
||||||
let Self { children } = self;
|
let Self { children, .. } = self;
|
||||||
let mut set = JoinSet::new();
|
let mut set = JoinSet::new();
|
||||||
let mut successes = Vec::with_capacity(children.len());
|
let mut successes = Vec::with_capacity(children.len());
|
||||||
let mut errors = Vec::default();
|
let mut errors = Vec::default();
|
||||||
|
@ -78,7 +92,7 @@ impl<'a> Actionable<'a> for CreateUsers {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Revertable<'a> for CreateUsersReceipt {
|
impl<'a> Revertable<'a> for CreateUsersReceipt {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,19 @@ pub use create_users::{CreateUsers, CreateUsersReceipt};
|
||||||
|
|
||||||
use crate::{HarmonicError, settings::InstallSettings};
|
use crate::{HarmonicError, settings::InstallSettings};
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
|
||||||
|
pub struct ActionDescription {
|
||||||
|
pub description: String,
|
||||||
|
pub explanation: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActionDescription {
|
||||||
|
fn new(description: String, explanation: Vec<String>) -> Self {
|
||||||
|
Self { description, explanation }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
CreateUsers(CreateUsers),
|
CreateUsers(CreateUsers),
|
||||||
|
@ -24,7 +37,7 @@ pub enum ActionReceipt {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for Action {
|
impl<'a> Actionable<'a> for Action {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
match self {
|
match self {
|
||||||
Action::StartNixDaemonService(i) => i.description(),
|
Action::StartNixDaemonService(i) => i.description(),
|
||||||
Action::CreateUser(i) => i.description(),
|
Action::CreateUser(i) => i.description(),
|
||||||
|
@ -43,7 +56,7 @@ impl<'a> Actionable<'a> for Action {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Revertable<'a> for ActionReceipt {
|
impl<'a> Revertable<'a> for ActionReceipt {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
match self {
|
match self {
|
||||||
ActionReceipt::StartNixDaemonService(i) => i.description(),
|
ActionReceipt::StartNixDaemonService(i) => i.description(),
|
||||||
ActionReceipt::CreateUser(i) => i.description(),
|
ActionReceipt::CreateUser(i) => i.description(),
|
||||||
|
@ -62,12 +75,12 @@ impl<'a> Revertable<'a> for ActionReceipt {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
pub trait Actionable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
||||||
fn description(&self) -> String;
|
fn description(&self) -> Vec<ActionDescription>;
|
||||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError>;
|
async fn execute(self) -> Result<ActionReceipt, HarmonicError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub trait Revertable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
pub trait Revertable<'a>: serde::de::Deserialize<'a> + serde::Serialize {
|
||||||
fn description(&self) -> String;
|
fn description(&self) -> Vec<ActionDescription>;
|
||||||
async fn revert(self) -> Result<(), HarmonicError>;
|
async fn revert(self) -> Result<(), HarmonicError>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
use reqwest::redirect::Action;
|
||||||
|
|
||||||
use crate::{settings::InstallSettings, HarmonicError};
|
use crate::{settings::InstallSettings, HarmonicError};
|
||||||
|
|
||||||
use super::{Actionable, ActionReceipt, Revertable};
|
use super::{Actionable, ActionReceipt, Revertable, ActionDescription};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
|
@ -21,8 +23,15 @@ impl StartNixDaemonService {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Actionable<'a> for StartNixDaemonService {
|
impl<'a> Actionable<'a> for StartNixDaemonService {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
todo!()
|
vec![
|
||||||
|
ActionDescription::new(
|
||||||
|
"Start the systemd Nix daemon".to_string(),
|
||||||
|
vec![
|
||||||
|
"The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string()
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
async fn execute(self) -> Result<ActionReceipt, HarmonicError> {
|
||||||
|
@ -33,7 +42,7 @@ impl<'a> Actionable<'a> for StartNixDaemonService {
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl<'a> Revertable<'a> for StartNixDaemonServiceReceipt {
|
impl<'a> Revertable<'a> for StartNixDaemonServiceReceipt {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> Vec<ActionDescription> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
131
src/cli/mod.rs
131
src/cli/mod.rs
|
@ -1,10 +1,14 @@
|
||||||
pub(crate) mod arg;
|
pub(crate) mod arg;
|
||||||
|
pub(crate) mod subcommand;
|
||||||
|
|
||||||
use crate::{cli::arg::ChannelValue, interaction};
|
use crate::{cli::arg::ChannelValue, interaction};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use harmonic::Harmonic;
|
use harmonic::{Harmonic, InstallPlan, InstallSettings};
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use self::subcommand::HarmonicSubcommand;
|
||||||
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub(crate) trait CommandExecute {
|
pub(crate) trait CommandExecute {
|
||||||
async fn execute(self) -> eyre::Result<ExitCode>;
|
async fn execute(self) -> eyre::Result<ExitCode>;
|
||||||
|
@ -14,14 +18,6 @@ pub(crate) trait CommandExecute {
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[clap(version)]
|
#[clap(version)]
|
||||||
pub(crate) struct HarmonicCli {
|
pub(crate) struct HarmonicCli {
|
||||||
// Don't actually install, just log expected actions
|
|
||||||
#[clap(
|
|
||||||
long,
|
|
||||||
action(ArgAction::SetTrue),
|
|
||||||
default_value = "false",
|
|
||||||
global = true
|
|
||||||
)]
|
|
||||||
pub(crate) dry_run: bool,
|
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) instrumentation: arg::Instrumentation,
|
pub(crate) instrumentation: arg::Instrumentation,
|
||||||
/// Channel(s) to add by default, pass multiple times for multiple channels
|
/// Channel(s) to add by default, pass multiple times for multiple channels
|
||||||
|
@ -34,11 +30,25 @@ pub(crate) struct HarmonicCli {
|
||||||
)]
|
)]
|
||||||
pub(crate) channel: Vec<arg::ChannelValue>,
|
pub(crate) channel: Vec<arg::ChannelValue>,
|
||||||
/// Don't modify the user profile to automatically load nix
|
/// Don't modify the user profile to automatically load nix
|
||||||
#[clap(long)]
|
#[clap(
|
||||||
|
long,
|
||||||
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
global = true
|
||||||
|
)]
|
||||||
pub(crate) no_modify_profile: bool,
|
pub(crate) no_modify_profile: bool,
|
||||||
/// Number of build users to create
|
/// Number of build users to create
|
||||||
#[clap(long, default_value = "32", env = "HARMONIC_NIX_DAEMON_USER_COUNT")]
|
#[clap(long, default_value = "32", env = "HARMONIC_NIX_DAEMON_USER_COUNT")]
|
||||||
pub(crate) daemon_user_count: usize,
|
pub(crate) daemon_user_count: usize,
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
global = true
|
||||||
|
)]
|
||||||
|
pub(crate) explain: bool,
|
||||||
|
#[clap(subcommand)]
|
||||||
|
subcommand: Option<HarmonicSubcommand>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -47,59 +57,100 @@ impl CommandExecute for HarmonicCli {
|
||||||
channels = %self.channel.iter().map(|ChannelValue(name, url)| format!("{name} {url}")).collect::<Vec<_>>().join(", "),
|
channels = %self.channel.iter().map(|ChannelValue(name, url)| format!("{name} {url}")).collect::<Vec<_>>().join(", "),
|
||||||
daemon_user_count = %self.daemon_user_count,
|
daemon_user_count = %self.daemon_user_count,
|
||||||
no_modify_profile = %self.no_modify_profile,
|
no_modify_profile = %self.no_modify_profile,
|
||||||
dry_run = %self.dry_run,
|
explain = %self.explain,
|
||||||
))]
|
))]
|
||||||
async fn execute(self) -> eyre::Result<ExitCode> {
|
async fn execute(self) -> eyre::Result<ExitCode> {
|
||||||
let Self {
|
let Self {
|
||||||
dry_run,
|
|
||||||
instrumentation: _,
|
instrumentation: _,
|
||||||
daemon_user_count,
|
daemon_user_count,
|
||||||
channel,
|
channel,
|
||||||
no_modify_profile,
|
no_modify_profile,
|
||||||
|
explain,
|
||||||
|
subcommand,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut harmonic = Harmonic::default();
|
match subcommand {
|
||||||
|
Some(HarmonicSubcommand::Plan(plan)) => {
|
||||||
|
return plan.execute().await
|
||||||
|
},
|
||||||
|
Some(HarmonicSubcommand::Execute(execute)) => {
|
||||||
|
return execute.execute().await
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
|
||||||
harmonic.dry_run(dry_run);
|
let mut settings = InstallSettings::default();
|
||||||
harmonic.daemon_user_count(daemon_user_count);
|
|
||||||
harmonic.channels(
|
settings.explain(explain);
|
||||||
|
settings.daemon_user_count(daemon_user_count);
|
||||||
|
settings.nix_build_group_name("nixbld".to_string());
|
||||||
|
settings.nix_build_group_id(30000);
|
||||||
|
settings.nix_build_user_prefix("nixbld".to_string());
|
||||||
|
settings.nix_build_user_id_base(30001);
|
||||||
|
settings.channels(
|
||||||
channel
|
channel
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|ChannelValue(name, url)| (name, url)),
|
.map(|ChannelValue(name, url)| (name, url)),
|
||||||
);
|
);
|
||||||
harmonic.modify_profile(!no_modify_profile);
|
settings.modify_profile(!no_modify_profile);
|
||||||
|
|
||||||
|
let plan = InstallPlan::new(settings).await?;
|
||||||
|
|
||||||
|
|
||||||
// TODO(@Hoverbear): Make this smarter
|
// TODO(@Hoverbear): Make this smarter
|
||||||
if !interaction::confirm(
|
if !interaction::confirm(
|
||||||
"\
|
plan.description()
|
||||||
Ready to install nix?\n\
|
|
||||||
\n\
|
|
||||||
This installer will:\n\
|
|
||||||
\n\
|
|
||||||
* Create a `nixbld` group\n\
|
|
||||||
* Create several `nixbld*` users\n\
|
|
||||||
* Create several Nix related directories\n\
|
|
||||||
* Place channel configurations\n\
|
|
||||||
* Fetch a copy of Nix and unpack it\n\
|
|
||||||
* Configure the shell profiles of various shells\n\
|
|
||||||
* Place a Nix configuration\n\
|
|
||||||
* Configure the Nix daemon to work with your init\
|
|
||||||
",
|
|
||||||
)
|
)
|
||||||
.await?
|
.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
harmonic.create_group().await?;
|
// let mut harmonic = Harmonic::default();
|
||||||
harmonic.create_users().await?;
|
|
||||||
harmonic.create_directories().await?;
|
// harmonic.dry_run(dry_run);
|
||||||
harmonic.place_channel_configuration().await?;
|
// harmonic.explain(explain);
|
||||||
harmonic.fetch_nix().await?;
|
// harmonic.daemon_user_count(daemon_user_count);
|
||||||
harmonic.configure_shell_profile().await?;
|
// harmonic.channels(
|
||||||
harmonic.setup_default_profile().await?;
|
// channel
|
||||||
harmonic.place_nix_configuration().await?;
|
// .into_iter()
|
||||||
harmonic.configure_nix_daemon_service().await?;
|
// .map(|ChannelValue(name, url)| (name, url)),
|
||||||
|
// );
|
||||||
|
// harmonic.modify_profile(!no_modify_profile);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// // TODO(@Hoverbear): Make this smarter
|
||||||
|
// if !interaction::confirm(
|
||||||
|
// "\
|
||||||
|
// Ready to install nix?\n\
|
||||||
|
// \n\
|
||||||
|
// This installer will:\n\
|
||||||
|
// \n\
|
||||||
|
// * Create a `nixbld` group\n\
|
||||||
|
// * Create several `nixbld*` users\n\
|
||||||
|
// * Create several Nix related directories\n\
|
||||||
|
// * Place channel configurations\n\
|
||||||
|
// * Fetch a copy of Nix and unpack it\n\
|
||||||
|
// * Configure the shell profiles of various shells\n\
|
||||||
|
// * Place a Nix configuration\n\
|
||||||
|
// * Configure the Nix daemon to work with your init\
|
||||||
|
// ",
|
||||||
|
// )
|
||||||
|
// .await?
|
||||||
|
// {
|
||||||
|
// interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// harmonic.create_group().await?;
|
||||||
|
// harmonic.create_users().await?;
|
||||||
|
// harmonic.create_directories().await?;
|
||||||
|
// harmonic.place_channel_configuration().await?;
|
||||||
|
// harmonic.fetch_nix().await?;
|
||||||
|
// harmonic.configure_shell_profile().await?;
|
||||||
|
// harmonic.setup_default_profile().await?;
|
||||||
|
// harmonic.place_nix_configuration().await?;
|
||||||
|
// harmonic.configure_nix_daemon_service().await?;
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
}
|
}
|
||||||
|
|
48
src/cli/subcommand/execute.rs
Normal file
48
src/cli/subcommand/execute.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use clap::{Parser, ArgAction};
|
||||||
|
use harmonic::{InstallSettings, InstallPlan};
|
||||||
|
use tokio::io::{AsyncWriteExt, AsyncReadExt};
|
||||||
|
|
||||||
|
use crate::{cli::{arg::ChannelValue, CommandExecute}, interaction};
|
||||||
|
|
||||||
|
/// An opinionated, experimental Nix installer
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct Execute {
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
global = true
|
||||||
|
)]
|
||||||
|
no_confirm: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl CommandExecute for Execute {
|
||||||
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
|
||||||
|
))]
|
||||||
|
async fn execute(self) -> eyre::Result<ExitCode> {
|
||||||
|
let Self { no_confirm } = self;
|
||||||
|
|
||||||
|
let mut stdin = tokio::io::stdin();
|
||||||
|
let mut json = String::default();
|
||||||
|
stdin.read_to_string(&mut json).await?;
|
||||||
|
let plan: InstallPlan = serde_json::from_str(&json)?;
|
||||||
|
|
||||||
|
if !no_confirm {
|
||||||
|
if !interaction::confirm(
|
||||||
|
plan.description()
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
|
}
|
12
src/cli/subcommand/mod.rs
Normal file
12
src/cli/subcommand/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
mod plan;
|
||||||
|
use plan::Plan;
|
||||||
|
mod execute;
|
||||||
|
use execute::Execute;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, clap::Subcommand)]
|
||||||
|
pub(crate) enum HarmonicSubcommand {
|
||||||
|
Plan(Plan),
|
||||||
|
Execute(Execute),
|
||||||
|
}
|
||||||
|
|
65
src/cli/subcommand/plan.rs
Normal file
65
src/cli/subcommand/plan.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
use std::process::ExitCode;
|
||||||
|
|
||||||
|
use clap::{Parser, ArgAction};
|
||||||
|
use harmonic::{InstallSettings, InstallPlan};
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
use crate::cli::{arg::ChannelValue, CommandExecute};
|
||||||
|
|
||||||
|
/// An opinionated, experimental Nix installer
|
||||||
|
#[derive(Debug, Parser)]
|
||||||
|
pub(crate) struct Plan {
|
||||||
|
/// Channel(s) to add by default, pass multiple times for multiple channels
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
value_parser,
|
||||||
|
action = clap::ArgAction::Append,
|
||||||
|
env = "HARMONIC_CHANNEL",
|
||||||
|
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable"
|
||||||
|
)]
|
||||||
|
pub(crate) channel: Vec<crate::cli::arg::ChannelValue>,
|
||||||
|
/// Don't modify the user profile to automatically load nix
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
global = true
|
||||||
|
)]
|
||||||
|
pub(crate) no_modify_profile: bool,
|
||||||
|
/// Number of build users to create
|
||||||
|
#[clap(long, default_value = "32", env = "HARMONIC_NIX_DAEMON_USER_COUNT")]
|
||||||
|
pub(crate) daemon_user_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl CommandExecute for Plan {
|
||||||
|
#[tracing::instrument(skip_all, fields(
|
||||||
|
channels = %self.channel.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,
|
||||||
|
))]
|
||||||
|
async fn execute(self) -> eyre::Result<ExitCode> {
|
||||||
|
let Self { channel, no_modify_profile, daemon_user_count } = self;
|
||||||
|
|
||||||
|
let mut settings = InstallSettings::default();
|
||||||
|
|
||||||
|
settings.daemon_user_count(daemon_user_count);
|
||||||
|
settings.nix_build_group_name("nixbld".to_string());
|
||||||
|
settings.nix_build_group_id(30000);
|
||||||
|
settings.nix_build_user_prefix("nixbld".to_string());
|
||||||
|
settings.nix_build_user_id_base(30001);
|
||||||
|
settings.channels(
|
||||||
|
channel
|
||||||
|
.into_iter()
|
||||||
|
.map(|ChannelValue(name, url)| (name, url)),
|
||||||
|
);
|
||||||
|
settings.modify_profile(!no_modify_profile);
|
||||||
|
|
||||||
|
let plan = InstallPlan::new(settings).await?;
|
||||||
|
|
||||||
|
let json = serde_json::to_string_pretty(&plan)?;
|
||||||
|
let mut stdout = tokio::io::stdout();
|
||||||
|
stdout.write_all(json.as_bytes()).await?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,8 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use error::HarmonicError;
|
pub use error::HarmonicError;
|
||||||
|
pub use plan::InstallPlan;
|
||||||
|
pub use settings::InstallSettings;
|
||||||
|
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
|
|
42
src/plan.rs
42
src/plan.rs
|
@ -1,11 +1,11 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{settings::InstallSettings, actions::{Action, StartNixDaemonService, Actionable, ActionReceipt, Revertable}, HarmonicError};
|
use crate::{settings::InstallSettings, actions::{Action, StartNixDaemonService, Actionable, ActionReceipt, Revertable, CreateUsers, ActionDescription}, HarmonicError};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
struct InstallPlan {
|
pub struct InstallPlan {
|
||||||
settings: InstallSettings,
|
settings: InstallSettings,
|
||||||
|
|
||||||
/** Bootstrap the install
|
/** Bootstrap the install
|
||||||
|
@ -26,15 +26,47 @@ struct InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstallPlan {
|
impl InstallPlan {
|
||||||
async fn plan(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
pub fn description(&self) -> String {
|
||||||
|
format!("\
|
||||||
|
This Nix install is for:\n\
|
||||||
|
Operating System: {os_type}\n\
|
||||||
|
Init system: {init_type}\n\
|
||||||
|
Nix channels: {nix_channels}\n\
|
||||||
|
\n\
|
||||||
|
The following actions will be taken:\n\
|
||||||
|
{actions}
|
||||||
|
",
|
||||||
|
os_type = "Linux",
|
||||||
|
init_type = "systemd",
|
||||||
|
nix_channels = self.settings.channels.iter().map(|(name,url)| format!("{name}={url}")).collect::<Vec<_>>().join(","),
|
||||||
|
actions = self.actions.iter().flat_map(|action| action.description()).map(|desc| {
|
||||||
|
let ActionDescription {
|
||||||
|
description,
|
||||||
|
explanation,
|
||||||
|
} = desc;
|
||||||
|
|
||||||
|
let mut buf = String::default();
|
||||||
|
buf.push_str(&format!("* {description}\n"));
|
||||||
|
if self.settings.explain {
|
||||||
|
for line in explanation {
|
||||||
|
buf.push_str(&format!(" {line}\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf
|
||||||
|
}).collect::<Vec<_>>().join("\n"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
pub async fn new(settings: InstallSettings) -> Result<Self, HarmonicError> {
|
||||||
let start_nix_daemon_service = StartNixDaemonService::plan();
|
let start_nix_daemon_service = StartNixDaemonService::plan();
|
||||||
|
let create_users = CreateUsers::plan(settings.nix_build_user_prefix.clone(), settings.nix_build_user_id_base, settings.daemon_user_count);
|
||||||
|
|
||||||
let actions = vec![
|
let actions = vec![
|
||||||
|
Action::CreateUsers(create_users),
|
||||||
Action::StartNixDaemonService(start_nix_daemon_service),
|
Action::StartNixDaemonService(start_nix_daemon_service),
|
||||||
];
|
];
|
||||||
Ok(Self { settings, actions })
|
Ok(Self { settings, actions })
|
||||||
}
|
}
|
||||||
async fn install(self) -> Result<Receipt, HarmonicError> {
|
pub async fn install(self) -> Result<Receipt, HarmonicError> {
|
||||||
let mut receipt = Receipt::default();
|
let mut receipt = Receipt::default();
|
||||||
// This is **deliberately sequential**.
|
// This is **deliberately sequential**.
|
||||||
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
// Actions which are parallelizable are represented by "group actions" like CreateUsers
|
||||||
|
@ -64,6 +96,6 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
struct Receipt {
|
pub struct Receipt {
|
||||||
actions: Vec<ActionReceipt>,
|
actions: Vec<ActionReceipt>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, Default)]
|
||||||
pub struct InstallSettings {
|
pub struct InstallSettings {
|
||||||
dry_run: bool,
|
pub(crate) dry_run: bool,
|
||||||
daemon_user_count: usize,
|
pub(crate) explain: bool,
|
||||||
channels: Vec<(String, Url)>,
|
pub(crate) daemon_user_count: usize,
|
||||||
modify_profile: bool,
|
pub(crate) channels: Vec<(String, Url)>,
|
||||||
nix_build_group_name: String,
|
pub(crate) modify_profile: bool,
|
||||||
nix_build_group_id: usize,
|
pub(crate) nix_build_group_name: String,
|
||||||
nix_build_user_prefix: String,
|
pub(crate) nix_build_group_id: usize,
|
||||||
nix_build_user_id_base: usize,
|
pub(crate) nix_build_user_prefix: String,
|
||||||
|
pub(crate) nix_build_user_id_base: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder Pattern
|
// Builder Pattern
|
||||||
impl InstallSettings {
|
impl InstallSettings {
|
||||||
|
pub fn explain(&mut self, explain: bool) -> &mut Self {
|
||||||
|
self.explain = explain;
|
||||||
|
self
|
||||||
|
}
|
||||||
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
|
pub fn dry_run(&mut self, dry_run: bool) -> &mut Self {
|
||||||
self.dry_run = dry_run;
|
self.dry_run = dry_run;
|
||||||
self
|
self
|
||||||
|
|
Loading…
Reference in a new issue