Rework planners to support custom args

This commit is contained in:
Ana Hobden 2022-10-25 11:57:09 -07:00
parent b920b384d3
commit 16acc1fe6c
28 changed files with 289 additions and 325 deletions

View file

@ -69,13 +69,13 @@ impl Actionable for CreateDirectory {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
path,
user,
group,
mode,
user: _,
group: _,
mode: _,
force_prune_on_revert: _,
action_state: _,
action_state,
} = &self;
if self.action_state == ActionState::Completed {
if *action_state == ActionState::Completed {
vec![]
} else {
vec![ActionDescription::new(

View file

@ -56,10 +56,10 @@ impl Actionable for CreateFile {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
path,
user,
group,
mode,
buf,
user: _,
group: _,
mode: _,
buf: _,
force: _,
action_state: _,
} = &self;

View file

@ -53,10 +53,10 @@ impl Actionable for CreateOrAppendFile {
fn describe_execute(&self) -> Vec<ActionDescription> {
let Self {
path,
user,
group,
mode,
buf,
user: _,
group: _,
mode: _,
buf: _,
action_state: _,
} = &self;
if self.action_state == ActionState::Completed {

View file

@ -110,7 +110,7 @@ impl Actionable for CreateVolume {
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
disk,
disk: _,
name,
case_sensitive: _,
action_state,

View file

@ -1,8 +1,5 @@
use serde::Serialize;
use std::path::{Path, PathBuf};
use tokio::process::Command;
use crate::execute_command;
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
@ -47,8 +44,8 @@ impl Actionable for EncryptVolume {
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
disk,
password,
disk: _,
password: _,
action_state,
} = self;
if *action_state == ActionState::Completed {
@ -77,8 +74,8 @@ impl Actionable for EncryptVolume {
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
disk,
password,
disk: _,
password: _,
action_state,
} = self;
if *action_state == ActionState::Uncompleted {

View file

@ -49,7 +49,7 @@ impl Actionable for UnmountVolume {
))]
async fn execute(&mut self) -> Result<(), Self::Error> {
let Self {
disk,
disk: _,
name,
action_state,
} = self;
@ -91,7 +91,7 @@ impl Actionable for UnmountVolume {
))]
async fn revert(&mut self) -> Result<(), Self::Error> {
let Self {
disk,
disk: _,
name,
action_state,
} = self;

View file

@ -1,17 +1,21 @@
use reqwest::Url;
use serde::Serialize;
use crate::actions::{
base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, SetupDefaultProfile,
SetupDefaultProfileError,
},
meta::{
ConfigureShellProfile, ConfigureShellProfileError, PlaceChannelConfiguration,
PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError,
},
Action, ActionState,
};
use crate::InstallSettings;
use crate::{
actions::{
base::{
ConfigureNixDaemonService, ConfigureNixDaemonServiceError, SetupDefaultProfile,
SetupDefaultProfileError,
},
meta::{
ConfigureShellProfile, ConfigureShellProfileError, PlaceChannelConfiguration,
PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError,
},
Action, ActionState,
},
cli::arg::ChannelValue,
};
use crate::actions::{ActionDescription, Actionable};
@ -28,13 +32,14 @@ pub struct ConfigureNix {
impl ConfigureNix {
#[tracing::instrument(skip_all)]
pub async fn plan(settings: InstallSettings) -> Result<Self, ConfigureNixError> {
let channels = settings
let channels: Vec<(String, Url)> = settings
.channels
.iter()
.map(|(channel, _)| channel.to_string())
.map(|ChannelValue(channel, url)| (channel.to_string(), url.clone()))
.collect();
let setup_default_profile = SetupDefaultProfile::plan(channels).await?;
let setup_default_profile =
SetupDefaultProfile::plan(channels.iter().map(|(v, _k)| v.clone()).collect()).await?;
let configure_shell_profile = if settings.modify_profile {
Some(ConfigureShellProfile::plan().await?)
@ -42,7 +47,7 @@ impl ConfigureNix {
None
};
let place_channel_configuration =
PlaceChannelConfiguration::plan(settings.channels, settings.force).await?;
PlaceChannelConfiguration::plan(channels, settings.force).await?;
let place_nix_configuration = PlaceNixConfiguration::plan(
settings.nix_build_group_name,
settings.extra_conf,

View file

@ -5,19 +5,15 @@ use std::{
};
use tokio::process::Command;
use crate::actions::{base::darwin, Action, ActionDescription, ActionState, Actionable};
use crate::{
actions::base::{
darwin::{
BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects,
CreateSyntheticObjectsError, CreateVolume, CreateVolumeError, EnableOwnership,
EnableOwnershipError, EncryptVolume, EncryptVolumeError, UnmountVolume,
UnmountVolumeError,
},
CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError,
use crate::actions::base::{
darwin::{
BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects, CreateSyntheticObjectsError,
CreateVolume, CreateVolumeError, EnableOwnership, EnableOwnershipError, EncryptVolume,
EncryptVolumeError, UnmountVolume, UnmountVolumeError,
},
execute_command,
CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError,
};
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist";

View file

@ -4,8 +4,7 @@ use serde::Serialize;
use tokio::task::JoinError;
use crate::actions::base::{
CreateDirectory, CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix,
MoveUnpackedNixError,
CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError,
};
use crate::InstallSettings;

View file

@ -1,6 +1,7 @@
use reqwest::Url;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChannelValue(pub String, pub Url);
impl clap::builder::ValueParserFactory for ChannelValue {
@ -10,6 +11,12 @@ impl clap::builder::ValueParserFactory for ChannelValue {
}
}
impl From<(String, Url)> for ChannelValue {
fn from((string, url): (String, Url)) -> Self {
Self(string, url)
}
}
#[derive(Clone, Debug)]
pub struct ChannelValueParser;
impl clap::builder::TypedValueParser for ChannelValueParser {

View file

@ -9,11 +9,11 @@ use valuable::Valuable;
pub struct Instrumentation {
/// Enable debug logs, -vv for trace
#[clap(short = 'v', long, action = clap::ArgAction::Count, global = true)]
pub(crate) verbose: u8,
pub verbose: u8,
}
impl<'a> Instrumentation {
pub(crate) fn log_level(&self) -> String {
pub fn log_level(&self) -> String {
match self.verbose {
0 => "info",
1 => "debug",
@ -22,7 +22,7 @@ impl<'a> Instrumentation {
.to_string()
}
pub(crate) fn setup<'b: 'a>(&'b self) -> eyre::Result<()> {
pub fn setup<'b: 'a>(&'b self) -> eyre::Result<()> {
let fmt_layer = self.fmt_layer();
let filter_layer = self.filter_layer()?;
@ -35,7 +35,7 @@ impl<'a> Instrumentation {
Ok(())
}
pub(crate) fn fmt_layer<S>(&self) -> impl tracing_subscriber::layer::Layer<S>
pub fn fmt_layer<S>(&self) -> impl tracing_subscriber::layer::Layer<S>
where
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
{
@ -45,7 +45,7 @@ impl<'a> Instrumentation {
.pretty()
}
pub(crate) fn filter_layer(&self) -> eyre::Result<EnvFilter> {
pub fn filter_layer(&self) -> eyre::Result<EnvFilter> {
let filter_layer = match EnvFilter::try_from_default_env() {
Ok(layer) => layer,
Err(e) => {

View file

@ -2,5 +2,3 @@ mod instrumentation;
pub(crate) use instrumentation::Instrumentation;
mod channel_value;
pub(crate) use channel_value::ChannelValue;
mod plan_options;
pub(crate) use plan_options::PlanOptions;

View file

@ -1,45 +0,0 @@
use clap::{ArgAction, Parser};
use harmonic::Planner;
/// Plan an install that can be repeated on an identical host later
#[derive(Debug, Parser)]
pub(crate) struct PlanOptions {
/// 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",
group = "plan_options"
)]
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,
group = "plan_options"
)]
pub(crate) no_modify_profile: bool,
/// Number of build users to create
#[clap(
long,
default_value = "32",
env = "HARMONIC_NIX_DAEMON_USER_COUNT",
group = "plan_options"
)]
pub(crate) daemon_user_count: usize,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true,
group = "plan_options"
)]
pub(crate) force: bool,
// Override the default planner for this OS/Architecture
#[clap(long, global = true, group = "plan_options", value_parser = clap::builder::EnumValueParser::<Planner>::new())]
pub(crate) planner: Option<Planner>,
}

View file

@ -7,7 +7,7 @@ use std::process::ExitCode;
use self::subcommand::HarmonicSubcommand;
#[async_trait::async_trait]
pub(crate) trait CommandExecute {
pub trait CommandExecute {
async fn execute(self) -> eyre::Result<ExitCode>;
}
@ -16,12 +16,12 @@ pub(crate) trait CommandExecute {
/// Plans a Nix install, prompts for confirmation, then executes it
#[derive(Debug, Parser)]
#[clap(version)]
pub(crate) struct HarmonicCli {
pub struct HarmonicCli {
#[clap(flatten)]
pub(crate) instrumentation: arg::Instrumentation,
pub instrumentation: arg::Instrumentation,
#[clap(subcommand)]
subcommand: HarmonicSubcommand,
pub subcommand: HarmonicSubcommand,
}
#[async_trait::async_trait]

View file

@ -1,41 +1,35 @@
use std::{path::PathBuf, process::ExitCode};
use crate::BuiltinPlanner;
use clap::{ArgAction, Parser};
use eyre::{eyre, WrapErr};
use harmonic::{InstallPlan, InstallSettings, Planner};
use crate::{
cli::{
arg::{ChannelValue, PlanOptions},
CommandExecute,
},
interaction,
};
use crate::{cli::CommandExecute, interaction};
/// Execute an install (possibly using an existing plan)
#[derive(Debug, Parser)]
pub(crate) struct Install {
#[command(args_conflicts_with_subcommands = true)]
pub struct Install {
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
no_confirm: bool,
#[clap(flatten)]
plan_options: PlanOptions,
pub no_confirm: bool,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
pub(crate) explain: bool,
#[clap(
conflicts_with_all = [ "plan_options" ],
env = "HARMONIC_PLAN",
)]
plan: Option<PathBuf>,
pub explain: bool,
#[clap(env = "HARMONIC_PLAN")]
pub plan: Option<PathBuf>,
#[clap(subcommand)]
pub planner: BuiltinPlanner,
}
#[async_trait::async_trait]
@ -45,7 +39,7 @@ impl CommandExecute for Install {
let Self {
no_confirm,
plan,
plan_options,
planner,
explain,
} = self;
@ -56,26 +50,7 @@ impl CommandExecute for Install {
.wrap_err("Reading plan")?;
serde_json::from_str(&install_plan_string)?
},
None => {
let mut settings = InstallSettings::default()?;
settings.force(plan_options.force);
settings.daemon_user_count(plan_options.daemon_user_count);
settings.channels(
plan_options
.channel
.into_iter()
.map(|ChannelValue(name, url)| (name, url)),
);
settings.modify_profile(!plan_options.no_modify_profile);
let planner = match plan_options.planner {
Some(planner) => planner,
None => Planner::default()?,
};
InstallPlan::new(planner, settings).await?
},
None => planner.plan().await?,
};
if !no_confirm {

View file

@ -6,7 +6,7 @@ mod uninstall;
use uninstall::Uninstall;
#[derive(Debug, clap::Subcommand)]
pub(crate) enum HarmonicSubcommand {
pub enum HarmonicSubcommand {
Plan(Plan),
Install(Install),
Uninstall(Uninstall),

View file

@ -1,61 +1,34 @@
use std::{path::PathBuf, process::ExitCode};
use crate::BuiltinPlanner;
use clap::Parser;
use harmonic::{InstallPlan, InstallSettings, Planner};
use eyre::WrapErr;
use crate::cli::{
arg::{ChannelValue, PlanOptions},
CommandExecute,
};
use crate::cli::CommandExecute;
/// Plan an install that can be repeated on an identical host later
#[derive(Debug, Parser)]
pub(crate) struct Plan {
#[clap(flatten)]
plan_options: PlanOptions,
#[clap(default_value = "/dev/stdout")]
pub(crate) plan: PathBuf,
#[command(multicall = true)]
pub struct Plan {
#[clap(subcommand)]
pub planner: Option<BuiltinPlanner>,
#[clap(env = "HARMONIC_PLAN")]
pub plan: PathBuf,
}
#[async_trait::async_trait]
impl CommandExecute for Plan {
#[tracing::instrument(skip_all, fields(
channels = %self.plan_options.channel.iter().map(|ChannelValue(name, url)| format!("{name} {url}")).collect::<Vec<_>>().join(", "),
daemon_user_count = %self.plan_options.daemon_user_count,
no_modify_profile = %self.plan_options.no_modify_profile,
))]
#[tracing::instrument(skip_all, fields())]
async fn execute(self) -> eyre::Result<ExitCode> {
let Self {
plan_options:
PlanOptions {
channel,
no_modify_profile,
daemon_user_count,
force,
planner,
},
plan,
} = self;
let mut settings = InstallSettings::default()?;
settings.force(force);
settings.daemon_user_count(daemon_user_count);
settings.channels(
channel
.into_iter()
.map(|ChannelValue(name, url)| (name, url)),
);
settings.modify_profile(!no_modify_profile);
let Self { planner, plan } = self;
let planner = match planner {
Some(planner) => planner,
None => Planner::default()?,
None => BuiltinPlanner::default()?,
};
let install_plan = InstallPlan::new(planner, settings).await?;
let install_plan = planner.plan().await?;
let json = serde_json::to_string_pretty(&install_plan)?;
tokio::fs::write(plan, json)

View file

@ -1,30 +1,30 @@
use std::{path::PathBuf, process::ExitCode};
use crate::InstallPlan;
use clap::{ArgAction, Parser};
use eyre::WrapErr;
use harmonic::InstallPlan;
use crate::{cli::CommandExecute, interaction};
/// Uninstall a previously installed Nix (only Harmonic done installs supported)
#[derive(Debug, Parser)]
pub(crate) struct Uninstall {
pub struct Uninstall {
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
no_confirm: bool,
pub no_confirm: bool,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true
)]
explain: bool,
pub explain: bool,
#[clap(default_value = "/nix/receipt.json")]
receipt: PathBuf,
pub receipt: PathBuf,
}
#[async_trait::async_trait]

View file

@ -1,19 +1,17 @@
mod actions;
pub mod cli;
mod error;
mod interaction;
mod os;
mod plan;
mod planner;
mod settings;
use std::{
ffi::OsStr,
fmt::Display,
process::{ExitStatus, Output},
};
use std::{ffi::OsStr, fmt::Display, process::Output};
pub use error::HarmonicError;
pub use plan::InstallPlan;
pub use planner::Planner;
pub use planner::BuiltinPlanner;
use serde::Serializer;
pub use settings::InstallSettings;

View file

@ -1,11 +1,7 @@
pub(crate) mod cli;
use std::process::ExitCode;
pub mod interaction;
use clap::Parser;
use cli::CommandExecute;
use harmonic::cli::CommandExecute;
#[tokio::main]
async fn main() -> color_eyre::Result<ExitCode> {
@ -17,7 +13,7 @@ async fn main() -> color_eyre::Result<ExitCode> {
})
.install()?;
let cli = cli::HarmonicCli::parse();
let cli = harmonic::cli::HarmonicCli::parse();
cli.instrumentation.setup()?;

View file

@ -2,32 +2,20 @@ use std::path::PathBuf;
use crate::{
actions::{Action, ActionDescription, ActionError, Actionable},
planner::PlannerError,
settings::InstallSettings,
HarmonicError, Planner,
BuiltinPlanner, HarmonicError,
};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct InstallPlan {
pub(crate) settings: InstallSettings,
pub(crate) actions: Vec<Action>,
pub(crate) planner: Planner,
pub(crate) planner: BuiltinPlanner,
}
impl InstallPlan {
pub async fn new(planner: Planner, settings: InstallSettings) -> Result<Self, PlannerError> {
planner.plan(settings).await
}
#[tracing::instrument(skip_all)]
pub fn describe_execute(&self, explain: bool) -> String {
let Self {
planner,
settings,
actions,
} = self;
let Self { planner, actions } = self;
format!(
"\
This Nix install is for:\n\
@ -42,12 +30,7 @@ impl InstallPlan {
",
os_type = "Linux",
init_type = "systemd",
nix_channels = settings
.channels
.iter()
.map(|(name, url)| format!("{name}={url}"))
.collect::<Vec<_>>()
.join(","),
nix_channels = "todo",
actions = actions
.iter()
.map(|v| v.describe_execute())
@ -76,7 +59,6 @@ impl InstallPlan {
pub async fn install(&mut self) -> Result<(), HarmonicError> {
let Self {
actions,
settings: _,
planner: _,
} = self;
@ -97,11 +79,7 @@ impl InstallPlan {
#[tracing::instrument(skip_all)]
pub fn describe_revert(&self, explain: bool) -> String {
let Self {
planner,
settings,
actions,
} = self;
let Self { planner, actions } = self;
format!(
"\
This Nix uninstall is for:\n\
@ -116,12 +94,7 @@ impl InstallPlan {
",
os_type = "Linux",
init_type = "systemd",
nix_channels = settings
.channels
.iter()
.map(|(name, url)| format!("{name}={url}"))
.collect::<Vec<_>>()
.join(","),
nix_channels = "todo",
actions = actions
.iter()
.map(|v| v.describe_revert())
@ -150,7 +123,6 @@ impl InstallPlan {
pub async fn revert(&mut self) -> Result<(), HarmonicError> {
let Self {
actions,
settings: _,
planner: _,
} = self;

View file

@ -1,3 +1,3 @@
mod multi_user;
mod multi;
pub use multi_user::DarwinMultiUser;
pub use multi::DarwinMulti;

View file

@ -11,20 +11,27 @@ use crate::{
execute_command,
os::darwin::DiskUtilOutput,
planner::{Plannable, PlannerError},
InstallPlan, Planner,
BuiltinPlanner, InstallPlan, InstallSettings,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct DarwinMultiUser;
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
pub struct DarwinMulti {
#[clap(flatten)]
settings: InstallSettings,
}
#[async_trait::async_trait]
impl Plannable for DarwinMultiUser {
impl Plannable for DarwinMulti {
const DISPLAY_STRING: &'static str = "Darwin Multi-User";
const SLUG: &'static str = "darwin-multi";
async fn plan(
settings: crate::InstallSettings,
) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
fn default() -> Result<Self, PlannerError> {
Ok(Self {
settings: InstallSettings::default()?,
})
}
async fn plan(self) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
let root_disk = {
let buf =
execute_command(Command::new("/usr/sbin/diskutil").args(["info", "-plist", "/"]))
@ -39,8 +46,7 @@ impl Plannable for DarwinMultiUser {
let volume_label = "Nix Store".into();
Ok(InstallPlan {
planner: Self.into(),
settings: settings.clone(),
planner: self.clone().into(),
actions: vec![
// Create Volume step:
//
@ -50,11 +56,11 @@ impl Plannable for DarwinMultiUser {
.await
.map(Action::from)
.map_err(ActionError::from)?,
ProvisionNix::plan(settings.clone())
ProvisionNix::plan(self.settings.clone())
.await
.map(Action::from)
.map_err(ActionError::from)?,
ConfigureNix::plan(settings)
ConfigureNix::plan(self.settings)
.await
.map(Action::from)
.map_err(ActionError::from)?,
@ -67,8 +73,8 @@ impl Plannable for DarwinMultiUser {
}
}
impl Into<Planner> for DarwinMultiUser {
fn into(self) -> Planner {
Planner::DarwinMultiUser
impl Into<BuiltinPlanner> for DarwinMulti {
fn into(self) -> BuiltinPlanner {
BuiltinPlanner::DarwinMulti(self)
}
}

View file

@ -1,3 +1,3 @@
mod multi_user;
mod multi;
pub use multi_user::LinuxMultiUser;
pub use multi::LinuxMulti;

View file

@ -5,31 +5,39 @@ use crate::{
Action, ActionError,
},
planner::{Plannable, PlannerError},
InstallPlan, InstallSettings, Planner,
BuiltinPlanner, InstallPlan, InstallSettings,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct LinuxMultiUser;
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
pub struct LinuxMulti {
#[clap(flatten)]
settings: InstallSettings,
}
#[async_trait::async_trait]
impl Plannable for LinuxMultiUser {
impl Plannable for LinuxMulti {
const DISPLAY_STRING: &'static str = "Linux Multi-User";
const SLUG: &'static str = "linux-multi";
async fn plan(settings: InstallSettings) -> Result<InstallPlan, PlannerError> {
fn default() -> Result<Self, PlannerError> {
Ok(Self {
settings: InstallSettings::default()?,
})
}
async fn plan(self) -> Result<InstallPlan, PlannerError> {
Ok(InstallPlan {
planner: Self.into(),
settings: settings.clone(),
planner: self.clone().into(),
actions: vec![
CreateDirectory::plan("/nix", None, None, 0o0755, true)
.await
.map(Action::from)
.map_err(ActionError::from)?,
ProvisionNix::plan(settings.clone())
ProvisionNix::plan(self.settings.clone())
.await
.map(Action::from)
.map_err(ActionError::from)?,
ConfigureNix::plan(settings)
ConfigureNix::plan(self.settings)
.await
.map(Action::from)
.map_err(ActionError::from)?,
@ -42,8 +50,8 @@ impl Plannable for LinuxMultiUser {
}
}
impl Into<Planner> for LinuxMultiUser {
fn into(self) -> Planner {
Planner::LinuxMultiUser
impl Into<BuiltinPlanner> for LinuxMulti {
fn into(self) -> BuiltinPlanner {
BuiltinPlanner::LinuxMulti(self)
}
}

View file

@ -1,51 +1,57 @@
mod darwin;
mod linux;
mod specific;
pub mod darwin;
pub mod linux;
pub mod specific;
use crate::{actions::ActionError, InstallPlan, InstallSettings};
use crate::{actions::ActionError, settings::InstallSettingsError, InstallPlan};
#[derive(Debug, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
pub enum Planner {
LinuxMultiUser,
DarwinMultiUser,
SteamDeck,
#[derive(Debug, Clone, clap::Subcommand, serde::Serialize, serde::Deserialize)]
pub enum BuiltinPlanner {
LinuxMulti(linux::LinuxMulti),
DarwinMulti(darwin::DarwinMulti),
SteamDeck(specific::SteamDeck),
}
impl Planner {
pub fn possible_values() -> &'static [Planner] {
&[Self::LinuxMultiUser, Self::DarwinMultiUser, Self::SteamDeck]
}
impl BuiltinPlanner {
pub fn default() -> Result<Self, PlannerError> {
use target_lexicon::{Architecture, OperatingSystem};
match (Architecture::host(), OperatingSystem::host()) {
(Architecture::X86_64, OperatingSystem::Linux) => Ok(Self::LinuxMultiUser),
(Architecture::Aarch64(_), OperatingSystem::Linux) => Ok(Self::LinuxMultiUser),
(Architecture::X86_64, OperatingSystem::Linux) => {
Ok(Self::LinuxMulti(linux::LinuxMulti::default()?))
},
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
Ok(Self::LinuxMulti(linux::LinuxMulti::default()?))
},
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
| (Architecture::X86_64, OperatingSystem::Darwin) => Ok(Self::DarwinMultiUser),
| (Architecture::X86_64, OperatingSystem::Darwin) => {
Ok(Self::DarwinMulti(darwin::DarwinMulti::default()?))
},
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => Ok(Self::DarwinMultiUser),
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
Ok(Self::DarwinMulti(darwin::DarwinMulti::default()?))
},
_ => Err(PlannerError::UnsupportedArchitecture(target_lexicon::HOST)),
}
}
pub async fn plan(self, settings: InstallSettings) -> Result<InstallPlan, PlannerError> {
pub async fn plan(self) -> Result<InstallPlan, PlannerError> {
match self {
Planner::LinuxMultiUser => linux::LinuxMultiUser::plan(settings).await,
Planner::DarwinMultiUser => darwin::DarwinMultiUser::plan(settings).await,
Planner::SteamDeck => specific::SteamDeck::plan(settings).await,
BuiltinPlanner::LinuxMulti(planner) => planner.plan().await,
BuiltinPlanner::DarwinMulti(planner) => planner.plan().await,
BuiltinPlanner::SteamDeck(planner) => planner.plan().await,
}
}
}
#[async_trait::async_trait]
trait Plannable: Into<Planner>
trait Plannable: Into<BuiltinPlanner>
where
Self: Sized,
{
const DISPLAY_STRING: &'static str;
const SLUG: &'static str;
async fn plan(settings: InstallSettings) -> Result<InstallPlan, PlannerError>;
fn default() -> Result<Self, PlannerError>;
async fn plan(self) -> Result<InstallPlan, PlannerError>;
}
#[derive(thiserror::Error, Debug)]
@ -58,4 +64,6 @@ pub enum PlannerError {
#[from]
ActionError,
),
#[error(transparent)]
InstallSettings(#[from] InstallSettingsError),
}

View file

@ -4,24 +4,30 @@ use crate::{
meta::{CreateSystemdSysext, ProvisionNix},
Action, ActionError,
},
planner::Plannable,
InstallPlan, Planner,
planner::{Plannable, PlannerError},
BuiltinPlanner, InstallPlan, InstallSettings,
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
pub struct SteamDeck;
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
pub struct SteamDeck {
#[clap(flatten)]
settings: InstallSettings,
}
#[async_trait::async_trait]
impl Plannable for SteamDeck {
const DISPLAY_STRING: &'static str = "Steam Deck (x86_64 Linux Multi-User)";
const SLUG: &'static str = "steam-deck";
async fn plan(
settings: crate::InstallSettings,
) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
fn default() -> Result<Self, PlannerError> {
Ok(Self {
settings: InstallSettings::default()?,
})
}
async fn plan(self) -> Result<crate::InstallPlan, PlannerError> {
Ok(InstallPlan {
planner: Self.into(),
settings: settings.clone(),
planner: self.clone().into(),
actions: vec![
CreateSystemdSysext::plan("/var/lib/extensions")
.await
@ -31,7 +37,7 @@ impl Plannable for SteamDeck {
.await
.map(Action::from)
.map_err(ActionError::from)?,
ProvisionNix::plan(settings.clone())
ProvisionNix::plan(self.settings.clone())
.await
.map(Action::from)
.map_err(ActionError::from)?,
@ -44,8 +50,8 @@ impl Plannable for SteamDeck {
}
}
impl Into<Planner> for SteamDeck {
fn into(self) -> Planner {
Planner::SteamDeck
impl Into<BuiltinPlanner> for SteamDeck {
fn into(self) -> BuiltinPlanner {
BuiltinPlanner::SteamDeck(self)
}
}

View file

@ -1,22 +1,90 @@
use crate::planner;
use target_lexicon::Triple;
use clap::ArgAction;
use derivative::Derivative;
use url::Url;
pub const NIX_X64_64_LINUX_URL: &str =
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz";
pub const NIX_AARCH64_LINUX_URL: &str =
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-linux.tar.xz";
pub const NIX_X64_64_DARWIN_URL: &str =
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-darwin.tar.xz";
pub const NIX_AARCH64_DARWIN_URL: &str =
"https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-darwin.tar.xz";
#[serde_with::serde_as]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, clap::Parser)]
pub struct InstallSettings {
pub(crate) daemon_user_count: usize,
pub(crate) channels: Vec<(String, Url)>,
/// Channel(s) to add by default, pass multiple times for multiple channels
#[clap(
long,
value_parser,
name = "channel",
action = clap::ArgAction::Append,
env = "HARMONIC_CHANNEL",
default_value = "nixpkgs=https://nixos.org/channels/nixpkgs-unstable",
)]
pub(crate) channels: Vec<crate::cli::arg::ChannelValue>,
/// Modify the user profile to automatically load nix
#[clap(
long,
action(ArgAction::SetFalse),
default_value = "true",
global = true,
env = "HARMONIC_NO_MODIFY_PROFILE",
name = "no-modify-profile"
)]
pub(crate) modify_profile: bool,
/// Number of build users to create
#[clap(long, default_value = "32", env = "HARMONIC_DAEMON_USER_COUNT")]
pub(crate) daemon_user_count: usize,
#[clap(long, default_value = "nixbld", env = "HARMONIC_NIX_BUILD_GROUP_NAME")]
pub(crate) nix_build_group_name: String,
#[clap(long, default_value_t = 3000, env = "HARMONIC_NIX_BUILD_GROUP_ID")]
pub(crate) nix_build_group_id: usize,
#[clap(long, env = "HARMONIC_NIX_BUILD_USER_PREFIX")]
#[cfg_attr(target_os = "macos", clap(default_value = "_nixbld"))]
#[cfg_attr(target_os = "linux", clap(default_value = "nixbld"))]
pub(crate) nix_build_user_prefix: String,
#[clap(long, env = "HARMONIC_NIX_BUILD_USER_ID_BASE")]
#[cfg_attr(target_os = "macos", clap(default_value_t = 300))]
#[cfg_attr(target_os = "linux", clap(default_value_t = 3000))]
pub(crate) nix_build_user_id_base: usize,
#[clap(long, env = "HARMONIC_NIX_PACKAGE_URL")]
#[cfg_attr(
all(target_os = "macos", target_arch = "x86_64"),
clap(
default_value = NIX_X64_64_DARWIN_URL,
)
)]
#[cfg_attr(
all(target_os = "macos", target_arch = "aarch64"),
clap(
default_value = NIX_AARCH64_DARWIN_URL,
)
)]
#[cfg_attr(
all(target_os = "linux", target_arch = "x86_64"),
clap(
default_value = NIX_X64_64_LINUX_URL,
)
)]
#[cfg_attr(
all(target_os = "linux", target_arch = "aarch64"),
clap(
default_value = NIX_AARCH64_LINUX_URL,
)
)]
pub(crate) nix_package_url: Url,
#[clap(long, env = "HARMONIC_EXTRA_CONF")]
pub(crate) extra_conf: Option<String>,
#[clap(
long,
action(ArgAction::SetTrue),
default_value = "false",
global = true,
env = "HARMONIC_FORCE"
)]
pub(crate) force: bool,
#[serde_as(as = "serde_with::DisplayFromStr")]
pub(crate) triple: Triple,
}
impl InstallSettings {
@ -28,24 +96,24 @@ impl InstallSettings {
use target_lexicon::{Architecture, OperatingSystem};
match (Architecture::host(), OperatingSystem::host()) {
(Architecture::X86_64, OperatingSystem::Linux) => {
url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-linux.tar.xz";
url = NIX_X64_64_LINUX_URL;
nix_build_user_prefix = "nixbld";
nix_build_user_id_base = 3000;
},
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-linux.tar.xz";
url = NIX_AARCH64_LINUX_URL;
nix_build_user_prefix = "nixbld";
nix_build_user_id_base = 3000;
},
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
| (Architecture::X86_64, OperatingSystem::Darwin) => {
url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-x86_64-darwin.tar.xz";
url = NIX_X64_64_DARWIN_URL;
nix_build_user_prefix = "_nixbld";
nix_build_user_id_base = 300;
},
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
url = "https://releases.nixos.org/nix/nix-2.11.0/nix-2.11.0-aarch64-darwin.tar.xz";
url = NIX_AARCH64_DARWIN_URL;
nix_build_user_prefix = "_nixbld";
nix_build_user_id_base = 300;
},
@ -57,17 +125,14 @@ impl InstallSettings {
};
Ok(Self {
triple: target_lexicon::HOST,
daemon_user_count: Default::default(),
channels: Default::default(),
modify_profile: Default::default(),
channels: Vec::default(),
modify_profile: true,
nix_build_group_name: String::from("nixbld"),
nix_build_group_id: 3000,
nix_build_user_prefix: nix_build_user_prefix.to_string(),
nix_build_user_id_base,
nix_package_url: url
.parse()
.expect("Could not parse default Nix archive url, please report this issue"),
nix_package_url: url.parse()?,
extra_conf: Default::default(),
force: false,
})
@ -82,7 +147,7 @@ impl InstallSettings {
}
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
self.channels = channels.into_iter().collect();
self.channels = channels.into_iter().map(Into::into).collect();
self
}
@ -128,10 +193,10 @@ impl InstallSettings {
pub enum InstallSettingsError {
#[error("Harmonic does not support the `{0}` architecture right now")]
UnsupportedArchitecture(target_lexicon::Triple),
#[error("Planner error")]
Planner(
#[error("Parsing URL")]
Parse(
#[source]
#[from]
planner::PlannerError,
url::ParseError,
),
}