diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a938dbb..a016630 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,33 @@ jobs: path: | result/bin/harmonic + RunX86Linux: + runs-on: ubuntu-latest + needs: BuildX86Linux + steps: + - uses: actions/download-artifact@v3 + with: + name: harmonic-x86_64-linux + - name: Set executable + run: chmod +x ./harmonic + - name: Initial install + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install linux-multi --no-confirm + - name: Test run + run: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + nix run nixpkgs#fortune + - name: Initial uninstall + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm + - name: Repeated install + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install linux-multi --no-confirm + - name: Repeated test run + run: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + nix run nixpkgs#fortune + - name: Repeated uninstall + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm + + BuildX86Darwin: runs-on: macos-latest steps: @@ -99,3 +126,30 @@ jobs: name: harmonic-x86_64-darwin path: | result/bin/harmonic + + RunX86Darwin: + runs-on: macos-latest + needs: BuildX86Darwin + steps: + - uses: actions/download-artifact@v3 + with: + name: harmonic-x86_64-darwin + - name: Set executable + run: chmod +x ./harmonic + - name: Initial install + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install darwin-multi --no-confirm + - name: Test run + run: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + nix run nixpkgs#fortune + - name: Initial uninstall + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm + - name: Repeated install + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic install darwin-multi --no-confirm + - name: Repeated test run + run: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh + nix run nixpkgs#fortune + - name: Repeated uninstall + run: sudo RUST_LOG=harmonic=trace RUST_BACKTRACE=full ./harmonic uninstall --no-confirm + \ No newline at end of file diff --git a/flake.nix b/flake.nix index 379df8b..3672b02 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "riff"; + description = "harmonic"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; diff --git a/src/action/common/configure_nix_daemon_service.rs b/src/action/base/configure_nix_daemon_service.rs similarity index 94% rename from src/action/common/configure_nix_daemon_service.rs rename to src/action/base/configure_nix_daemon_service.rs index 5e810fd..900a2a4 100644 --- a/src/action/common/configure_nix_daemon_service.rs +++ b/src/action/base/configure_nix_daemon_service.rs @@ -132,6 +132,15 @@ impl Action for ConfigureNixDaemonService { execute_command(Command::new("systemctl").arg("daemon-reload")) .await .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; + + execute_command( + Command::new("systemctl") + .arg("enable") + .arg("--now") + .arg("nix-daemon.socket"), + ) + .await + .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; }, }; @@ -181,11 +190,11 @@ impl Action for ConfigureNixDaemonService { .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; }, _ => { - execute_command(Command::new("systemctl").args(["disable", SOCKET_SRC])) + execute_command(Command::new("systemctl").args(["disable", SOCKET_SRC, "--now"])) .await .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; - execute_command(Command::new("systemctl").args(["disable", SERVICE_SRC])) + execute_command(Command::new("systemctl").args(["disable", SERVICE_SRC, "--now"])) .await .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; @@ -212,6 +221,10 @@ impl Action for ConfigureNixDaemonService { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_directory.rs b/src/action/base/create_directory.rs similarity index 99% rename from src/action/common/create_directory.rs rename to src/action/base/create_directory.rs index b67fe76..e40edda 100644 --- a/src/action/common/create_directory.rs +++ b/src/action/base/create_directory.rs @@ -214,6 +214,10 @@ impl Action for CreateDirectory { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_file.rs b/src/action/base/create_file.rs similarity index 98% rename from src/action/common/create_file.rs rename to src/action/base/create_file.rs index 08b4c54..9b462f2 100644 --- a/src/action/common/create_file.rs +++ b/src/action/base/create_file.rs @@ -188,6 +188,10 @@ impl Action for CreateFile { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_group.rs b/src/action/base/create_group.rs similarity index 98% rename from src/action/common/create_group.rs rename to src/action/base/create_group.rs index 23108d2..8e60cf9 100644 --- a/src/action/common/create_group.rs +++ b/src/action/base/create_group.rs @@ -175,6 +175,10 @@ impl Action for CreateGroup { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_or_append_file.rs b/src/action/base/create_or_append_file.rs similarity index 99% rename from src/action/common/create_or_append_file.rs rename to src/action/base/create_or_append_file.rs index 81ad802..d4c6e52 100644 --- a/src/action/common/create_or_append_file.rs +++ b/src/action/base/create_or_append_file.rs @@ -223,6 +223,10 @@ impl Action for CreateOrAppendFile { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_user.rs b/src/action/base/create_user.rs similarity index 99% rename from src/action/common/create_user.rs rename to src/action/base/create_user.rs index c3f108f..cf1704e 100644 --- a/src/action/common/create_user.rs +++ b/src/action/base/create_user.rs @@ -269,6 +269,10 @@ impl Action for CreateUser { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/fetch_nix.rs b/src/action/base/fetch_nix.rs similarity index 97% rename from src/action/common/fetch_nix.rs rename to src/action/base/fetch_nix.rs index 47f7349..e1f111b 100644 --- a/src/action/common/fetch_nix.rs +++ b/src/action/base/fetch_nix.rs @@ -115,6 +115,10 @@ impl Action for FetchNix { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/base/mod.rs b/src/action/base/mod.rs new file mode 100644 index 0000000..f956868 --- /dev/null +++ b/src/action/base/mod.rs @@ -0,0 +1,21 @@ +//! Base actions that themselves have no other actions as dependencies + +mod configure_nix_daemon_service; +mod create_directory; +mod create_file; +mod create_group; +mod create_or_append_file; +mod create_user; +mod fetch_nix; +mod move_unpacked_nix; +mod setup_default_profile; + +pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError}; +pub use create_directory::{CreateDirectory, CreateDirectoryError}; +pub use create_file::{CreateFile, CreateFileError}; +pub use create_group::{CreateGroup, CreateGroupError}; +pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError}; +pub use create_user::{CreateUser, CreateUserError}; +pub use fetch_nix::{FetchNix, FetchNixError}; +pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError}; +pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError}; diff --git a/src/action/common/move_unpacked_nix.rs b/src/action/base/move_unpacked_nix.rs similarity index 98% rename from src/action/common/move_unpacked_nix.rs rename to src/action/base/move_unpacked_nix.rs index 6638cec..d545e23 100644 --- a/src/action/common/move_unpacked_nix.rs +++ b/src/action/base/move_unpacked_nix.rs @@ -106,6 +106,10 @@ impl Action for MoveUnpackedNix { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/setup_default_profile.rs b/src/action/base/setup_default_profile.rs similarity index 98% rename from src/action/common/setup_default_profile.rs rename to src/action/base/setup_default_profile.rs index 38618f3..5e8efb3 100644 --- a/src/action/common/setup_default_profile.rs +++ b/src/action/base/setup_default_profile.rs @@ -181,6 +181,10 @@ impl Action for SetupDefaultProfile { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/configure_nix.rs b/src/action/common/configure_nix.rs index c088c7c..ac2c561 100644 --- a/src/action/common/configure_nix.rs +++ b/src/action/common/configure_nix.rs @@ -1,17 +1,15 @@ -use reqwest::Url; - use crate::{ action::{ - common::{ - ConfigureNixDaemonService, ConfigureShellProfile, PlaceChannelConfiguration, - PlaceNixConfiguration, SetupDefaultProfile, - }, + base::{ConfigureNixDaemonService, SetupDefaultProfile}, + common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration}, Action, ActionDescription, ActionState, }, channel_value::ChannelValue, BoxableError, CommonSettings, }; +use reqwest::Url; + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct ConfigureNix { setup_default_profile: SetupDefaultProfile, @@ -184,4 +182,8 @@ impl Action for ConfigureNix { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } diff --git a/src/action/common/configure_shell_profile.rs b/src/action/common/configure_shell_profile.rs index f84c5dc..b5b434d 100644 --- a/src/action/common/configure_shell_profile.rs +++ b/src/action/common/configure_shell_profile.rs @@ -1,13 +1,10 @@ +use crate::action::base::{CreateOrAppendFile, CreateOrAppendFileError}; +use crate::action::{Action, ActionDescription, ActionState}; +use crate::BoxableError; + use std::path::Path; - use tokio::task::{JoinError, JoinSet}; -use crate::action::common::{CreateOrAppendFile, CreateOrAppendFileError}; -use crate::{ - action::{Action, ActionDescription, ActionState}, - BoxableError, -}; - const PROFILE_TARGETS: &[&str] = &[ "/etc/bashrc", "/etc/profile.d/nix.sh", @@ -181,6 +178,10 @@ impl Action for ConfigureShellProfile { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_nix_tree.rs b/src/action/common/create_nix_tree.rs index ab4d1e3..3ebe209 100644 --- a/src/action/common/create_nix_tree.rs +++ b/src/action/common/create_nix_tree.rs @@ -1,4 +1,4 @@ -use crate::action::common::{CreateDirectory, CreateDirectoryError}; +use crate::action::base::{CreateDirectory, CreateDirectoryError}; use crate::action::{Action, ActionDescription, ActionState}; const PATHS: &[&str] = &[ @@ -134,6 +134,10 @@ impl Action for CreateNixTree { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/create_users_and_group.rs b/src/action/common/create_users_and_group.rs index 3b2cd62..a4fea57 100644 --- a/src/action/common/create_users_and_group.rs +++ b/src/action/common/create_users_and_group.rs @@ -1,14 +1,12 @@ -use tokio::task::{JoinError, JoinSet}; - use crate::CommonSettings; - use crate::{ action::{ - common::{CreateGroup, CreateGroupError, CreateUser, CreateUserError}, + base::{CreateGroup, CreateGroupError, CreateUser, CreateUserError}, Action, ActionDescription, ActionState, }, BoxableError, }; +use tokio::task::{JoinError, JoinSet}; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct CreateUsersAndGroup { @@ -229,6 +227,10 @@ impl Action for CreateUsersAndGroup { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/mod.rs b/src/action/common/mod.rs index ce9520a..baa2d29 100644 --- a/src/action/common/mod.rs +++ b/src/action/common/mod.rs @@ -1,35 +1,17 @@ /*! Actions which only call other base plugins. */ mod configure_nix; -mod configure_nix_daemon_service; mod configure_shell_profile; -mod create_directory; -mod create_file; -mod create_group; mod create_nix_tree; -mod create_or_append_file; -mod create_user; mod create_users_and_group; -mod fetch_nix; -mod move_unpacked_nix; mod place_channel_configuration; mod place_nix_configuration; mod provision_nix; -mod setup_default_profile; pub use configure_nix::ConfigureNix; -pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError}; pub use configure_shell_profile::ConfigureShellProfile; -pub use create_directory::{CreateDirectory, CreateDirectoryError}; -pub use create_file::{CreateFile, CreateFileError}; -pub use create_group::{CreateGroup, CreateGroupError}; pub use create_nix_tree::{CreateNixTree, CreateNixTreeError}; -pub use create_or_append_file::{CreateOrAppendFile, CreateOrAppendFileError}; -pub use create_user::{CreateUser, CreateUserError}; pub use create_users_and_group::{CreateUsersAndGroup, CreateUsersAndGroupError}; -pub use fetch_nix::{FetchNix, FetchNixError}; -pub use move_unpacked_nix::{MoveUnpackedNix, MoveUnpackedNixError}; pub use place_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError}; pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError}; pub use provision_nix::{ProvisionNix, ProvisionNixError}; -pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError}; diff --git a/src/action/common/place_channel_configuration.rs b/src/action/common/place_channel_configuration.rs index ca424c1..d8eba90 100644 --- a/src/action/common/place_channel_configuration.rs +++ b/src/action/common/place_channel_configuration.rs @@ -1,11 +1,9 @@ -use reqwest::Url; - +use crate::action::base::{CreateFile, CreateFileError}; use crate::{ action::{Action, ActionDescription, ActionState}, BoxableError, }; - -use crate::action::common::{CreateFile, CreateFileError}; +use reqwest::Url; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct PlaceChannelConfiguration { @@ -130,6 +128,10 @@ impl Action for PlaceChannelConfiguration { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/place_nix_configuration.rs b/src/action/common/place_nix_configuration.rs index 1e513ef..e2240f6 100644 --- a/src/action/common/place_nix_configuration.rs +++ b/src/action/common/place_nix_configuration.rs @@ -1,7 +1,6 @@ +use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; use crate::action::{Action, ActionDescription, ActionState}; -use crate::action::common::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; - const NIX_CONF_FOLDER: &str = "/etc/nix"; const NIX_CONF: &str = "/etc/nix/nix.conf"; @@ -115,6 +114,10 @@ impl Action for PlaceNixConfiguration { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/common/provision_nix.rs b/src/action/common/provision_nix.rs index 60f9302..10fbbae 100644 --- a/src/action/common/provision_nix.rs +++ b/src/action/common/provision_nix.rs @@ -1,16 +1,13 @@ -use std::path::PathBuf; - -use tokio::task::JoinError; - -use crate::action::common::{ +use crate::action::base::{ CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, }; use crate::CommonSettings; - use crate::{ action::{Action, ActionDescription, ActionState}, BoxableError, }; +use std::path::PathBuf; +use tokio::task::JoinError; use super::{CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError}; @@ -168,6 +165,10 @@ impl Action for ProvisionNix { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/bootstrap_volume.rs b/src/action/darwin/bootstrap_volume.rs index ecdb447..f9f0530 100644 --- a/src/action/darwin/bootstrap_volume.rs +++ b/src/action/darwin/bootstrap_volume.rs @@ -106,6 +106,10 @@ impl Action for BootstrapVolume { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/create_apfs_volume.rs b/src/action/darwin/create_apfs_volume.rs index 7f37002..f92f174 100644 --- a/src/action/darwin/create_apfs_volume.rs +++ b/src/action/darwin/create_apfs_volume.rs @@ -1,12 +1,6 @@ -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; -use tokio::process::Command; - use crate::{ action::{ - common::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError}, + base::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError}, darwin::{ BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects, CreateSyntheticObjectsError, CreateVolume, CreateVolumeError, EnableOwnership, @@ -17,6 +11,11 @@ use crate::{ }, BoxableError, }; +use std::{ + path::{Path, PathBuf}, + time::Duration, +}; +use tokio::process::Command; pub const NIX_VOLUME_MOUNTD_DEST: &str = "/Library/LaunchDaemons/org.nixos.darwin-store.plist"; @@ -283,6 +282,10 @@ impl Action for CreateApfsVolume { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/create_synthetic_objects.rs b/src/action/darwin/create_synthetic_objects.rs index 94a43e4..97c2604 100644 --- a/src/action/darwin/create_synthetic_objects.rs +++ b/src/action/darwin/create_synthetic_objects.rs @@ -98,6 +98,10 @@ impl Action for CreateSyntheticObjects { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/create_volume.rs b/src/action/darwin/create_volume.rs index 49a7899..de02144 100644 --- a/src/action/darwin/create_volume.rs +++ b/src/action/darwin/create_volume.rs @@ -130,6 +130,10 @@ impl Action for CreateVolume { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/enable_ownership.rs b/src/action/darwin/enable_ownership.rs index e0cf1d6..06fa7dd 100644 --- a/src/action/darwin/enable_ownership.rs +++ b/src/action/darwin/enable_ownership.rs @@ -108,6 +108,10 @@ impl Action for EnableOwnership { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/encrypt_volume.rs b/src/action/darwin/encrypt_volume.rs index ab70689..f411b65 100644 --- a/src/action/darwin/encrypt_volume.rs +++ b/src/action/darwin/encrypt_volume.rs @@ -182,6 +182,10 @@ impl Action for EncryptVolume { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/kickstart_launchctl_service.rs b/src/action/darwin/kickstart_launchctl_service.rs index c98eed8..2566ae1 100644 --- a/src/action/darwin/kickstart_launchctl_service.rs +++ b/src/action/darwin/kickstart_launchctl_service.rs @@ -96,6 +96,10 @@ impl Action for KickstartLaunchctlService { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/darwin/unmount_volume.rs b/src/action/darwin/unmount_volume.rs index 834cea3..70cad7c 100644 --- a/src/action/darwin/unmount_volume.rs +++ b/src/action/darwin/unmount_volume.rs @@ -117,6 +117,10 @@ impl Action for UnmountVolume { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/linux/create_systemd_sysext.rs b/src/action/linux/create_systemd_sysext.rs index 36c617a..b4029a7 100644 --- a/src/action/linux/create_systemd_sysext.rs +++ b/src/action/linux/create_systemd_sysext.rs @@ -1,10 +1,9 @@ -use std::path::{Path, PathBuf}; - -use crate::action::common::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; +use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError}; use crate::{ action::{Action, ActionDescription, ActionState}, BoxableError, }; +use std::path::{Path, PathBuf}; const PATHS: &[&str] = &[ "usr", @@ -185,6 +184,10 @@ impl Action for CreateSystemdSysext { *action_state = ActionState::Uncompleted; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/linux/start_systemd_unit.rs b/src/action/linux/start_systemd_unit.rs index 7f36f57..33c45d7 100644 --- a/src/action/linux/start_systemd_unit.rs +++ b/src/action/linux/start_systemd_unit.rs @@ -90,14 +90,22 @@ impl Action for StartSystemdUnit { tracing::debug!("Stopping systemd unit"); // TODO(@Hoverbear): Handle proxy vars - execute_command(Command::new("systemctl").arg("stop").arg(format!("{unit}"))) - .await - .map_err(|e| StartSystemdUnitError::Command(e).boxed())?; + execute_command( + Command::new("systemctl") + .arg("disable") + .arg(format!("{unit}")), + ) + .await + .map_err(|e| StartSystemdUnitError::Command(e).boxed())?; tracing::trace!("Stopped systemd unit"); *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/linux/systemd_sysext_merge.rs b/src/action/linux/systemd_sysext_merge.rs index 332192f..e021c2f 100644 --- a/src/action/linux/systemd_sysext_merge.rs +++ b/src/action/linux/systemd_sysext_merge.rs @@ -1,13 +1,10 @@ -use std::path::PathBuf; - -use tokio::process::Command; - use crate::execute_command; - use crate::{ action::{Action, ActionDescription, ActionState}, BoxableError, }; +use std::path::PathBuf; +use tokio::process::Command; #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct SystemdSysextMerge { @@ -102,6 +99,10 @@ impl Action for SystemdSysextMerge { *action_state = ActionState::Completed; Ok(()) } + + fn action_state(&self) -> ActionState { + self.action_state + } } #[derive(Debug, thiserror::Error)] diff --git a/src/action/mod.rs b/src/action/mod.rs index 508e49d..62b3751 100644 --- a/src/action/mod.rs +++ b/src/action/mod.rs @@ -1,3 +1,4 @@ +pub mod base; pub mod common; pub mod darwin; pub mod linux; @@ -13,11 +14,12 @@ pub trait Action: Send + Sync + std::fmt::Debug + dyn_clone::DynClone { // They should also have an `async fn plan(args...) -> Result, Box>;` async fn execute(&mut self) -> Result<(), Box>; async fn revert(&mut self) -> Result<(), Box>; + fn action_state(&self) -> ActionState; } dyn_clone::clone_trait_object!(Action); -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy)] pub enum ActionState { Completed, // Only applicable to meta-actions that start multiple sub-actions. diff --git a/src/cli/subcommand/install.rs b/src/cli/subcommand/install.rs index 38f1fc8..2bbb32b 100644 --- a/src/cli/subcommand/install.rs +++ b/src/cli/subcommand/install.rs @@ -1,6 +1,9 @@ -use std::{path::PathBuf, process::ExitCode}; +use std::{ + path::{Path, PathBuf}, + process::ExitCode, +}; -use crate::BuiltinPlanner; +use crate::{action::ActionState, plan::RECEIPT_LOCATION, BuiltinPlanner, InstallPlan, Planner}; use clap::{ArgAction, Parser}; use eyre::{eyre, WrapErr}; @@ -8,7 +11,7 @@ use crate::{cli::CommandExecute, interaction}; /// Execute an install (possibly using an existing plan) #[derive(Debug, Parser)] -#[command(args_conflicts_with_subcommands = true)] +#[command(args_conflicts_with_subcommands = true, arg_required_else_help = true)] pub struct Install { #[clap( long, @@ -29,7 +32,7 @@ pub struct Install { pub plan: Option, #[clap(subcommand)] - pub planner: BuiltinPlanner, + pub planner: Option, } #[async_trait::async_trait] @@ -43,28 +46,71 @@ impl CommandExecute for Install { explain, } = self; - let mut plan = match &plan { - Some(plan_path) => { - let install_plan_string = tokio::fs::read_to_string(&plan_path) + let existing_receipt: Option = match Path::new(RECEIPT_LOCATION).exists() { + true => { + let install_plan_string = tokio::fs::read_to_string(&RECEIPT_LOCATION) .await .wrap_err("Reading plan")?; + Some(serde_json::from_str(&install_plan_string)?) + }, + false => None, + }; + + let mut install_plan = match (planner, plan) { + (Some(planner), None) => { + let chosen_planner: Box = planner.clone().boxed(); + + match existing_receipt { + Some(existing_receipt) => { + if existing_receipt.planner.typetag_name() != chosen_planner.typetag_name() { + return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}` which used a different planner, try uninstalling the existing install")) + } + if existing_receipt.planner.settings().map_err(|e| eyre!(e))? != chosen_planner.settings().map_err(|e| eyre!(e))? { + return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}` which used different planner settings, try uninstalling the existing install")) + } + if existing_receipt.actions.iter().all(|v| v.action_state() == ActionState::Completed) { + return Err(eyre!("Found existing plan in `{RECEIPT_LOCATION}`, with the same settings, already completed, try uninstalling and reinstalling if Nix isn't working")) + } + existing_receipt + } , + None => { + planner.plan().await.map_err(|e| eyre!(e))? + }, + } + }, + (None, Some(plan_path)) => { + let install_plan_string = tokio::fs::read_to_string(&plan_path) + .await + .wrap_err("Reading plan")?; serde_json::from_str(&install_plan_string)? }, - None => planner.plan().await.map_err(|e| eyre!(e))?, + (None, None) => return Err(eyre!("`--plan` or a planner is required")), + (Some(_), Some(_)) => return Err(eyre!("`--plan` conflicts with passing a planner, a planner creates plans, so passing an existing plan doesn't make sense")), }; if !no_confirm { - if !interaction::confirm(plan.describe_execute(explain).map_err(|e| eyre!(e))?).await? { + if !interaction::confirm( + install_plan + .describe_execute(explain) + .map_err(|e| eyre!(e))?, + ) + .await? + { interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; } } - if let Err(err) = plan.install().await { - tracing::error!("{:?}", eyre!(err)); - if !interaction::confirm(plan.describe_revert(explain)).await? { - interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; + if let Err(err) = install_plan.install().await { + let error = eyre!(err).wrap_err("Install failure"); + if !no_confirm { + tracing::error!("{:?}", error); + if !interaction::confirm(install_plan.describe_revert(explain)).await? { + interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await; + } + install_plan.revert().await? + } else { + return Err(error); } - plan.revert().await? } Ok(ExitCode::SUCCESS) diff --git a/src/cli/subcommand/plan.rs b/src/cli/subcommand/plan.rs index 96cdd43..41c3796 100644 --- a/src/cli/subcommand/plan.rs +++ b/src/cli/subcommand/plan.rs @@ -9,19 +9,19 @@ use crate::cli::CommandExecute; /// Plan an install that can be repeated on an identical host later #[derive(Debug, Parser)] -#[command(multicall = true)] +#[command(args_conflicts_with_subcommands = true, arg_required_else_help = true)] pub struct Plan { #[clap(subcommand)] pub planner: Option, #[clap(env = "HARMONIC_PLAN")] - pub plan: PathBuf, + pub output: PathBuf, } #[async_trait::async_trait] impl CommandExecute for Plan { #[tracing::instrument(skip_all, fields())] async fn execute(self) -> eyre::Result { - let Self { planner, plan } = self; + let Self { planner, output } = self; let planner = match planner { Some(planner) => planner, @@ -33,7 +33,7 @@ impl CommandExecute for Plan { let install_plan = planner.plan().await.map_err(|e| eyre::eyre!(e))?; let json = serde_json::to_string_pretty(&install_plan)?; - tokio::fs::write(plan, json) + tokio::fs::write(output, json) .await .wrap_err("Writing plan")?; diff --git a/src/cli/subcommand/uninstall.rs b/src/cli/subcommand/uninstall.rs index af242d7..5f2db68 100644 --- a/src/cli/subcommand/uninstall.rs +++ b/src/cli/subcommand/uninstall.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, process::ExitCode}; -use crate::InstallPlan; +use crate::{plan::RECEIPT_LOCATION, InstallPlan}; use clap::{ArgAction, Parser}; use eyre::WrapErr; @@ -23,7 +23,7 @@ pub struct Uninstall { global = true )] pub explain: bool, - #[clap(default_value = "/nix/receipt.json")] + #[clap(default_value = RECEIPT_LOCATION)] pub receipt: PathBuf, } diff --git a/src/plan.rs b/src/plan.rs index c661787..a1aaee1 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -8,6 +8,8 @@ use crate::{ HarmonicError, }; +pub const RECEIPT_LOCATION: &str = "/nix/receipt.json"; + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct InstallPlan { pub(crate) actions: Vec>, @@ -43,7 +45,7 @@ impl InstallPlan { }, planner = planner.typetag_name(), plan_settings = planner - .describe()? + .settings()? .into_iter() .map(|(k, v)| format!("* {k}: {v}", k = k.bold().white())) .collect::>() @@ -166,7 +168,7 @@ async fn write_receipt(plan: InstallPlan) -> Result<(), HarmonicError> { tokio::fs::create_dir_all("/nix") .await .map_err(|e| HarmonicError::RecordingReceipt(PathBuf::from("/nix"), e))?; - let install_receipt_path = PathBuf::from("/nix/receipt.json"); + let install_receipt_path = PathBuf::from(RECEIPT_LOCATION); let self_json = serde_json::to_string_pretty(&plan).map_err(HarmonicError::SerializingReceipt)?; tokio::fs::write(&install_receipt_path, self_json) diff --git a/src/planner/darwin/multi.rs b/src/planner/darwin/multi.rs index e5cb3ba..14f7c88 100644 --- a/src/planner/darwin/multi.rs +++ b/src/planner/darwin/multi.rs @@ -118,7 +118,7 @@ impl Planner for DarwinMulti { }) } - fn describe( + fn settings( &self, ) -> Result, Box> { let Self { diff --git a/src/planner/linux/multi.rs b/src/planner/linux/multi.rs index 54aa3aa..bcf3e67 100644 --- a/src/planner/linux/multi.rs +++ b/src/planner/linux/multi.rs @@ -1,13 +1,12 @@ -use std::collections::HashMap; - use crate::{ action::{ - common::{ConfigureNix, CreateDirectory, ProvisionNix}, - linux::StartSystemdUnit, + base::CreateDirectory, + common::{ConfigureNix, ProvisionNix}, }, planner::Planner, BuiltinPlanner, CommonSettings, InstallPlan, }; +use std::collections::HashMap; #[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)] pub struct LinuxMulti { @@ -31,12 +30,11 @@ impl Planner for LinuxMulti { Box::new(CreateDirectory::plan("/nix", None, None, 0o0755, true).await?), Box::new(ProvisionNix::plan(self.settings.clone()).await?), Box::new(ConfigureNix::plan(self.settings).await?), - Box::new(StartSystemdUnit::plan("nix-daemon.socket".into()).await?), ], }) } - fn describe( + fn settings( &self, ) -> Result, Box> { let Self { settings } = self; diff --git a/src/planner/mod.rs b/src/planner/mod.rs index 4dd3dfa..f3a2bf0 100644 --- a/src/planner/mod.rs +++ b/src/planner/mod.rs @@ -13,9 +13,15 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone { where Self: Sized; async fn plan(self) -> Result>; - fn describe( + fn settings( &self, ) -> Result, Box>; + fn boxed(self) -> Box + where + Self: Sized + 'static, + { + Box::new(self) + } } dyn_clone::clone_trait_object!(Planner); @@ -56,6 +62,13 @@ impl BuiltinPlanner { BuiltinPlanner::SteamDeck(planner) => planner.plan().await, } } + pub fn boxed(self) -> Box { + match self { + BuiltinPlanner::LinuxMulti(i) => i.boxed(), + BuiltinPlanner::DarwinMulti(i) => i.boxed(), + BuiltinPlanner::SteamDeck(i) => i.boxed(), + } + } } #[derive(thiserror::Error, Debug)] diff --git a/src/planner/specific/steam_deck.rs b/src/planner/specific/steam_deck.rs index 99ee2bc..628f6e0 100644 --- a/src/planner/specific/steam_deck.rs +++ b/src/planner/specific/steam_deck.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use crate::{ action::{ - common::{CreateDirectory, ProvisionNix}, + base::CreateDirectory, + common::ProvisionNix, linux::{CreateSystemdSysext, StartSystemdUnit}, }, planner::Planner, @@ -36,7 +37,7 @@ impl Planner for SteamDeck { }) } - fn describe( + fn settings( &self, ) -> Result, Box> { let Self { settings } = self;