2022-11-28 22:57:35 +00:00
/*! [`BuiltinPlanner`]s and traits to create new types which can be used to plan out an [`InstallPlan`]
2023-02-01 20:35:52 +00:00
It ' s a [ ` Planner ` ] s job to construct ( if possible ) a valid [ ` InstallPlan ` ] for the host . Some planners are operating system specific , others are device specific .
2022-11-28 22:57:35 +00:00
[ ` Planner ` ] s contain their planner specific settings , typically alongside a [ ` CommonSettings ` ] [ crate ::settings ::CommonSettings ] .
[ ` BuiltinPlanner ::default ( ) ` ] offers a way to get the default builtin planner for a given host .
Custom Planners can also be used to create a platform , project , or organization specific install .
A custom [ ` Planner ` ] can be created :
` ` ` rust , no_run
use std ::{ error ::Error , collections ::HashMap } ;
2022-12-19 18:26:58 +00:00
use nix_installer ::{
2022-11-28 22:57:35 +00:00
InstallPlan ,
settings ::{ CommonSettings , InstallSettingsError } ,
2023-02-01 20:35:52 +00:00
planner ::{ Planner , PlannerError } ,
action ::{ Action , StatefulAction , base ::CreateFile } ,
2022-11-28 22:57:35 +00:00
} ;
#[ derive(Debug, Clone, serde::Serialize, serde::Deserialize) ]
pub struct MyPlanner {
pub common : CommonSettings ,
}
#[ async_trait::async_trait ]
#[ typetag::serde(name = " my-planner " ) ]
impl Planner for MyPlanner {
async fn default ( ) -> Result < Self , PlannerError > {
Ok ( Self {
2023-02-01 20:35:52 +00:00
common : CommonSettings ::default ( ) . await ? ,
2022-11-28 22:57:35 +00:00
} )
}
async fn plan ( & self ) -> Result < Vec < StatefulAction < Box < dyn Action > > > , PlannerError > {
Ok ( vec! [
// ...
2023-02-01 20:35:52 +00:00
CreateFile ::plan ( " /example " , None , None , None , " Example " . to_string ( ) , false )
2022-11-28 22:57:35 +00:00
. await
. map_err ( PlannerError ::Action ) ? . boxed ( ) ,
] )
}
fn settings ( & self ) -> Result < HashMap < String , serde_json ::Value > , InstallSettingsError > {
let Self { common } = self ;
let mut map = std ::collections ::HashMap ::default ( ) ;
map . extend ( common . settings ( ) ? . into_iter ( ) ) ;
Ok ( map )
}
2023-02-24 18:11:12 +00:00
2023-03-13 20:30:04 +00:00
async fn configured_settings (
& self ,
) -> Result < HashMap < String , serde_json ::Value > , PlannerError > {
let default = Self ::default ( ) . await ? . settings ( ) ? ;
let configured = self . settings ( ) ? ;
let mut settings : HashMap < String , serde_json ::Value > = HashMap ::new ( ) ;
for ( key , value ) in configured . iter ( ) {
if default . get ( key ) ! = Some ( value ) {
settings . insert ( key . clone ( ) , value . clone ( ) ) ;
}
}
Ok ( settings )
}
2023-02-24 18:11:12 +00:00
#[ cfg(feature = " diagnostics " ) ]
async fn diagnostic_data ( & self ) -> Result < nix_installer ::diagnostics ::DiagnosticData , PlannerError > {
Ok ( nix_installer ::diagnostics ::DiagnosticData ::new (
self . common . diagnostic_endpoint . clone ( ) ,
self . typetag_name ( ) . into ( ) ,
2023-03-13 20:30:04 +00:00
self . configured_settings ( )
. await ?
. into_keys ( )
. collect ::< Vec < _ > > ( ) ,
2023-02-24 18:11:12 +00:00
) )
}
2022-11-28 22:57:35 +00:00
}
# async fn custom_planner_install ( ) -> color_eyre ::Result < ( ) > {
let planner = MyPlanner ::default ( ) . await ? ;
let mut plan = InstallPlan ::plan ( planner ) . await ? ;
match plan . install ( None ) . await {
Ok ( ( ) ) = > tracing ::info! ( " Done " ) ,
Err ( e ) = > {
match e . source ( ) {
Some ( source ) = > tracing ::error! ( " {e}: {} " , source ) ,
None = > tracing ::error! ( " {e} " ) ,
} ;
plan . uninstall ( None ) . await ? ;
} ,
} ;
# Ok ( ( ) )
# }
` ` `
* /
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2022-10-25 18:57:09 +00:00
pub mod linux ;
2023-02-10 20:35:00 +00:00
#[ cfg(target_os = " macos " ) ]
pub mod macos ;
#[ cfg(target_os = " linux " ) ]
pub mod steam_deck ;
2022-10-14 22:14:03 +00:00
2022-12-19 15:30:45 +00:00
use std ::{ collections ::HashMap , string ::FromUtf8Error } ;
2022-10-28 19:44:07 +00:00
2022-11-28 22:57:35 +00:00
use crate ::{
2022-12-05 16:55:30 +00:00
action ::{ ActionError , StatefulAction } ,
2022-12-12 20:20:50 +00:00
error ::HasExpectedErrors ,
2022-12-16 18:55:28 +00:00
settings ::{ CommonSettings , InstallSettingsError } ,
2022-12-19 18:26:58 +00:00
Action , InstallPlan , NixInstallerError ,
2022-11-28 22:57:35 +00:00
} ;
2022-10-26 22:13:42 +00:00
2022-11-28 22:57:35 +00:00
/// Something which can be used to plan out an [`InstallPlan`]
2022-10-26 22:13:42 +00:00
#[ async_trait::async_trait ]
#[ typetag::serde(tag = " planner " ) ]
pub trait Planner : std ::fmt ::Debug + Send + Sync + dyn_clone ::DynClone {
2022-11-28 22:57:35 +00:00
/// Instantiate the planner with default settings, if possible
async fn default ( ) -> Result < Self , PlannerError >
2022-10-26 22:13:42 +00:00
where
Self : Sized ;
2022-11-28 22:57:35 +00:00
/// Plan out the [`Action`]s for an [`InstallPlan`]
async fn plan ( & self ) -> Result < Vec < StatefulAction < Box < dyn Action > > > , PlannerError > ;
/// The settings being used by the planner
fn settings ( & self ) -> Result < HashMap < String , serde_json ::Value > , InstallSettingsError > ;
2023-02-24 18:11:12 +00:00
2023-03-13 20:30:04 +00:00
async fn configured_settings ( & self )
-> Result < HashMap < String , serde_json ::Value > , PlannerError > ;
2023-02-24 18:11:12 +00:00
2022-11-28 22:57:35 +00:00
/// A boxed, type erased planner
2022-11-08 17:58:53 +00:00
fn boxed ( self ) -> Box < dyn Planner >
where
Self : Sized + 'static ,
{
Box ::new ( self )
}
2023-02-24 18:11:12 +00:00
#[ cfg(feature = " diagnostics " ) ]
async fn diagnostic_data ( & self ) -> Result < crate ::diagnostics ::DiagnosticData , PlannerError > ;
2022-10-26 22:13:42 +00:00
}
dyn_clone ::clone_trait_object! ( Planner ) ;
2022-10-14 22:14:03 +00:00
2022-11-28 22:57:35 +00:00
/// Planners built into this crate
#[ derive(Debug, Clone, serde::Serialize, serde::Deserialize) ]
#[ cfg_attr(feature = " cli " , derive(clap::Subcommand)) ]
2022-10-25 18:57:09 +00:00
pub enum BuiltinPlanner {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
/// A planner for Linux installs
Linux ( linux ::Linux ) ,
/// A planner MacOS (Darwin) for installs
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2023-02-10 20:35:00 +00:00
Macos ( macos ::Macos ) ,
/// A planner suitable for the Valve Steam Deck running SteamOS
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
SteamDeck ( steam_deck ::SteamDeck ) ,
2022-10-14 22:14:03 +00:00
}
2022-10-25 18:57:09 +00:00
impl BuiltinPlanner {
2022-11-28 22:57:35 +00:00
/// Heuristically determine the default planner for the target system
pub async fn default ( ) -> Result < Self , PlannerError > {
2022-10-14 22:14:03 +00:00
use target_lexicon ::{ Architecture , OperatingSystem } ;
match ( Architecture ::host ( ) , OperatingSystem ::host ( ) ) {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2022-10-25 18:57:09 +00:00
( Architecture ::X86_64 , OperatingSystem ::Linux ) = > {
2023-02-10 20:35:00 +00:00
Ok ( Self ::Linux ( linux ::Linux ::default ( ) . await ? ) )
2022-10-25 18:57:09 +00:00
} ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-06 15:50:23 +00:00
( Architecture ::X86_32 ( _ ) , OperatingSystem ::Linux ) = > {
2023-02-10 20:35:00 +00:00
Ok ( Self ::Linux ( linux ::Linux ::default ( ) . await ? ) )
2023-02-06 15:50:23 +00:00
} ,
#[ cfg(target_os = " linux " ) ]
2022-10-25 18:57:09 +00:00
( Architecture ::Aarch64 ( _ ) , OperatingSystem ::Linux ) = > {
2023-02-10 20:35:00 +00:00
Ok ( Self ::Linux ( linux ::Linux ::default ( ) . await ? ) )
2022-10-25 18:57:09 +00:00
} ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2022-10-24 23:16:18 +00:00
( Architecture ::X86_64 , OperatingSystem ::MacOSX { .. } )
2022-10-25 18:57:09 +00:00
| ( Architecture ::X86_64 , OperatingSystem ::Darwin ) = > {
2023-02-10 20:35:00 +00:00
Ok ( Self ::Macos ( macos ::Macos ::default ( ) . await ? ) )
2022-10-25 18:57:09 +00:00
} ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2022-10-24 23:16:18 +00:00
( Architecture ::Aarch64 ( _ ) , OperatingSystem ::MacOSX { .. } )
2022-10-25 18:57:09 +00:00
| ( Architecture ::Aarch64 ( _ ) , OperatingSystem ::Darwin ) = > {
2023-02-10 20:35:00 +00:00
Ok ( Self ::Macos ( macos ::Macos ::default ( ) . await ? ) )
2022-10-25 18:57:09 +00:00
} ,
2022-11-28 22:57:35 +00:00
_ = > Err ( PlannerError ::UnsupportedArchitecture ( target_lexicon ::HOST ) ) ,
2022-10-14 22:14:03 +00:00
}
}
2022-12-16 18:55:28 +00:00
pub async fn from_common_settings ( settings : CommonSettings ) -> Result < Self , PlannerError > {
let mut built = Self ::default ( ) . await ? ;
match & mut built {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Linux ( inner ) = > inner . settings = settings ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2022-12-16 18:55:28 +00:00
BuiltinPlanner ::SteamDeck ( inner ) = > inner . settings = settings ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Macos ( inner ) = > inner . settings = settings ,
2022-12-16 18:55:28 +00:00
}
Ok ( built )
}
2023-03-13 20:30:04 +00:00
pub async fn configured_settings (
& self ,
) -> Result < HashMap < String , serde_json ::Value > , PlannerError > {
2023-02-24 18:11:12 +00:00
match self {
#[ cfg(target_os = " linux " ) ]
BuiltinPlanner ::Linux ( inner ) = > inner . configured_settings ( ) . await ,
#[ cfg(target_os = " linux " ) ]
BuiltinPlanner ::SteamDeck ( inner ) = > inner . configured_settings ( ) . await ,
#[ cfg(target_os = " macos " ) ]
BuiltinPlanner ::Macos ( inner ) = > inner . configured_settings ( ) . await ,
}
}
2022-12-19 18:26:58 +00:00
pub async fn plan ( self ) -> Result < InstallPlan , NixInstallerError > {
2022-10-14 22:14:03 +00:00
match self {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Linux ( planner ) = > InstallPlan ::plan ( planner ) . await ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2022-11-28 22:57:35 +00:00
BuiltinPlanner ::SteamDeck ( planner ) = > InstallPlan ::plan ( planner ) . await ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Macos ( planner ) = > InstallPlan ::plan ( planner ) . await ,
2022-10-14 22:14:03 +00:00
}
}
2022-11-08 17:58:53 +00:00
pub fn boxed ( self ) -> Box < dyn Planner > {
match self {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Linux ( i ) = > i . boxed ( ) ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2022-11-08 17:58:53 +00:00
BuiltinPlanner ::SteamDeck ( i ) = > i . boxed ( ) ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Macos ( i ) = > i . boxed ( ) ,
2022-11-08 17:58:53 +00:00
}
}
2023-01-04 17:32:45 +00:00
pub fn typetag_name ( & self ) -> & 'static str {
match self {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Linux ( i ) = > i . typetag_name ( ) ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-01-04 17:32:45 +00:00
BuiltinPlanner ::SteamDeck ( i ) = > i . typetag_name ( ) ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Macos ( i ) = > i . typetag_name ( ) ,
2023-01-04 17:32:45 +00:00
}
}
pub fn settings ( & self ) -> Result < HashMap < String , serde_json ::Value > , InstallSettingsError > {
match self {
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Linux ( i ) = > i . settings ( ) ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " linux " ) ]
2023-01-04 17:32:45 +00:00
BuiltinPlanner ::SteamDeck ( i ) = > i . settings ( ) ,
2023-02-01 20:35:52 +00:00
#[ cfg(target_os = " macos " ) ]
2023-02-10 20:35:00 +00:00
BuiltinPlanner ::Macos ( i ) = > i . settings ( ) ,
2023-01-04 17:32:45 +00:00
}
}
2023-02-24 18:11:12 +00:00
#[ cfg(feature = " diagnostics " ) ]
pub async fn diagnostic_data (
& self ,
) -> Result < crate ::diagnostics ::DiagnosticData , PlannerError > {
match self {
#[ cfg(target_os = " linux " ) ]
BuiltinPlanner ::Linux ( i ) = > i . diagnostic_data ( ) . await ,
#[ cfg(target_os = " linux " ) ]
BuiltinPlanner ::SteamDeck ( i ) = > i . diagnostic_data ( ) . await ,
#[ cfg(target_os = " macos " ) ]
BuiltinPlanner ::Macos ( i ) = > i . diagnostic_data ( ) . await ,
}
}
2022-10-14 22:14:03 +00:00
}
2022-11-28 22:57:35 +00:00
/// An error originating from a [`Planner`]
2023-03-03 20:03:51 +00:00
#[ non_exhaustive ]
2023-02-24 18:11:12 +00:00
#[ derive(thiserror::Error, Debug, strum::IntoStaticStr) ]
2022-11-28 22:57:35 +00:00
pub enum PlannerError {
2022-12-19 18:26:58 +00:00
/// `nix-installer` does not have a default planner for the target architecture right now
#[ error( " `nix-installer` does not have a default planner for the `{0}` architecture right now, pass a specific archetype " ) ]
2022-10-14 22:14:03 +00:00
UnsupportedArchitecture ( target_lexicon ::Triple ) ,
2022-11-28 22:57:35 +00:00
/// Error executing action
2022-10-14 22:14:03 +00:00
#[ error( " Error executing action " ) ]
2022-12-05 16:55:30 +00:00
Action (
#[ source ]
#[ from ]
ActionError ,
) ,
2022-11-28 22:57:35 +00:00
/// An [`InstallSettingsError`]
2022-10-25 18:57:09 +00:00
#[ error(transparent) ]
InstallSettings ( #[ from ] InstallSettingsError ) ,
2022-11-28 22:57:35 +00:00
/// A MacOS (Darwin) plist related error
2022-10-26 16:27:50 +00:00
#[ error(transparent) ]
Plist ( #[ from ] plist ::Error ) ,
2022-12-19 15:30:45 +00:00
/// A Linux SELinux related error
2022-12-19 18:26:58 +00:00
#[ error( " This installer doesn't yet support SELinux in `Enforcing` mode. If SELinux is important to you, please see https://github.com/DeterminateSystems/nix-installer/issues/124. You can also try again after setting SELinux to `Permissive` mode with `setenforce Permissive` " ) ]
2022-12-19 15:30:45 +00:00
SelinuxEnforcing ,
/// A UTF-8 related error
#[ error( " UTF-8 error " ) ]
Utf8 ( #[ from ] FromUtf8Error ) ,
2022-11-28 22:57:35 +00:00
/// Custom planner error
#[ error( " Custom planner error " ) ]
Custom ( #[ source ] Box < dyn std ::error ::Error + Send + Sync > ) ,
2022-12-12 20:20:50 +00:00
#[ error( " NixOS already has Nix installed " ) ]
NixOs ,
#[ error( " `nix` is already a valid command, so it is installed " ) ]
NixExists ,
2023-03-03 17:49:46 +00:00
#[ error( " WSL1 is not supported, please upgrade to WSL2: https://learn.microsoft.com/en-us/windows/wsl/install#upgrade-version-from-wsl-1-to-wsl-2 " ) ]
Wsl1 ,
2022-12-12 20:20:50 +00:00
}
impl HasExpectedErrors for PlannerError {
fn expected < ' a > ( & ' a self ) -> Option < Box < dyn std ::error ::Error + ' a > > {
match self {
this @ PlannerError ::UnsupportedArchitecture ( _ ) = > Some ( Box ::new ( this ) ) ,
PlannerError ::Action ( _ ) = > None ,
PlannerError ::InstallSettings ( _ ) = > None ,
PlannerError ::Plist ( _ ) = > None ,
2022-12-19 15:30:45 +00:00
PlannerError ::Utf8 ( _ ) = > None ,
PlannerError ::SelinuxEnforcing = > Some ( Box ::new ( self ) ) ,
2022-12-12 20:20:50 +00:00
PlannerError ::Custom ( _ ) = > None ,
this @ PlannerError ::NixOs = > Some ( Box ::new ( this ) ) ,
this @ PlannerError ::NixExists = > Some ( Box ::new ( this ) ) ,
2023-03-03 17:49:46 +00:00
this @ PlannerError ::Wsl1 = > Some ( Box ::new ( this ) ) ,
2022-12-12 20:20:50 +00:00
}
}
2022-10-14 22:14:03 +00:00
}
2023-03-03 22:20:17 +00:00
#[ cfg(feature = " diagnostics " ) ]
impl crate ::diagnostics ::ErrorDiagnostic for PlannerError {
fn diagnostic ( & self ) -> String {
let static_str : & 'static str = ( self ) . into ( ) ;
return static_str . to_string ( ) ;
}
}