forked from lix-project/lix-installer
Rework planners to support custom args
This commit is contained in:
parent
b920b384d3
commit
16acc1fe6c
|
@ -69,13 +69,13 @@ impl Actionable for CreateDirectory {
|
||||||
fn describe_execute(&self) -> Vec<ActionDescription> {
|
fn describe_execute(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
user,
|
user: _,
|
||||||
group,
|
group: _,
|
||||||
mode,
|
mode: _,
|
||||||
force_prune_on_revert: _,
|
force_prune_on_revert: _,
|
||||||
action_state: _,
|
action_state,
|
||||||
} = &self;
|
} = &self;
|
||||||
if self.action_state == ActionState::Completed {
|
if *action_state == ActionState::Completed {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
vec![ActionDescription::new(
|
vec![ActionDescription::new(
|
||||||
|
|
|
@ -56,10 +56,10 @@ impl Actionable for CreateFile {
|
||||||
fn describe_execute(&self) -> Vec<ActionDescription> {
|
fn describe_execute(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
user,
|
user: _,
|
||||||
group,
|
group: _,
|
||||||
mode,
|
mode: _,
|
||||||
buf,
|
buf: _,
|
||||||
force: _,
|
force: _,
|
||||||
action_state: _,
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
|
|
|
@ -53,10 +53,10 @@ impl Actionable for CreateOrAppendFile {
|
||||||
fn describe_execute(&self) -> Vec<ActionDescription> {
|
fn describe_execute(&self) -> Vec<ActionDescription> {
|
||||||
let Self {
|
let Self {
|
||||||
path,
|
path,
|
||||||
user,
|
user: _,
|
||||||
group,
|
group: _,
|
||||||
mode,
|
mode: _,
|
||||||
buf,
|
buf: _,
|
||||||
action_state: _,
|
action_state: _,
|
||||||
} = &self;
|
} = &self;
|
||||||
if self.action_state == ActionState::Completed {
|
if self.action_state == ActionState::Completed {
|
||||||
|
|
|
@ -110,7 +110,7 @@ impl Actionable for CreateVolume {
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
disk,
|
disk: _,
|
||||||
name,
|
name,
|
||||||
case_sensitive: _,
|
case_sensitive: _,
|
||||||
action_state,
|
action_state,
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
use crate::execute_command;
|
|
||||||
|
|
||||||
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
|
use crate::actions::{Action, ActionDescription, ActionState, Actionable};
|
||||||
|
|
||||||
|
@ -47,8 +44,8 @@ impl Actionable for EncryptVolume {
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
disk,
|
disk: _,
|
||||||
password,
|
password: _,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
if *action_state == ActionState::Completed {
|
if *action_state == ActionState::Completed {
|
||||||
|
@ -77,8 +74,8 @@ impl Actionable for EncryptVolume {
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
disk,
|
disk: _,
|
||||||
password,
|
password: _,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
if *action_state == ActionState::Uncompleted {
|
if *action_state == ActionState::Uncompleted {
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl Actionable for UnmountVolume {
|
||||||
))]
|
))]
|
||||||
async fn execute(&mut self) -> Result<(), Self::Error> {
|
async fn execute(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
disk,
|
disk: _,
|
||||||
name,
|
name,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
@ -91,7 +91,7 @@ impl Actionable for UnmountVolume {
|
||||||
))]
|
))]
|
||||||
async fn revert(&mut self) -> Result<(), Self::Error> {
|
async fn revert(&mut self) -> Result<(), Self::Error> {
|
||||||
let Self {
|
let Self {
|
||||||
disk,
|
disk: _,
|
||||||
name,
|
name,
|
||||||
action_state,
|
action_state,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
|
@ -1,17 +1,21 @@
|
||||||
|
use reqwest::Url;
|
||||||
use serde::Serialize;
|
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::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};
|
use crate::actions::{ActionDescription, Actionable};
|
||||||
|
|
||||||
|
@ -28,13 +32,14 @@ pub struct ConfigureNix {
|
||||||
impl ConfigureNix {
|
impl ConfigureNix {
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub async fn plan(settings: InstallSettings) -> Result<Self, ConfigureNixError> {
|
pub async fn plan(settings: InstallSettings) -> Result<Self, ConfigureNixError> {
|
||||||
let channels = settings
|
let channels: Vec<(String, Url)> = settings
|
||||||
.channels
|
.channels
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(channel, _)| channel.to_string())
|
.map(|ChannelValue(channel, url)| (channel.to_string(), url.clone()))
|
||||||
.collect();
|
.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 {
|
let configure_shell_profile = if settings.modify_profile {
|
||||||
Some(ConfigureShellProfile::plan().await?)
|
Some(ConfigureShellProfile::plan().await?)
|
||||||
|
@ -42,7 +47,7 @@ impl ConfigureNix {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let place_channel_configuration =
|
let place_channel_configuration =
|
||||||
PlaceChannelConfiguration::plan(settings.channels, settings.force).await?;
|
PlaceChannelConfiguration::plan(channels, settings.force).await?;
|
||||||
let place_nix_configuration = PlaceNixConfiguration::plan(
|
let place_nix_configuration = PlaceNixConfiguration::plan(
|
||||||
settings.nix_build_group_name,
|
settings.nix_build_group_name,
|
||||||
settings.extra_conf,
|
settings.extra_conf,
|
||||||
|
|
|
@ -5,19 +5,15 @@ use std::{
|
||||||
};
|
};
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
use crate::actions::{base::darwin, Action, ActionDescription, ActionState, Actionable};
|
use crate::actions::base::{
|
||||||
use crate::{
|
darwin::{
|
||||||
actions::base::{
|
BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects, CreateSyntheticObjectsError,
|
||||||
darwin::{
|
CreateVolume, CreateVolumeError, EnableOwnership, EnableOwnershipError, EncryptVolume,
|
||||||
BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects,
|
EncryptVolumeError, UnmountVolume, UnmountVolumeError,
|
||||||
CreateSyntheticObjectsError, CreateVolume, CreateVolumeError, EnableOwnership,
|
|
||||||
EnableOwnershipError, EncryptVolume, EncryptVolumeError, UnmountVolume,
|
|
||||||
UnmountVolumeError,
|
|
||||||
},
|
|
||||||
CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError,
|
|
||||||
},
|
},
|
||||||
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";
|
const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist";
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@ use serde::Serialize;
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
use crate::actions::base::{
|
use crate::actions::base::{
|
||||||
CreateDirectory, CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix,
|
CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError,
|
||||||
MoveUnpackedNixError,
|
|
||||||
};
|
};
|
||||||
use crate::InstallSettings;
|
use crate::InstallSettings;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ChannelValue(pub String, pub Url);
|
pub struct ChannelValue(pub String, pub Url);
|
||||||
|
|
||||||
impl clap::builder::ValueParserFactory for ChannelValue {
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ChannelValueParser;
|
pub struct ChannelValueParser;
|
||||||
impl clap::builder::TypedValueParser for ChannelValueParser {
|
impl clap::builder::TypedValueParser for ChannelValueParser {
|
||||||
|
|
|
@ -9,11 +9,11 @@ use valuable::Valuable;
|
||||||
pub struct Instrumentation {
|
pub struct Instrumentation {
|
||||||
/// Enable debug logs, -vv for trace
|
/// Enable debug logs, -vv for trace
|
||||||
#[clap(short = 'v', long, action = clap::ArgAction::Count, global = true)]
|
#[clap(short = 'v', long, action = clap::ArgAction::Count, global = true)]
|
||||||
pub(crate) verbose: u8,
|
pub verbose: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Instrumentation {
|
impl<'a> Instrumentation {
|
||||||
pub(crate) fn log_level(&self) -> String {
|
pub fn log_level(&self) -> String {
|
||||||
match self.verbose {
|
match self.verbose {
|
||||||
0 => "info",
|
0 => "info",
|
||||||
1 => "debug",
|
1 => "debug",
|
||||||
|
@ -22,7 +22,7 @@ impl<'a> Instrumentation {
|
||||||
.to_string()
|
.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 fmt_layer = self.fmt_layer();
|
||||||
let filter_layer = self.filter_layer()?;
|
let filter_layer = self.filter_layer()?;
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ impl<'a> Instrumentation {
|
||||||
Ok(())
|
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
|
where
|
||||||
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
|
S: tracing::Subscriber + for<'span> tracing_subscriber::registry::LookupSpan<'span>,
|
||||||
{
|
{
|
||||||
|
@ -45,7 +45,7 @@ impl<'a> Instrumentation {
|
||||||
.pretty()
|
.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() {
|
let filter_layer = match EnvFilter::try_from_default_env() {
|
||||||
Ok(layer) => layer,
|
Ok(layer) => layer,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
|
@ -2,5 +2,3 @@ mod instrumentation;
|
||||||
pub(crate) use instrumentation::Instrumentation;
|
pub(crate) use instrumentation::Instrumentation;
|
||||||
mod channel_value;
|
mod channel_value;
|
||||||
pub(crate) use channel_value::ChannelValue;
|
pub(crate) use channel_value::ChannelValue;
|
||||||
mod plan_options;
|
|
||||||
pub(crate) use plan_options::PlanOptions;
|
|
||||||
|
|
|
@ -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>,
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ use std::process::ExitCode;
|
||||||
use self::subcommand::HarmonicSubcommand;
|
use self::subcommand::HarmonicSubcommand;
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
pub(crate) trait CommandExecute {
|
pub trait CommandExecute {
|
||||||
async fn execute(self) -> eyre::Result<ExitCode>;
|
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
|
/// Plans a Nix install, prompts for confirmation, then executes it
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[clap(version)]
|
#[clap(version)]
|
||||||
pub(crate) struct HarmonicCli {
|
pub struct HarmonicCli {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub(crate) instrumentation: arg::Instrumentation,
|
pub instrumentation: arg::Instrumentation,
|
||||||
|
|
||||||
#[clap(subcommand)]
|
#[clap(subcommand)]
|
||||||
subcommand: HarmonicSubcommand,
|
pub subcommand: HarmonicSubcommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
|
@ -1,41 +1,35 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
|
use crate::BuiltinPlanner;
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::{eyre, WrapErr};
|
use eyre::{eyre, WrapErr};
|
||||||
use harmonic::{InstallPlan, InstallSettings, Planner};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{cli::CommandExecute, interaction};
|
||||||
cli::{
|
|
||||||
arg::{ChannelValue, PlanOptions},
|
|
||||||
CommandExecute,
|
|
||||||
},
|
|
||||||
interaction,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Execute an install (possibly using an existing plan)
|
/// Execute an install (possibly using an existing plan)
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct Install {
|
#[command(args_conflicts_with_subcommands = true)]
|
||||||
|
pub struct Install {
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
action(ArgAction::SetTrue),
|
action(ArgAction::SetTrue),
|
||||||
default_value = "false",
|
default_value = "false",
|
||||||
global = true
|
global = true
|
||||||
)]
|
)]
|
||||||
no_confirm: bool,
|
pub no_confirm: bool,
|
||||||
#[clap(flatten)]
|
|
||||||
plan_options: PlanOptions,
|
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
action(ArgAction::SetTrue),
|
action(ArgAction::SetTrue),
|
||||||
default_value = "false",
|
default_value = "false",
|
||||||
global = true
|
global = true
|
||||||
)]
|
)]
|
||||||
pub(crate) explain: bool,
|
pub explain: bool,
|
||||||
#[clap(
|
#[clap(env = "HARMONIC_PLAN")]
|
||||||
conflicts_with_all = [ "plan_options" ],
|
pub plan: Option<PathBuf>,
|
||||||
env = "HARMONIC_PLAN",
|
|
||||||
)]
|
#[clap(subcommand)]
|
||||||
plan: Option<PathBuf>,
|
pub planner: BuiltinPlanner,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -45,7 +39,7 @@ impl CommandExecute for Install {
|
||||||
let Self {
|
let Self {
|
||||||
no_confirm,
|
no_confirm,
|
||||||
plan,
|
plan,
|
||||||
plan_options,
|
planner,
|
||||||
explain,
|
explain,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -56,26 +50,7 @@ impl CommandExecute for Install {
|
||||||
.wrap_err("Reading plan")?;
|
.wrap_err("Reading plan")?;
|
||||||
serde_json::from_str(&install_plan_string)?
|
serde_json::from_str(&install_plan_string)?
|
||||||
},
|
},
|
||||||
None => {
|
None => planner.plan().await?,
|
||||||
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?
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !no_confirm {
|
if !no_confirm {
|
||||||
|
|
|
@ -6,7 +6,7 @@ mod uninstall;
|
||||||
use uninstall::Uninstall;
|
use uninstall::Uninstall;
|
||||||
|
|
||||||
#[derive(Debug, clap::Subcommand)]
|
#[derive(Debug, clap::Subcommand)]
|
||||||
pub(crate) enum HarmonicSubcommand {
|
pub enum HarmonicSubcommand {
|
||||||
Plan(Plan),
|
Plan(Plan),
|
||||||
Install(Install),
|
Install(Install),
|
||||||
Uninstall(Uninstall),
|
Uninstall(Uninstall),
|
||||||
|
|
|
@ -1,61 +1,34 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
|
use crate::BuiltinPlanner;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use harmonic::{InstallPlan, InstallSettings, Planner};
|
|
||||||
|
|
||||||
use eyre::WrapErr;
|
use eyre::WrapErr;
|
||||||
|
|
||||||
use crate::cli::{
|
use crate::cli::CommandExecute;
|
||||||
arg::{ChannelValue, PlanOptions},
|
|
||||||
CommandExecute,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Plan an install that can be repeated on an identical host later
|
/// Plan an install that can be repeated on an identical host later
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct Plan {
|
#[command(multicall = true)]
|
||||||
#[clap(flatten)]
|
pub struct Plan {
|
||||||
plan_options: PlanOptions,
|
#[clap(subcommand)]
|
||||||
#[clap(default_value = "/dev/stdout")]
|
pub planner: Option<BuiltinPlanner>,
|
||||||
pub(crate) plan: PathBuf,
|
#[clap(env = "HARMONIC_PLAN")]
|
||||||
|
pub plan: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl CommandExecute for Plan {
|
impl CommandExecute for Plan {
|
||||||
#[tracing::instrument(skip_all, fields(
|
#[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,
|
|
||||||
))]
|
|
||||||
async fn execute(self) -> eyre::Result<ExitCode> {
|
async fn execute(self) -> eyre::Result<ExitCode> {
|
||||||
let Self {
|
let Self { planner, plan } = 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 planner = match planner {
|
let planner = match planner {
|
||||||
Some(planner) => 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)?;
|
let json = serde_json::to_string_pretty(&install_plan)?;
|
||||||
tokio::fs::write(plan, json)
|
tokio::fs::write(plan, json)
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
|
use crate::InstallPlan;
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::WrapErr;
|
use eyre::WrapErr;
|
||||||
use harmonic::InstallPlan;
|
|
||||||
|
|
||||||
use crate::{cli::CommandExecute, interaction};
|
use crate::{cli::CommandExecute, interaction};
|
||||||
|
|
||||||
/// Uninstall a previously installed Nix (only Harmonic done installs supported)
|
/// Uninstall a previously installed Nix (only Harmonic done installs supported)
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
pub(crate) struct Uninstall {
|
pub struct Uninstall {
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
action(ArgAction::SetTrue),
|
action(ArgAction::SetTrue),
|
||||||
default_value = "false",
|
default_value = "false",
|
||||||
global = true
|
global = true
|
||||||
)]
|
)]
|
||||||
no_confirm: bool,
|
pub no_confirm: bool,
|
||||||
#[clap(
|
#[clap(
|
||||||
long,
|
long,
|
||||||
action(ArgAction::SetTrue),
|
action(ArgAction::SetTrue),
|
||||||
default_value = "false",
|
default_value = "false",
|
||||||
global = true
|
global = true
|
||||||
)]
|
)]
|
||||||
explain: bool,
|
pub explain: bool,
|
||||||
#[clap(default_value = "/nix/receipt.json")]
|
#[clap(default_value = "/nix/receipt.json")]
|
||||||
receipt: PathBuf,
|
pub receipt: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -1,19 +1,17 @@
|
||||||
mod actions;
|
mod actions;
|
||||||
|
pub mod cli;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod interaction;
|
||||||
mod os;
|
mod os;
|
||||||
mod plan;
|
mod plan;
|
||||||
mod planner;
|
mod planner;
|
||||||
mod settings;
|
mod settings;
|
||||||
|
|
||||||
use std::{
|
use std::{ffi::OsStr, fmt::Display, process::Output};
|
||||||
ffi::OsStr,
|
|
||||||
fmt::Display,
|
|
||||||
process::{ExitStatus, Output},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use error::HarmonicError;
|
pub use error::HarmonicError;
|
||||||
pub use plan::InstallPlan;
|
pub use plan::InstallPlan;
|
||||||
pub use planner::Planner;
|
pub use planner::BuiltinPlanner;
|
||||||
use serde::Serializer;
|
use serde::Serializer;
|
||||||
pub use settings::InstallSettings;
|
pub use settings::InstallSettings;
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
pub(crate) mod cli;
|
|
||||||
|
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
|
|
||||||
pub mod interaction;
|
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::CommandExecute;
|
use harmonic::cli::CommandExecute;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> color_eyre::Result<ExitCode> {
|
async fn main() -> color_eyre::Result<ExitCode> {
|
||||||
|
@ -17,7 +13,7 @@ async fn main() -> color_eyre::Result<ExitCode> {
|
||||||
})
|
})
|
||||||
.install()?;
|
.install()?;
|
||||||
|
|
||||||
let cli = cli::HarmonicCli::parse();
|
let cli = harmonic::cli::HarmonicCli::parse();
|
||||||
|
|
||||||
cli.instrumentation.setup()?;
|
cli.instrumentation.setup()?;
|
||||||
|
|
||||||
|
|
40
src/plan.rs
40
src/plan.rs
|
@ -2,32 +2,20 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actions::{Action, ActionDescription, ActionError, Actionable},
|
actions::{Action, ActionDescription, ActionError, Actionable},
|
||||||
planner::PlannerError,
|
BuiltinPlanner, HarmonicError,
|
||||||
settings::InstallSettings,
|
|
||||||
HarmonicError, Planner,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||||
pub struct InstallPlan {
|
pub struct InstallPlan {
|
||||||
pub(crate) settings: InstallSettings,
|
|
||||||
|
|
||||||
pub(crate) actions: Vec<Action>,
|
pub(crate) actions: Vec<Action>,
|
||||||
|
|
||||||
pub(crate) planner: Planner,
|
pub(crate) planner: BuiltinPlanner,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstallPlan {
|
impl InstallPlan {
|
||||||
pub async fn new(planner: Planner, settings: InstallSettings) -> Result<Self, PlannerError> {
|
|
||||||
planner.plan(settings).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn describe_execute(&self, explain: bool) -> String {
|
pub fn describe_execute(&self, explain: bool) -> String {
|
||||||
let Self {
|
let Self { planner, actions } = self;
|
||||||
planner,
|
|
||||||
settings,
|
|
||||||
actions,
|
|
||||||
} = self;
|
|
||||||
format!(
|
format!(
|
||||||
"\
|
"\
|
||||||
This Nix install is for:\n\
|
This Nix install is for:\n\
|
||||||
|
@ -42,12 +30,7 @@ impl InstallPlan {
|
||||||
",
|
",
|
||||||
os_type = "Linux",
|
os_type = "Linux",
|
||||||
init_type = "systemd",
|
init_type = "systemd",
|
||||||
nix_channels = settings
|
nix_channels = "todo",
|
||||||
.channels
|
|
||||||
.iter()
|
|
||||||
.map(|(name, url)| format!("{name}={url}"))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(","),
|
|
||||||
actions = actions
|
actions = actions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| v.describe_execute())
|
.map(|v| v.describe_execute())
|
||||||
|
@ -76,7 +59,6 @@ impl InstallPlan {
|
||||||
pub async fn install(&mut self) -> Result<(), HarmonicError> {
|
pub async fn install(&mut self) -> Result<(), HarmonicError> {
|
||||||
let Self {
|
let Self {
|
||||||
actions,
|
actions,
|
||||||
settings: _,
|
|
||||||
planner: _,
|
planner: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
@ -97,11 +79,7 @@ impl InstallPlan {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub fn describe_revert(&self, explain: bool) -> String {
|
pub fn describe_revert(&self, explain: bool) -> String {
|
||||||
let Self {
|
let Self { planner, actions } = self;
|
||||||
planner,
|
|
||||||
settings,
|
|
||||||
actions,
|
|
||||||
} = self;
|
|
||||||
format!(
|
format!(
|
||||||
"\
|
"\
|
||||||
This Nix uninstall is for:\n\
|
This Nix uninstall is for:\n\
|
||||||
|
@ -116,12 +94,7 @@ impl InstallPlan {
|
||||||
",
|
",
|
||||||
os_type = "Linux",
|
os_type = "Linux",
|
||||||
init_type = "systemd",
|
init_type = "systemd",
|
||||||
nix_channels = settings
|
nix_channels = "todo",
|
||||||
.channels
|
|
||||||
.iter()
|
|
||||||
.map(|(name, url)| format!("{name}={url}"))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(","),
|
|
||||||
actions = actions
|
actions = actions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| v.describe_revert())
|
.map(|v| v.describe_revert())
|
||||||
|
@ -150,7 +123,6 @@ impl InstallPlan {
|
||||||
pub async fn revert(&mut self) -> Result<(), HarmonicError> {
|
pub async fn revert(&mut self) -> Result<(), HarmonicError> {
|
||||||
let Self {
|
let Self {
|
||||||
actions,
|
actions,
|
||||||
settings: _,
|
|
||||||
planner: _,
|
planner: _,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
mod multi_user;
|
mod multi;
|
||||||
|
|
||||||
pub use multi_user::DarwinMultiUser;
|
pub use multi::DarwinMulti;
|
||||||
|
|
|
@ -11,20 +11,27 @@ use crate::{
|
||||||
execute_command,
|
execute_command,
|
||||||
os::darwin::DiskUtilOutput,
|
os::darwin::DiskUtilOutput,
|
||||||
planner::{Plannable, PlannerError},
|
planner::{Plannable, PlannerError},
|
||||||
InstallPlan, Planner,
|
BuiltinPlanner, InstallPlan, InstallSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
|
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct DarwinMultiUser;
|
pub struct DarwinMulti {
|
||||||
|
#[clap(flatten)]
|
||||||
|
settings: InstallSettings,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Plannable for DarwinMultiUser {
|
impl Plannable for DarwinMulti {
|
||||||
const DISPLAY_STRING: &'static str = "Darwin Multi-User";
|
const DISPLAY_STRING: &'static str = "Darwin Multi-User";
|
||||||
const SLUG: &'static str = "darwin-multi";
|
const SLUG: &'static str = "darwin-multi";
|
||||||
|
|
||||||
async fn plan(
|
fn default() -> Result<Self, PlannerError> {
|
||||||
settings: crate::InstallSettings,
|
Ok(Self {
|
||||||
) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
|
settings: InstallSettings::default()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn plan(self) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
|
||||||
let root_disk = {
|
let root_disk = {
|
||||||
let buf =
|
let buf =
|
||||||
execute_command(Command::new("/usr/sbin/diskutil").args(["info", "-plist", "/"]))
|
execute_command(Command::new("/usr/sbin/diskutil").args(["info", "-plist", "/"]))
|
||||||
|
@ -39,8 +46,7 @@ impl Plannable for DarwinMultiUser {
|
||||||
let volume_label = "Nix Store".into();
|
let volume_label = "Nix Store".into();
|
||||||
|
|
||||||
Ok(InstallPlan {
|
Ok(InstallPlan {
|
||||||
planner: Self.into(),
|
planner: self.clone().into(),
|
||||||
settings: settings.clone(),
|
|
||||||
actions: vec![
|
actions: vec![
|
||||||
// Create Volume step:
|
// Create Volume step:
|
||||||
//
|
//
|
||||||
|
@ -50,11 +56,11 @@ impl Plannable for DarwinMultiUser {
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
ProvisionNix::plan(settings.clone())
|
ProvisionNix::plan(self.settings.clone())
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
ConfigureNix::plan(settings)
|
ConfigureNix::plan(self.settings)
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
|
@ -67,8 +73,8 @@ impl Plannable for DarwinMultiUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Planner> for DarwinMultiUser {
|
impl Into<BuiltinPlanner> for DarwinMulti {
|
||||||
fn into(self) -> Planner {
|
fn into(self) -> BuiltinPlanner {
|
||||||
Planner::DarwinMultiUser
|
BuiltinPlanner::DarwinMulti(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
mod multi_user;
|
mod multi;
|
||||||
|
|
||||||
pub use multi_user::LinuxMultiUser;
|
pub use multi::LinuxMulti;
|
||||||
|
|
|
@ -5,31 +5,39 @@ use crate::{
|
||||||
Action, ActionError,
|
Action, ActionError,
|
||||||
},
|
},
|
||||||
planner::{Plannable, PlannerError},
|
planner::{Plannable, PlannerError},
|
||||||
InstallPlan, InstallSettings, Planner,
|
BuiltinPlanner, InstallPlan, InstallSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
|
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LinuxMultiUser;
|
pub struct LinuxMulti {
|
||||||
|
#[clap(flatten)]
|
||||||
|
settings: InstallSettings,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Plannable for LinuxMultiUser {
|
impl Plannable for LinuxMulti {
|
||||||
const DISPLAY_STRING: &'static str = "Linux Multi-User";
|
const DISPLAY_STRING: &'static str = "Linux Multi-User";
|
||||||
const SLUG: &'static str = "linux-multi";
|
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 {
|
Ok(InstallPlan {
|
||||||
planner: Self.into(),
|
planner: self.clone().into(),
|
||||||
settings: settings.clone(),
|
|
||||||
actions: vec![
|
actions: vec![
|
||||||
CreateDirectory::plan("/nix", None, None, 0o0755, true)
|
CreateDirectory::plan("/nix", None, None, 0o0755, true)
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
ProvisionNix::plan(settings.clone())
|
ProvisionNix::plan(self.settings.clone())
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
ConfigureNix::plan(settings)
|
ConfigureNix::plan(self.settings)
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
|
@ -42,8 +50,8 @@ impl Plannable for LinuxMultiUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Planner> for LinuxMultiUser {
|
impl Into<BuiltinPlanner> for LinuxMulti {
|
||||||
fn into(self) -> Planner {
|
fn into(self) -> BuiltinPlanner {
|
||||||
Planner::LinuxMultiUser
|
BuiltinPlanner::LinuxMulti(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,51 +1,57 @@
|
||||||
mod darwin;
|
pub mod darwin;
|
||||||
mod linux;
|
pub mod linux;
|
||||||
mod specific;
|
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)]
|
#[derive(Debug, Clone, clap::Subcommand, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum Planner {
|
pub enum BuiltinPlanner {
|
||||||
LinuxMultiUser,
|
LinuxMulti(linux::LinuxMulti),
|
||||||
DarwinMultiUser,
|
DarwinMulti(darwin::DarwinMulti),
|
||||||
SteamDeck,
|
SteamDeck(specific::SteamDeck),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Planner {
|
impl BuiltinPlanner {
|
||||||
pub fn possible_values() -> &'static [Planner] {
|
|
||||||
&[Self::LinuxMultiUser, Self::DarwinMultiUser, Self::SteamDeck]
|
|
||||||
}
|
|
||||||
pub fn default() -> Result<Self, PlannerError> {
|
pub fn default() -> Result<Self, PlannerError> {
|
||||||
use target_lexicon::{Architecture, OperatingSystem};
|
use target_lexicon::{Architecture, OperatingSystem};
|
||||||
match (Architecture::host(), OperatingSystem::host()) {
|
match (Architecture::host(), OperatingSystem::host()) {
|
||||||
(Architecture::X86_64, OperatingSystem::Linux) => Ok(Self::LinuxMultiUser),
|
(Architecture::X86_64, OperatingSystem::Linux) => {
|
||||||
(Architecture::Aarch64(_), OperatingSystem::Linux) => Ok(Self::LinuxMultiUser),
|
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::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::MacOSX { .. })
|
||||||
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => Ok(Self::DarwinMultiUser),
|
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
|
||||||
|
Ok(Self::DarwinMulti(darwin::DarwinMulti::default()?))
|
||||||
|
},
|
||||||
_ => Err(PlannerError::UnsupportedArchitecture(target_lexicon::HOST)),
|
_ => 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 {
|
match self {
|
||||||
Planner::LinuxMultiUser => linux::LinuxMultiUser::plan(settings).await,
|
BuiltinPlanner::LinuxMulti(planner) => planner.plan().await,
|
||||||
Planner::DarwinMultiUser => darwin::DarwinMultiUser::plan(settings).await,
|
BuiltinPlanner::DarwinMulti(planner) => planner.plan().await,
|
||||||
Planner::SteamDeck => specific::SteamDeck::plan(settings).await,
|
BuiltinPlanner::SteamDeck(planner) => planner.plan().await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
trait Plannable: Into<Planner>
|
trait Plannable: Into<BuiltinPlanner>
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
const DISPLAY_STRING: &'static str;
|
const DISPLAY_STRING: &'static str;
|
||||||
const SLUG: &'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)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -58,4 +64,6 @@ pub enum PlannerError {
|
||||||
#[from]
|
#[from]
|
||||||
ActionError,
|
ActionError,
|
||||||
),
|
),
|
||||||
|
#[error(transparent)]
|
||||||
|
InstallSettings(#[from] InstallSettingsError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,30 @@ use crate::{
|
||||||
meta::{CreateSystemdSysext, ProvisionNix},
|
meta::{CreateSystemdSysext, ProvisionNix},
|
||||||
Action, ActionError,
|
Action, ActionError,
|
||||||
},
|
},
|
||||||
planner::Plannable,
|
planner::{Plannable, PlannerError},
|
||||||
InstallPlan, Planner,
|
BuiltinPlanner, InstallPlan, InstallSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
|
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct SteamDeck;
|
pub struct SteamDeck {
|
||||||
|
#[clap(flatten)]
|
||||||
|
settings: InstallSettings,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Plannable for SteamDeck {
|
impl Plannable for SteamDeck {
|
||||||
const DISPLAY_STRING: &'static str = "Steam Deck (x86_64 Linux Multi-User)";
|
const DISPLAY_STRING: &'static str = "Steam Deck (x86_64 Linux Multi-User)";
|
||||||
const SLUG: &'static str = "steam-deck";
|
const SLUG: &'static str = "steam-deck";
|
||||||
|
|
||||||
async fn plan(
|
fn default() -> Result<Self, PlannerError> {
|
||||||
settings: crate::InstallSettings,
|
Ok(Self {
|
||||||
) -> Result<crate::InstallPlan, crate::planner::PlannerError> {
|
settings: InstallSettings::default()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn plan(self) -> Result<crate::InstallPlan, PlannerError> {
|
||||||
Ok(InstallPlan {
|
Ok(InstallPlan {
|
||||||
planner: Self.into(),
|
planner: self.clone().into(),
|
||||||
settings: settings.clone(),
|
|
||||||
actions: vec![
|
actions: vec![
|
||||||
CreateSystemdSysext::plan("/var/lib/extensions")
|
CreateSystemdSysext::plan("/var/lib/extensions")
|
||||||
.await
|
.await
|
||||||
|
@ -31,7 +37,7 @@ impl Plannable for SteamDeck {
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
ProvisionNix::plan(settings.clone())
|
ProvisionNix::plan(self.settings.clone())
|
||||||
.await
|
.await
|
||||||
.map(Action::from)
|
.map(Action::from)
|
||||||
.map_err(ActionError::from)?,
|
.map_err(ActionError::from)?,
|
||||||
|
@ -44,8 +50,8 @@ impl Plannable for SteamDeck {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<Planner> for SteamDeck {
|
impl Into<BuiltinPlanner> for SteamDeck {
|
||||||
fn into(self) -> Planner {
|
fn into(self) -> BuiltinPlanner {
|
||||||
Planner::SteamDeck
|
BuiltinPlanner::SteamDeck(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
107
src/settings.rs
107
src/settings.rs
|
@ -1,22 +1,90 @@
|
||||||
use crate::planner;
|
use clap::ArgAction;
|
||||||
use target_lexicon::Triple;
|
use derivative::Derivative;
|
||||||
use url::Url;
|
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]
|
#[serde_with::serde_as]
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, clap::Parser)]
|
||||||
pub struct InstallSettings {
|
pub struct InstallSettings {
|
||||||
pub(crate) daemon_user_count: usize,
|
/// Channel(s) to add by default, pass multiple times for multiple channels
|
||||||
pub(crate) channels: Vec<(String, Url)>,
|
#[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,
|
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,
|
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,
|
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,
|
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,
|
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,
|
pub(crate) nix_package_url: Url,
|
||||||
|
#[clap(long, env = "HARMONIC_EXTRA_CONF")]
|
||||||
pub(crate) extra_conf: Option<String>,
|
pub(crate) extra_conf: Option<String>,
|
||||||
|
#[clap(
|
||||||
|
long,
|
||||||
|
action(ArgAction::SetTrue),
|
||||||
|
default_value = "false",
|
||||||
|
global = true,
|
||||||
|
env = "HARMONIC_FORCE"
|
||||||
|
)]
|
||||||
pub(crate) force: bool,
|
pub(crate) force: bool,
|
||||||
#[serde_as(as = "serde_with::DisplayFromStr")]
|
|
||||||
pub(crate) triple: Triple,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstallSettings {
|
impl InstallSettings {
|
||||||
|
@ -28,24 +96,24 @@ impl InstallSettings {
|
||||||
use target_lexicon::{Architecture, OperatingSystem};
|
use target_lexicon::{Architecture, OperatingSystem};
|
||||||
match (Architecture::host(), OperatingSystem::host()) {
|
match (Architecture::host(), OperatingSystem::host()) {
|
||||||
(Architecture::X86_64, OperatingSystem::Linux) => {
|
(Architecture::X86_64, OperatingSystem::Linux) => {
|
||||||
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_prefix = "nixbld";
|
||||||
nix_build_user_id_base = 3000;
|
nix_build_user_id_base = 3000;
|
||||||
},
|
},
|
||||||
(Architecture::Aarch64(_), OperatingSystem::Linux) => {
|
(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_prefix = "nixbld";
|
||||||
nix_build_user_id_base = 3000;
|
nix_build_user_id_base = 3000;
|
||||||
},
|
},
|
||||||
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
|
(Architecture::X86_64, OperatingSystem::MacOSX { .. })
|
||||||
| (Architecture::X86_64, OperatingSystem::Darwin) => {
|
| (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_prefix = "_nixbld";
|
||||||
nix_build_user_id_base = 300;
|
nix_build_user_id_base = 300;
|
||||||
},
|
},
|
||||||
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
|
(Architecture::Aarch64(_), OperatingSystem::MacOSX { .. })
|
||||||
| (Architecture::Aarch64(_), OperatingSystem::Darwin) => {
|
| (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_prefix = "_nixbld";
|
||||||
nix_build_user_id_base = 300;
|
nix_build_user_id_base = 300;
|
||||||
},
|
},
|
||||||
|
@ -57,17 +125,14 @@ impl InstallSettings {
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
triple: target_lexicon::HOST,
|
|
||||||
daemon_user_count: Default::default(),
|
daemon_user_count: Default::default(),
|
||||||
channels: Default::default(),
|
channels: Vec::default(),
|
||||||
modify_profile: Default::default(),
|
modify_profile: true,
|
||||||
nix_build_group_name: String::from("nixbld"),
|
nix_build_group_name: String::from("nixbld"),
|
||||||
nix_build_group_id: 3000,
|
nix_build_group_id: 3000,
|
||||||
nix_build_user_prefix: nix_build_user_prefix.to_string(),
|
nix_build_user_prefix: nix_build_user_prefix.to_string(),
|
||||||
nix_build_user_id_base,
|
nix_build_user_id_base,
|
||||||
nix_package_url: url
|
nix_package_url: url.parse()?,
|
||||||
.parse()
|
|
||||||
.expect("Could not parse default Nix archive url, please report this issue"),
|
|
||||||
extra_conf: Default::default(),
|
extra_conf: Default::default(),
|
||||||
force: false,
|
force: false,
|
||||||
})
|
})
|
||||||
|
@ -82,7 +147,7 @@ impl InstallSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
pub fn channels(&mut self, channels: impl IntoIterator<Item = (String, Url)>) -> &mut Self {
|
||||||
self.channels = channels.into_iter().collect();
|
self.channels = channels.into_iter().map(Into::into).collect();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,10 +193,10 @@ impl InstallSettings {
|
||||||
pub enum InstallSettingsError {
|
pub enum InstallSettingsError {
|
||||||
#[error("Harmonic does not support the `{0}` architecture right now")]
|
#[error("Harmonic does not support the `{0}` architecture right now")]
|
||||||
UnsupportedArchitecture(target_lexicon::Triple),
|
UnsupportedArchitecture(target_lexicon::Triple),
|
||||||
#[error("Planner error")]
|
#[error("Parsing URL")]
|
||||||
Planner(
|
Parse(
|
||||||
#[source]
|
#[source]
|
||||||
#[from]
|
#[from]
|
||||||
planner::PlannerError,
|
url::ParseError,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue