Merge branch 'main' into hoverbear/ds-431-ctrlc-should-be-handled-and-terminate-us

This commit is contained in:
Ana Hobden 2022-11-10 08:55:44 -08:00
commit 8c77b6eb38
41 changed files with 342 additions and 106 deletions

View file

@ -82,6 +82,33 @@ jobs:
path: | path: |
result/bin/harmonic 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: BuildX86Darwin:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
@ -99,3 +126,30 @@ jobs:
name: harmonic-x86_64-darwin name: harmonic-x86_64-darwin
path: | path: |
result/bin/harmonic 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

View file

@ -1,5 +1,5 @@
{ {
description = "riff"; description = "harmonic";
inputs = { inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05"; nixpkgs.url = "github:nixos/nixpkgs/nixos-22.05";

View file

@ -148,6 +148,16 @@ impl Action for ConfigureNixDaemonService {
) )
.await .await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?; .map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
execute_command(
Command::new("systemctl")
.arg("enable")
.arg("--now")
.arg("nix-daemon.socket")
.stdin(std::process::Stdio::null()),
)
.await
.map_err(|e| ConfigureNixDaemonServiceError::Command(e).boxed())?;
}, },
}; };
@ -199,7 +209,7 @@ impl Action for ConfigureNixDaemonService {
_ => { _ => {
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
.args(["disable", SOCKET_SRC]) .args(["disable", SOCKET_SRC, "--now"])
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
@ -207,7 +217,7 @@ impl Action for ConfigureNixDaemonService {
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
.args(["disable", SERVICE_SRC]) .args(["disable", SERVICE_SRC, "--now"])
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
.await .await
@ -241,6 +251,10 @@ impl Action for ConfigureNixDaemonService {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -214,6 +214,10 @@ impl Action for CreateDirectory {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -188,6 +188,10 @@ impl Action for CreateFile {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -167,6 +167,10 @@ impl Action for CreateGroup {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -223,6 +223,10 @@ impl Action for CreateOrAppendFile {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -278,6 +278,10 @@ impl Action for CreateUser {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -115,6 +115,10 @@ impl Action for FetchNix {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

21
src/action/base/mod.rs Normal file
View file

@ -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};

View file

@ -106,6 +106,10 @@ impl Action for MoveUnpackedNix {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -183,6 +183,10 @@ impl Action for SetupDefaultProfile {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,17 +1,15 @@
use reqwest::Url;
use crate::{ use crate::{
action::{ action::{
common::{ base::{ConfigureNixDaemonService, SetupDefaultProfile},
ConfigureNixDaemonService, ConfigureShellProfile, PlaceChannelConfiguration, common::{ConfigureShellProfile, PlaceChannelConfiguration, PlaceNixConfiguration},
PlaceNixConfiguration, SetupDefaultProfile,
},
Action, ActionDescription, ActionState, Action, ActionDescription, ActionState,
}, },
channel_value::ChannelValue, channel_value::ChannelValue,
BoxableError, CommonSettings, BoxableError, CommonSettings,
}; };
use reqwest::Url;
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ConfigureNix { pub struct ConfigureNix {
setup_default_profile: SetupDefaultProfile, setup_default_profile: SetupDefaultProfile,
@ -184,4 +182,8 @@ impl Action for ConfigureNix {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }

View file

@ -1,13 +1,10 @@
use crate::action::base::{CreateOrAppendFile, CreateOrAppendFileError};
use crate::action::{Action, ActionDescription, ActionState};
use crate::BoxableError;
use std::path::Path; use std::path::Path;
use tokio::task::{JoinError, JoinSet}; use tokio::task::{JoinError, JoinSet};
use crate::action::common::{CreateOrAppendFile, CreateOrAppendFileError};
use crate::{
action::{Action, ActionDescription, ActionState},
BoxableError,
};
const PROFILE_TARGETS: &[&str] = &[ const PROFILE_TARGETS: &[&str] = &[
"/etc/bashrc", "/etc/bashrc",
"/etc/profile.d/nix.sh", "/etc/profile.d/nix.sh",
@ -181,6 +178,10 @@ impl Action for ConfigureShellProfile {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,4 +1,4 @@
use crate::action::common::{CreateDirectory, CreateDirectoryError}; use crate::action::base::{CreateDirectory, CreateDirectoryError};
use crate::action::{Action, ActionDescription, ActionState}; use crate::action::{Action, ActionDescription, ActionState};
const PATHS: &[&str] = &[ const PATHS: &[&str] = &[
@ -134,6 +134,10 @@ impl Action for CreateNixTree {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,14 +1,12 @@
use tokio::task::{JoinError, JoinSet};
use crate::CommonSettings; use crate::CommonSettings;
use crate::{ use crate::{
action::{ action::{
common::{CreateGroup, CreateGroupError, CreateUser, CreateUserError}, base::{CreateGroup, CreateGroupError, CreateUser, CreateUserError},
Action, ActionDescription, ActionState, Action, ActionDescription, ActionState,
}, },
BoxableError, BoxableError,
}; };
use tokio::task::{JoinError, JoinSet};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct CreateUsersAndGroup { pub struct CreateUsersAndGroup {
@ -229,6 +227,10 @@ impl Action for CreateUsersAndGroup {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,35 +1,17 @@
/*! Actions which only call other base plugins. */ /*! Actions which only call other base plugins. */
mod configure_nix; mod configure_nix;
mod configure_nix_daemon_service;
mod configure_shell_profile; mod configure_shell_profile;
mod create_directory;
mod create_file;
mod create_group;
mod create_nix_tree; mod create_nix_tree;
mod create_or_append_file;
mod create_user;
mod create_users_and_group; mod create_users_and_group;
mod fetch_nix;
mod move_unpacked_nix;
mod place_channel_configuration; mod place_channel_configuration;
mod place_nix_configuration; mod place_nix_configuration;
mod provision_nix; mod provision_nix;
mod setup_default_profile;
pub use configure_nix::ConfigureNix; pub use configure_nix::ConfigureNix;
pub use configure_nix_daemon_service::{ConfigureNixDaemonService, ConfigureNixDaemonServiceError};
pub use configure_shell_profile::ConfigureShellProfile; 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_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 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_channel_configuration::{PlaceChannelConfiguration, PlaceChannelConfigurationError};
pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError}; pub use place_nix_configuration::{PlaceNixConfiguration, PlaceNixConfigurationError};
pub use provision_nix::{ProvisionNix, ProvisionNixError}; pub use provision_nix::{ProvisionNix, ProvisionNixError};
pub use setup_default_profile::{SetupDefaultProfile, SetupDefaultProfileError};

View file

@ -1,11 +1,9 @@
use reqwest::Url; use crate::action::base::{CreateFile, CreateFileError};
use crate::{ use crate::{
action::{Action, ActionDescription, ActionState}, action::{Action, ActionDescription, ActionState},
BoxableError, BoxableError,
}; };
use reqwest::Url;
use crate::action::common::{CreateFile, CreateFileError};
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct PlaceChannelConfiguration { pub struct PlaceChannelConfiguration {
@ -130,6 +128,10 @@ impl Action for PlaceChannelConfiguration {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,7 +1,6 @@
use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
use crate::action::{Action, ActionDescription, ActionState}; use crate::action::{Action, ActionDescription, ActionState};
use crate::action::common::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
const NIX_CONF_FOLDER: &str = "/etc/nix"; const NIX_CONF_FOLDER: &str = "/etc/nix";
const NIX_CONF: &str = "/etc/nix/nix.conf"; const NIX_CONF: &str = "/etc/nix/nix.conf";
@ -115,6 +114,10 @@ impl Action for PlaceNixConfiguration {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,16 +1,13 @@
use std::path::PathBuf; use crate::action::base::{
use tokio::task::JoinError;
use crate::action::common::{
CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError, CreateDirectoryError, FetchNix, FetchNixError, MoveUnpackedNix, MoveUnpackedNixError,
}; };
use crate::CommonSettings; use crate::CommonSettings;
use crate::{ use crate::{
action::{Action, ActionDescription, ActionState}, action::{Action, ActionDescription, ActionState},
BoxableError, BoxableError,
}; };
use std::path::PathBuf;
use tokio::task::JoinError;
use super::{CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError}; use super::{CreateNixTree, CreateNixTreeError, CreateUsersAndGroup, CreateUsersAndGroupError};
@ -168,6 +165,10 @@ impl Action for ProvisionNix {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -108,6 +108,10 @@ impl Action for BootstrapVolume {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,12 +1,6 @@
use std::{
path::{Path, PathBuf},
time::Duration,
};
use tokio::process::Command;
use crate::{ use crate::{
action::{ action::{
common::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError}, base::{CreateFile, CreateFileError, CreateOrAppendFile, CreateOrAppendFileError},
darwin::{ darwin::{
BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects, BootstrapVolume, BootstrapVolumeError, CreateSyntheticObjects,
CreateSyntheticObjectsError, CreateVolume, CreateVolumeError, EnableOwnership, CreateSyntheticObjectsError, CreateVolume, CreateVolumeError, EnableOwnership,
@ -17,6 +11,11 @@ use crate::{
}, },
BoxableError, BoxableError,
}; };
use std::{
path::{Path, PathBuf},
time::Duration,
};
use tokio::process::Command;
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";
@ -289,6 +288,10 @@ impl Action for CreateApfsVolume {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -102,6 +102,10 @@ impl Action for CreateSyntheticObjects {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -138,6 +138,10 @@ impl Action for CreateVolume {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -111,6 +111,10 @@ impl Action for EnableOwnership {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -86,6 +86,10 @@ impl Action for EncryptVolume {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -97,6 +97,10 @@ impl Action for KickstartLaunchctlService {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -119,6 +119,10 @@ impl Action for UnmountVolume {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,10 +1,9 @@
use std::path::{Path, PathBuf}; use crate::action::base::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
use crate::action::common::{CreateDirectory, CreateDirectoryError, CreateFile, CreateFileError};
use crate::{ use crate::{
action::{Action, ActionDescription, ActionState}, action::{Action, ActionDescription, ActionState},
BoxableError, BoxableError,
}; };
use std::path::{Path, PathBuf};
const PATHS: &[&str] = &[ const PATHS: &[&str] = &[
"usr", "usr",
@ -185,6 +184,10 @@ impl Action for CreateSystemdSysext {
*action_state = ActionState::Uncompleted; *action_state = ActionState::Uncompleted;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -93,7 +93,7 @@ impl Action for StartSystemdUnit {
// TODO(@Hoverbear): Handle proxy vars // TODO(@Hoverbear): Handle proxy vars
execute_command( execute_command(
Command::new("systemctl") Command::new("systemctl")
.arg("stop") .arg("disable")
.arg(format!("{unit}")) .arg(format!("{unit}"))
.stdin(std::process::Stdio::null()), .stdin(std::process::Stdio::null()),
) )
@ -104,6 +104,10 @@ impl Action for StartSystemdUnit {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,13 +1,10 @@
use std::path::PathBuf;
use tokio::process::Command;
use crate::execute_command; use crate::execute_command;
use crate::{ use crate::{
action::{Action, ActionDescription, ActionState}, action::{Action, ActionDescription, ActionState},
BoxableError, BoxableError,
}; };
use std::path::PathBuf;
use tokio::process::Command;
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct SystemdSysextMerge { pub struct SystemdSysextMerge {
@ -112,6 +109,10 @@ impl Action for SystemdSysextMerge {
*action_state = ActionState::Completed; *action_state = ActionState::Completed;
Ok(()) Ok(())
} }
fn action_state(&self) -> ActionState {
self.action_state
}
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]

View file

@ -1,3 +1,4 @@
pub mod base;
pub mod common; pub mod common;
pub mod darwin; pub mod darwin;
pub mod linux; 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<ActionState<Self>, Box<dyn std::error::Error + Send + Sync>>;` // They should also have an `async fn plan(args...) -> Result<ActionState<Self>, Box<dyn std::error::Error + Send + Sync>>;`
async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>; async fn execute(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>; async fn revert(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
fn action_state(&self) -> ActionState;
} }
dyn_clone::clone_trait_object!(Action); dyn_clone::clone_trait_object!(Action);
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Copy)]
pub enum ActionState { pub enum ActionState {
Completed, Completed,
// Only applicable to meta-actions that start multiple sub-actions. // Only applicable to meta-actions that start multiple sub-actions.

View file

@ -71,3 +71,7 @@ pub(crate) async fn signal_channel() -> eyre::Result<(Sender<()>, Receiver<()>)>
Ok((sender, reciever)) Ok((sender, reciever))
} }
pub fn is_root() -> bool {
nix::unistd::getuid() == nix::unistd::Uid::from_raw(0)
}

View file

@ -1,6 +1,12 @@
use std::{path::PathBuf, process::ExitCode}; use std::{
path::{Path, PathBuf},
process::ExitCode,
};
use crate::{cli::signal_channel, BuiltinPlanner, HarmonicError}; use crate::{
action::ActionState, cli::is_root, cli::signal_channel, plan::RECEIPT_LOCATION, BuiltinPlanner,
InstallPlan, Planner,
};
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
use eyre::{eyre, WrapErr}; use eyre::{eyre, WrapErr};
use tokio_util::sync::CancellationToken; use tokio_util::sync::CancellationToken;
@ -9,7 +15,7 @@ use crate::{cli::CommandExecute, interaction};
/// Execute an install (possibly using an existing plan) /// Execute an install (possibly using an existing plan)
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(args_conflicts_with_subcommands = true)] #[command(args_conflicts_with_subcommands = true, arg_required_else_help = true)]
pub struct Install { pub struct Install {
#[clap( #[clap(
long, long,
@ -30,7 +36,7 @@ pub struct Install {
pub plan: Option<PathBuf>, pub plan: Option<PathBuf>,
#[clap(subcommand)] #[clap(subcommand)]
pub planner: BuiltinPlanner, pub planner: Option<BuiltinPlanner>,
} }
#[async_trait::async_trait] #[async_trait::async_trait]
@ -44,36 +50,79 @@ impl CommandExecute for Install {
explain, explain,
} = self; } = self;
let mut plan = match &plan { if !is_root() {
Some(plan_path) => { return Err(eyre!(
"`harmonic install` must be run as `root`, try `sudo harmonic install`"
));
}
let existing_receipt: Option<InstallPlan> = 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<dyn Planner> = 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) let install_plan_string = tokio::fs::read_to_string(&plan_path)
.await .await
.wrap_err("Reading plan")?; .wrap_err("Reading plan")?;
serde_json::from_str(&install_plan_string)? 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 !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; interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
} }
} }
let (tx, rx1) = signal_channel().await?; let (tx, rx1) = signal_channel().await?;
if let Err(err) = plan.install(rx1).await { if let Err(err) = install_plan.install(rx1).await {
match err { let error = eyre!(err).wrap_err("Install failure");
HarmonicError::Cancelled => {}, if !no_confirm {
err => { tracing::error!("{:?}", error);
tracing::error!("{:?}", eyre!(err)); if !interaction::confirm(install_plan.describe_revert(explain)).await? {
if !interaction::confirm(plan.describe_revert(explain)).await? { interaction::clean_exit_with_message("Okay, didn't do anything! Bye!").await;
interaction::clean_exit_with_message("Okay, didn't do anything! Bye!")
.await;
} }
let rx2 = tx.subscribe(); let rx2 = tx.subscribe();
plan.revert(rx2).await? install_plan.revert(rx2).await?
}, } else {
return Err(error);
} }
} }

View file

@ -9,11 +9,11 @@ use crate::cli::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)]
#[command(args_conflicts_with_subcommands = true, arg_required_else_help = true)] #[command(arg_required_else_help = true)]
pub struct Plan { pub struct Plan {
#[clap(subcommand)] #[clap(subcommand)]
pub planner: Option<BuiltinPlanner>, pub planner: Option<BuiltinPlanner>,
#[clap(env = "HARMONIC_PLAN")] #[clap(env = "HARMONIC_PLAN", default_value = "/dev/stdout")]
pub output: PathBuf, pub output: PathBuf,
} }

View file

@ -1,8 +1,12 @@
use std::{path::PathBuf, process::ExitCode}; use std::{path::PathBuf, process::ExitCode};
use crate::{cli::signal_channel, InstallPlan}; use crate::{
cli::{is_root, signal_channel},
plan::RECEIPT_LOCATION,
InstallPlan,
};
use clap::{ArgAction, Parser}; use clap::{ArgAction, Parser};
use eyre::WrapErr; use eyre::{eyre, WrapErr};
use crate::{cli::CommandExecute, interaction}; use crate::{cli::CommandExecute, interaction};
@ -23,7 +27,7 @@ pub struct Uninstall {
global = true global = true
)] )]
pub explain: bool, pub explain: bool,
#[clap(default_value = "/nix/receipt.json")] #[clap(default_value = RECEIPT_LOCATION)]
pub receipt: PathBuf, pub receipt: PathBuf,
} }
@ -37,6 +41,12 @@ impl CommandExecute for Uninstall {
explain, explain,
} = self; } = self;
if !is_root() {
return Err(eyre!(
"`harmonic install` must be run as `root`, try `sudo harmonic install`"
));
}
let install_receipt_string = tokio::fs::read_to_string(receipt) let install_receipt_string = tokio::fs::read_to_string(receipt)
.await .await
.wrap_err("Reading receipt")?; .wrap_err("Reading receipt")?;

View file

@ -10,6 +10,8 @@ use crate::{
HarmonicError, HarmonicError,
}; };
pub const RECEIPT_LOCATION: &str = "/nix/receipt.json";
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct InstallPlan { pub struct InstallPlan {
pub(crate) actions: Vec<Box<dyn Action>>, pub(crate) actions: Vec<Box<dyn Action>>,
@ -45,7 +47,7 @@ impl InstallPlan {
}, },
planner = planner.typetag_name(), planner = planner.typetag_name(),
plan_settings = planner plan_settings = planner
.describe()? .settings()?
.into_iter() .into_iter()
.map(|(k, v)| format!("* {k}: {v}", k = k.bold().white())) .map(|(k, v)| format!("* {k}: {v}", k = k.bold().white()))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@ -198,7 +200,7 @@ async fn write_receipt(plan: InstallPlan) -> Result<(), HarmonicError> {
tokio::fs::create_dir_all("/nix") tokio::fs::create_dir_all("/nix")
.await .await
.map_err(|e| HarmonicError::RecordingReceipt(PathBuf::from("/nix"), e))?; .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 = let self_json =
serde_json::to_string_pretty(&plan).map_err(HarmonicError::SerializingReceipt)?; serde_json::to_string_pretty(&plan).map_err(HarmonicError::SerializingReceipt)?;
tokio::fs::write(&install_receipt_path, self_json) tokio::fs::write(&install_receipt_path, self_json)

View file

@ -104,7 +104,7 @@ impl Planner for DarwinMulti {
}) })
} }
fn describe( fn settings(
&self, &self,
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> { ) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
let Self { let Self {

View file

@ -1,13 +1,12 @@
use std::collections::HashMap;
use crate::{ use crate::{
action::{ action::{
common::{ConfigureNix, CreateDirectory, ProvisionNix}, base::CreateDirectory,
linux::StartSystemdUnit, common::{ConfigureNix, ProvisionNix},
}, },
planner::Planner, planner::Planner,
BuiltinPlanner, CommonSettings, InstallPlan, BuiltinPlanner, CommonSettings, InstallPlan,
}; };
use std::collections::HashMap;
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
pub struct LinuxMulti { pub struct LinuxMulti {
@ -31,12 +30,11 @@ impl Planner for LinuxMulti {
Box::new(CreateDirectory::plan("/nix", None, None, 0o0755, true).await?), Box::new(CreateDirectory::plan("/nix", None, None, 0o0755, true).await?),
Box::new(ProvisionNix::plan(self.settings.clone()).await?), Box::new(ProvisionNix::plan(self.settings.clone()).await?),
Box::new(ConfigureNix::plan(self.settings).await?), Box::new(ConfigureNix::plan(self.settings).await?),
Box::new(StartSystemdUnit::plan("nix-daemon.socket".into()).await?),
], ],
}) })
} }
fn describe( fn settings(
&self, &self,
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> { ) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
let Self { settings } = self; let Self { settings } = self;

View file

@ -13,9 +13,15 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
where where
Self: Sized; Self: Sized;
async fn plan(self) -> Result<InstallPlan, Box<dyn std::error::Error + Sync + Send>>; async fn plan(self) -> Result<InstallPlan, Box<dyn std::error::Error + Sync + Send>>;
fn describe( fn settings(
&self, &self,
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>>; ) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>>;
fn boxed(self) -> Box<dyn Planner>
where
Self: Sized + 'static,
{
Box::new(self)
}
} }
dyn_clone::clone_trait_object!(Planner); dyn_clone::clone_trait_object!(Planner);
@ -56,6 +62,13 @@ impl BuiltinPlanner {
BuiltinPlanner::SteamDeck(planner) => planner.plan().await, BuiltinPlanner::SteamDeck(planner) => planner.plan().await,
} }
} }
pub fn boxed(self) -> Box<dyn Planner> {
match self {
BuiltinPlanner::LinuxMulti(i) => i.boxed(),
BuiltinPlanner::DarwinMulti(i) => i.boxed(),
BuiltinPlanner::SteamDeck(i) => i.boxed(),
}
}
} }
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]

View file

@ -2,7 +2,8 @@ use std::collections::HashMap;
use crate::{ use crate::{
action::{ action::{
common::{CreateDirectory, ProvisionNix}, base::CreateDirectory,
common::ProvisionNix,
linux::{CreateSystemdSysext, StartSystemdUnit}, linux::{CreateSystemdSysext, StartSystemdUnit},
}, },
planner::Planner, planner::Planner,
@ -36,7 +37,7 @@ impl Planner for SteamDeck {
}) })
} }
fn describe( fn settings(
&self, &self,
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> { ) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error + Sync + Send>> {
let Self { settings } = self; let Self { settings } = self;