diff --git a/src/actions/base/create_directory.rs b/src/actions/base/create_directory.rs index 2ad5206..575822e 100644 --- a/src/actions/base/create_directory.rs +++ b/src/actions/base/create_directory.rs @@ -14,6 +14,7 @@ pub struct CreateDirectory { group: String, mode: u32, action_state: ActionState, + force_prune_on_revert: bool, } impl CreateDirectory { @@ -23,23 +24,37 @@ impl CreateDirectory { user: String, group: String, mode: u32, - force: bool, + force_prune_on_revert: bool, ) -> Result { let path = path.as_ref(); - if path.exists() && !force { - return Err(CreateDirectoryError::Exists(std::io::Error::new( - std::io::ErrorKind::AlreadyExists, - format!("Directory `{}` already exists", path.display()), - ))); - } + let action_state = if path.exists() { + let metadata = tokio::fs::metadata(path) + .await + .map_err(|e| CreateDirectoryError::GettingMetadata(path.to_path_buf(), e))?; + if metadata.is_dir() { + // TODO: Validate owner/group... + ActionState::Completed + } else { + return Err(CreateDirectoryError::Exists(std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!( + "Path `{}` already exists and is not directory", + path.display() + ), + ))); + } + } else { + ActionState::Uncompleted + }; Ok(Self { path: path.to_path_buf(), user, group, mode, - action_state: ActionState::Uncompleted, + force_prune_on_revert, + action_state, }) } } @@ -54,6 +69,7 @@ impl Actionable for CreateDirectory { user, group, mode, + force_prune_on_revert: _, action_state: _, } = &self; if self.action_state == ActionState::Completed { @@ -81,6 +97,7 @@ impl Actionable for CreateDirectory { user, group, mode, + force_prune_on_revert: _, action_state, } = self; if *action_state == ActionState::Completed { @@ -118,13 +135,22 @@ impl Actionable for CreateDirectory { user: _, group: _, mode: _, + force_prune_on_revert, action_state: _, } = &self; if self.action_state == ActionState::Uncompleted { vec![] } else { vec![ActionDescription::new( - format!("Remove the directory `{}`", path.display()), + format!( + "Remove the directory `{}`{}", + path.display(), + if *force_prune_on_revert { + "" + } else { + " if no other contents exists" + } + ), vec![], )] } @@ -142,6 +168,7 @@ impl Actionable for CreateDirectory { user: _, group: _, mode: _, + force_prune_on_revert, action_state, } = self; if *action_state == ActionState::Uncompleted { @@ -151,9 +178,18 @@ impl Actionable for CreateDirectory { tracing::debug!("Removing directory"); tracing::trace!(path = %path.display(), "Removing directory"); - remove_dir_all(path.clone()) - .await - .map_err(|e| Self::Error::Removing(path.clone(), e))?; + + let is_empty = path + .read_dir() + .map_err(|e| CreateDirectoryError::ReadDir(path.clone(), e))? + .next() + .is_some(); + match (is_empty, force_prune_on_revert) { + (true, _) | (false, true) => remove_dir_all(path.clone()) + .await + .map_err(|e| Self::Error::Removing(path.clone(), e))?, + (false, false) => {}, + }; tracing::trace!("Removed directory"); *action_state = ActionState::Uncompleted; @@ -185,6 +221,20 @@ pub enum CreateDirectoryError { #[serde(serialize_with = "crate::serialize_error_to_display")] std::io::Error, ), + #[error("Getting metadata for {0}`")] + GettingMetadata( + std::path::PathBuf, + #[source] + #[serde(serialize_with = "crate::serialize_error_to_display")] + std::io::Error, + ), + #[error("Reading directory `{0}``")] + ReadDir( + std::path::PathBuf, + #[source] + #[serde(serialize_with = "crate::serialize_error_to_display")] + std::io::Error, + ), #[error("Set mode `{0}` on `{1}`")] SetPermissions( u32, diff --git a/src/actions/base/mod.rs b/src/actions/base/mod.rs index 123bc43..b9ef9db 100644 --- a/src/actions/base/mod.rs +++ b/src/actions/base/mod.rs @@ -10,6 +10,7 @@ mod fetch_nix; mod move_unpacked_nix; mod setup_default_profile; mod start_systemd_unit; +mod systemd_sysext_merge; pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError}; pub use create_directory::{CreateDirectory, CreateDirectoryError}; @@ -21,3 +22,4 @@ pub use fetch_nix::{FetchNix, FetchNixError}; pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError}; pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError}; pub use start_systemd_unit::{StartSystemdUnit, StartSystemdUnitError}; +pub use systemd_sysext_merge::{SystemdSysextMerge, SystemdSysextMergeError}; diff --git a/src/actions/base/systemd_sysext_merge.rs b/src/actions/base/systemd_sysext_merge.rs new file mode 100644 index 0000000..5769111 --- /dev/null +++ b/src/actions/base/systemd_sysext_merge.rs @@ -0,0 +1,120 @@ +use std::path::PathBuf; + +use serde::Serialize; +use tokio::process::Command; + +use crate::execute_command; + +use crate::actions::{Action, ActionDescription, ActionState, Actionable}; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct SystemdSysextMerge { + device: PathBuf, + action_state: ActionState, +} + +impl SystemdSysextMerge { + #[tracing::instrument(skip_all)] + pub async fn plan(device: PathBuf) -> Result { + Ok(Self { + device, + action_state: ActionState::Uncompleted, + }) + } +} + +#[async_trait::async_trait] +impl Actionable for SystemdSysextMerge { + type Error = SystemdSysextMergeError; + + fn describe_execute(&self) -> Vec { + let Self { + action_state, + device, + } = self; + if *action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Run `systemd-sysext merge `{}`", device.display()), + vec![], + )] + } + } + + #[tracing::instrument(skip_all, fields( + device = %self.device.display(), + ))] + async fn execute(&mut self) -> Result<(), Self::Error> { + let Self { + device, + action_state, + } = self; + if *action_state == ActionState::Completed { + tracing::trace!("Already completed: Merging systemd-sysext"); + return Ok(()); + } + tracing::debug!("Merging systemd-sysext"); + + execute_command(Command::new("systemd-sysext").arg("merge").arg(device)) + .await + .map_err(SystemdSysextMergeError::Command)?; + + tracing::trace!("Merged systemd-sysext"); + *action_state = ActionState::Completed; + Ok(()) + } + + fn describe_revert(&self) -> Vec { + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + "Stop the systemd Nix service and socket".to_string(), + vec![ + "The `nix` command line tool communicates with a running Nix daemon managed by your init system".to_string() + ] + )] + } + } + + #[tracing::instrument(skip_all, fields( + device = %self.device.display(), + ))] + async fn revert(&mut self) -> Result<(), Self::Error> { + let Self { + device, + action_state, + } = self; + if *action_state == ActionState::Uncompleted { + tracing::trace!("Already reverted: Stopping systemd unit"); + return Ok(()); + } + tracing::debug!("Unmrging systemd-sysext"); + + // TODO(@Hoverbear): Handle proxy vars + execute_command(Command::new("systemd-sysext").arg("unmerge").arg(device)) + .await + .map_err(SystemdSysextMergeError::Command)?; + + tracing::trace!("Unmerged systemd-sysext"); + *action_state = ActionState::Completed; + Ok(()) + } +} + +impl From for Action { + fn from(v: SystemdSysextMerge) -> Self { + Action::SystemdSysextMerge(v) + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum SystemdSysextMergeError { + #[error("Failed to execute command")] + Command( + #[source] + #[serde(serialize_with = "crate::serialize_error_to_display")] + std::io::Error, + ), +} diff --git a/src/actions/meta/create_nix_tree.rs b/src/actions/meta/create_nix_tree.rs index 88b1862..82d9d0c 100644 --- a/src/actions/meta/create_nix_tree.rs +++ b/src/actions/meta/create_nix_tree.rs @@ -27,12 +27,12 @@ pub struct CreateNixTree { impl CreateNixTree { #[tracing::instrument(skip_all)] - pub async fn plan(force: bool) -> Result { + pub async fn plan() -> Result { let mut create_directories = Vec::default(); for path in PATHS { // We use `create_dir` over `create_dir_all` to ensure we always set permissions right create_directories.push( - CreateDirectory::plan(path, "root".into(), "root".into(), 0o0755, force).await?, + CreateDirectory::plan(path, "root".into(), "root".into(), 0o0755, false).await?, ) } diff --git a/src/actions/meta/create_systemd_sysext.rs b/src/actions/meta/create_systemd_sysext.rs new file mode 100644 index 0000000..93d09a7 --- /dev/null +++ b/src/actions/meta/create_systemd_sysext.rs @@ -0,0 +1,216 @@ +use serde::Serialize; +use std::path::{Path, PathBuf}; + +use crate::actions::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; +use crate::actions::{Action, ActionDescription, ActionState, Actionable}; + +const PATHS: &[&str] = &[ + "usr", + "usr/lib", + "usr/lib/extension-release.d", + "usr/lib/systemd", + "usr/lib/systemd/system", +]; + +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct CreateSystemdSysext { + destination: PathBuf, + create_directories: Vec, + create_extension_release: CreateFile, + create_bind_mount_unit: CreateFile, + action_state: ActionState, +} + +impl CreateSystemdSysext { + #[tracing::instrument(skip_all)] + pub async fn plan(destination: impl AsRef) -> Result { + let destination = destination.as_ref(); + + let mut create_directories = vec![ + CreateDirectory::plan(destination, "root".into(), "root".into(), 0o0755, true).await?, + ]; + for path in PATHS { + create_directories.push( + CreateDirectory::plan( + destination.join(path), + "root".into(), + "root".into(), + 0o0755, + false, + ) + .await?, + ) + } + + let version_id = tokio::fs::read_to_string("/etc/os-release") + .await + .map(|buf| { + buf.lines() + .find_map(|line| match line.starts_with("VERSION_ID") { + true => line.rsplit("=").next().map(|inner| inner.to_owned()), + false => None, + }) + }) + .map_err(CreateSystemdSysextError::ReadingOsRelease)? + .ok_or(CreateSystemdSysextError::NoVersionId)?; + let extension_release_buf = + format!("SYSEXT_LEVEL=1.0\nID=steamos\nVERSION_ID={version_id}"); + let create_extension_release = CreateFile::plan( + destination.join("usr/lib/extension-release.d/extension-release.nix"), + "root".into(), + "root".into(), + 0o0755, + extension_release_buf, + false, + ) + .await?; + + let create_bind_mount_buf = format!( + " + [Mount]\n\ + What={}\n\ + Where=/nix\n\ + Type=none\n\ + Options=bind\n\ + ", + destination.display(), + ); + let create_bind_mount_unit = CreateFile::plan( + destination.join("usr/lib/systemd/system/nix.mount"), + "root".into(), + "root".into(), + 0o0755, + create_bind_mount_buf, + false, + ) + .await?; + + Ok(Self { + destination: destination.to_path_buf(), + create_directories, + create_extension_release, + create_bind_mount_unit, + action_state: ActionState::Uncompleted, + }) + } +} + +#[async_trait::async_trait] +impl Actionable for CreateSystemdSysext { + type Error = CreateSystemdSysextError; + + fn describe_execute(&self) -> Vec { + let Self { + action_state: _, + destination, + create_bind_mount_unit: _, + create_directories: _, + create_extension_release: _, + } = &self; + if self.action_state == ActionState::Completed { + vec![] + } else { + vec![ActionDescription::new( + format!("Create a systemd sysext at `{}`", destination.display()), + vec![format!( + "Create a writable, persistent systemd system extension.", + )], + )] + } + } + + #[tracing::instrument(skip_all, fields(destination,))] + async fn execute(&mut self) -> Result<(), Self::Error> { + let Self { + destination: _, + action_state, + create_directories, + create_extension_release, + create_bind_mount_unit, + } = self; + if *action_state == ActionState::Completed { + tracing::trace!("Already completed: Creating sysext"); + return Ok(()); + } + tracing::debug!("Creating sysext"); + + for create_directory in create_directories { + create_directory.execute().await?; + } + create_extension_release.execute().await?; + create_bind_mount_unit.execute().await?; + + tracing::trace!("Created sysext"); + *action_state = ActionState::Completed; + Ok(()) + } + + fn describe_revert(&self) -> Vec { + let Self { + destination, + action_state: _, + create_directories: _, + create_extension_release: _, + create_bind_mount_unit: _, + } = &self; + if self.action_state == ActionState::Uncompleted { + vec![] + } else { + vec![ActionDescription::new( + format!("Remove the sysext located at `{}`", destination.display()), + vec![], + )] + } + } + + #[tracing::instrument(skip_all, fields(destination,))] + async fn revert(&mut self) -> Result<(), Self::Error> { + let Self { + destination: _, + action_state, + create_directories, + create_extension_release, + create_bind_mount_unit, + } = self; + if *action_state == ActionState::Uncompleted { + tracing::trace!("Already reverted: Removing sysext"); + return Ok(()); + } + tracing::debug!("Removing sysext"); + + create_bind_mount_unit.revert().await?; + + create_extension_release.revert().await?; + + for create_directory in create_directories.iter_mut().rev() { + create_directory.revert().await?; + } + + tracing::trace!("Removed sysext"); + *action_state = ActionState::Uncompleted; + Ok(()) + } +} + +impl From for Action { + fn from(v: CreateSystemdSysext) -> Self { + Action::CreateSystemdSysext(v) + } +} + +#[derive(Debug, thiserror::Error, Serialize)] +pub enum CreateSystemdSysextError { + #[error(transparent)] + CreateDirectory(#[from] CreateDirectoryError), + #[error(transparent)] + CreateFile(#[from] CreateFileError), + #[error("Reading /etc/os-release")] + ReadingOsRelease( + #[source] + #[from] + #[serde(serialize_with = "crate::serialize_error_to_display")] + std::io::Error, + ), + #[error("No `VERSION_ID` line in /etc/os-release")] + NoVersionId, +} diff --git a/src/actions/meta/mod.rs b/src/actions/meta/mod.rs index 3d64346..7a0f544 100644 --- a/src/actions/meta/mod.rs +++ b/src/actions/meta/mod.rs @@ -3,6 +3,7 @@ mod configure_nix; mod configure_shell_profile; mod create_nix_tree; +mod create_systemd_sysext; mod create_users_and_group; mod place_channel_configuration; mod place_nix_configuration; @@ -12,6 +13,7 @@ mod start_nix_daemon; pub use configure_nix::{ConfigureNix, ConfigureNixError}; pub use configure_shell_profile::{ConfigureShellProfile, ConfigureShellProfileError}; pub use create_nix_tree::{CreateNixTree, CreateNixTreeError}; +pub use create_systemd_sysext::{CreateSystemdSysext, CreateSystemdSysextError}; pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError}; pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError}; pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError}; diff --git a/src/actions/meta/provision_nix.rs b/src/actions/meta/provision_nix.rs index 24a5f2d..8cf074b 100644 --- a/src/actions/meta/provision_nix.rs +++ b/src/actions/meta/provision_nix.rs @@ -27,8 +27,7 @@ impl ProvisionNix { #[tracing::instrument(skip_all)] pub async fn plan(settings: InstallSettings) -> Result { let create_nix_dir = - CreateDirectory::plan("/nix", "root".into(), "root".into(), 0o0755, settings.force) - .await?; + CreateDirectory::plan("/nix", "root".into(), "root".into(), 0o0755, true).await?; let fetch_nix = FetchNix::plan( settings.nix_package_url.clone(), @@ -36,7 +35,7 @@ impl ProvisionNix { ) .await?; let create_users_and_group = CreateUsersAndGroup::plan(settings.clone()).await?; - let create_nix_tree = CreateNixTree::plan(settings.force).await?; + let create_nix_tree = CreateNixTree::plan().await?; let move_unpacked_nix = MoveUnpackedNix::plan(PathBuf::from("/nix/temp-install-dir")).await?; Ok(Self { diff --git a/src/actions/mod.rs b/src/actions/mod.rs index a1d988b..d0d6bbd 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -6,19 +6,18 @@ use base::{ CreateDirectoryError, CreateFile, CreateFileError, CreateGroup, CreateGroupError, CreateOrAppendFile, CreateOrAppendFileError, CreateUser, CreateUserError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, SetupDefaultProfile, - SetupDefaultProfileError, + SetupDefaultProfileError, StartSystemdUnit, StartSystemdUnitError, SystemdSysextMerge, + SystemdSysextMergeError, }; use meta::{ ConfigureNix, ConfigureNixError, ConfigureShellProfile, ConfigureShellProfileError, - CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError, - PlaceChannelConfiguration, PlaceChannelConfigurationError, PlaceNixConfiguration, - PlaceNixConfigurationError, ProvisionNix, ProvisionNixError, StartNixDaemon, - StartNixDaemonError, + CreateNixTree, CreateNixTreeError, CreateSystemdSysext, CreateSystemdSysextError, + CreateUsersAndGroup, CreateUsersAndGroupError, PlaceChannelConfiguration, + PlaceChannelConfigurationError, PlaceNixConfiguration, PlaceNixConfigurationError, + ProvisionNix, ProvisionNixError, StartNixDaemon, StartNixDaemonError, }; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use self::base::{StartSystemdUnit, StartSystemdUnitError}; - #[async_trait::async_trait] pub trait Actionable: DeserializeOwned + Serialize + Into { type Error: std::error::Error + std::fmt::Debug + Serialize + Into; @@ -61,6 +60,7 @@ pub enum Action { ConfigureNix(ConfigureNix), ConfigureShellProfile(ConfigureShellProfile), CreateDirectory(CreateDirectory), + CreateSystemdSysext(CreateSystemdSysext), CreateFile(CreateFile), CreateGroup(CreateGroup), CreateOrAppendFile(CreateOrAppendFile), @@ -74,6 +74,7 @@ pub enum Action { SetupDefaultProfile(SetupDefaultProfile), StartNixDaemon(StartNixDaemon), StartSystemdUnit(StartSystemdUnit), + SystemdSysextMerge(SystemdSysextMerge), ProvisionNix(ProvisionNix), } @@ -94,6 +95,8 @@ pub enum ActionError { #[error(transparent)] CreateDirectory(#[from] CreateDirectoryError), #[error(transparent)] + CreateSystemdSysext(#[from] CreateSystemdSysextError), + #[error(transparent)] CreateFile(#[from] CreateFileError), #[error(transparent)] CreateGroup(#[from] CreateGroupError), @@ -120,6 +123,8 @@ pub enum ActionError { #[error(transparent)] StartSystemdUnit(#[from] StartSystemdUnitError), #[error(transparent)] + SystemdSysExtMerge(#[from] SystemdSysextMergeError), + #[error(transparent)] ProvisionNix(#[from] ProvisionNixError), } @@ -132,6 +137,7 @@ impl Actionable for Action { Action::ConfigureNix(i) => i.describe_execute(), Action::ConfigureShellProfile(i) => i.describe_execute(), Action::CreateDirectory(i) => i.describe_execute(), + Action::CreateSystemdSysext(i) => i.describe_execute(), Action::CreateFile(i) => i.describe_execute(), Action::CreateGroup(i) => i.describe_execute(), Action::CreateOrAppendFile(i) => i.describe_execute(), @@ -145,6 +151,7 @@ impl Actionable for Action { Action::SetupDefaultProfile(i) => i.describe_execute(), Action::StartNixDaemon(i) => i.describe_execute(), Action::StartSystemdUnit(i) => i.describe_execute(), + Action::SystemdSysextMerge(i) => i.describe_execute(), Action::ProvisionNix(i) => i.describe_execute(), } } @@ -155,6 +162,7 @@ impl Actionable for Action { Action::ConfigureNix(i) => i.execute().await?, Action::ConfigureShellProfile(i) => i.execute().await?, Action::CreateDirectory(i) => i.execute().await?, + Action::CreateSystemdSysext(i) => i.execute().await?, Action::CreateFile(i) => i.execute().await?, Action::CreateGroup(i) => i.execute().await?, Action::CreateOrAppendFile(i) => i.execute().await?, @@ -168,6 +176,7 @@ impl Actionable for Action { Action::SetupDefaultProfile(i) => i.execute().await?, Action::StartNixDaemon(i) => i.execute().await?, Action::StartSystemdUnit(i) => i.execute().await?, + Action::SystemdSysextMerge(i) => i.execute().await?, Action::ProvisionNix(i) => i.execute().await?, }; Ok(()) @@ -179,6 +188,7 @@ impl Actionable for Action { Action::ConfigureNix(i) => i.describe_revert(), Action::ConfigureShellProfile(i) => i.describe_revert(), Action::CreateDirectory(i) => i.describe_revert(), + Action::CreateSystemdSysext(i) => i.describe_revert(), Action::CreateFile(i) => i.describe_revert(), Action::CreateGroup(i) => i.describe_revert(), Action::CreateOrAppendFile(i) => i.describe_revert(), @@ -192,6 +202,7 @@ impl Actionable for Action { Action::SetupDefaultProfile(i) => i.describe_revert(), Action::StartNixDaemon(i) => i.describe_revert(), Action::StartSystemdUnit(i) => i.describe_revert(), + Action::SystemdSysextMerge(i) => i.describe_revert(), Action::ProvisionNix(i) => i.describe_revert(), } } @@ -202,6 +213,7 @@ impl Actionable for Action { Action::ConfigureNix(i) => i.revert().await?, Action::ConfigureShellProfile(i) => i.revert().await?, Action::CreateDirectory(i) => i.revert().await?, + Action::CreateSystemdSysext(i) => i.revert().await?, Action::CreateFile(i) => i.revert().await?, Action::CreateGroup(i) => i.revert().await?, Action::CreateOrAppendFile(i) => i.revert().await?, @@ -215,6 +227,7 @@ impl Actionable for Action { Action::SetupDefaultProfile(i) => i.revert().await?, Action::StartNixDaemon(i) => i.revert().await?, Action::StartSystemdUnit(i) => i.revert().await?, + Action::SystemdSysextMerge(i) => i.revert().await?, Action::ProvisionNix(i) => i.revert().await?, } Ok(()) diff --git a/src/plan.rs b/src/plan.rs index 27ef78c..31807be 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -1,10 +1,7 @@ use std::path::PathBuf; use crate::{ - actions::{ - meta::{ConfigureNix, ProvisionNix, StartNixDaemon}, - Action, ActionDescription, ActionError, Actionable, - }, + actions::{Action, ActionDescription, ActionError, Actionable}, planner::PlannerError, settings::InstallSettings, HarmonicError, Planner, diff --git a/src/planner/mod.rs b/src/planner/mod.rs index 8c7cbe2..4a85c31 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -2,9 +2,7 @@ mod darwin; mod linux; mod specific; -use std::{ffi::OsStr, str::FromStr}; - -use crate::{actions::ActionError, HarmonicError, InstallPlan, InstallSettings}; +use crate::{actions::ActionError, InstallPlan, InstallSettings}; #[derive(Debug, Clone, clap::ValueEnum, serde::Serialize, serde::Deserialize)] pub enum Planner { diff --git a/src/planner/specific/steam_deck.rs b/src/planner/specific/steam_deck.rs index 8fad970..9d6a053 100644 --- a/src/planner/specific/steam_deck.rs +++ b/src/planner/specific/steam_deck.rs @@ -1,4 +1,11 @@ -use crate::{planner::Plannable, Planner}; +use crate::{ + actions::{ + meta::{CreateSystemdSysext, ProvisionNix, StartNixDaemon}, + Action, ActionError, + }, + planner::Plannable, + InstallPlan, Planner, +}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] pub struct SteamDeck; @@ -11,7 +18,24 @@ impl Plannable for SteamDeck { async fn plan( settings: crate::InstallSettings, ) -> Result { - todo!() + Ok(InstallPlan { + planner: Self.into(), + settings: settings.clone(), + actions: vec![ + CreateSystemdSysext::plan("/var/lib/extensions") + .await + .map(Action::from) + .map_err(ActionError::from)?, + ProvisionNix::plan(settings.clone()) + .await + .map(Action::from) + .map_err(ActionError::from)?, + StartNixDaemon::plan() + .await + .map(Action::from) + .map_err(ActionError::from)?, + ], + }) } } diff --git a/src/settings.rs b/src/settings.rs index fb2fa44..e14e6d8 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,4 +1,4 @@ -use crate::{planner, Planner}; +use crate::planner; use target_lexicon::Triple; use url::Url;