forked from lix-project/lix-installer
Install can detect existing /nix/receipt.json
This commit is contained in:
parent
6678b1cdee
commit
e57311a807
4 changed files with 66 additions and 15 deletions
|
@ -1,6 +1,9 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
process::ExitCode,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::BuiltinPlanner;
|
use crate::{plan::RECEIPT_LOCATION, BuiltinPlanner, InstallPlan, Planner};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::{eyre, WrapErr};
|
use eyre::{eyre, WrapErr};
|
||||||
|
|
||||||
|
@ -8,7 +11,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,
|
||||||
|
@ -29,7 +32,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]
|
||||||
|
@ -43,28 +46,61 @@ impl CommandExecute for Install {
|
||||||
explain,
|
explain,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut plan = match &plan {
|
let existing_receipt: Option<InstallPlan> = match Path::new(RECEIPT_LOCATION).exists() {
|
||||||
Some(plan_path) => {
|
true => {
|
||||||
let install_plan_string = tokio::fs::read_to_string(&plan_path)
|
let install_plan_string = tokio::fs::read_to_string(&RECEIPT_LOCATION)
|
||||||
.await
|
.await
|
||||||
.wrap_err("Reading plan")?;
|
.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() {
|
||||||
|
existing_receipt
|
||||||
|
} else {
|
||||||
|
return Err(eyre!("Found existing plan in `/nix/receipt.json` which used a different planner, try uninstalling the existing install"))
|
||||||
|
}
|
||||||
|
} ,
|
||||||
|
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)?
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = plan.install().await {
|
if let Err(err) = install_plan.install().await {
|
||||||
tracing::error!("{:?}", eyre!(err));
|
tracing::error!("{:?}", eyre!(err));
|
||||||
if !interaction::confirm(plan.describe_revert(explain)).await? {
|
if !interaction::confirm(install_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;
|
||||||
}
|
}
|
||||||
plan.revert().await?
|
install_plan.revert().await?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ExitCode::SUCCESS)
|
Ok(ExitCode::SUCCESS)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{path::PathBuf, process::ExitCode};
|
use std::{path::PathBuf, process::ExitCode};
|
||||||
|
|
||||||
use crate::InstallPlan;
|
use crate::{plan::RECEIPT_LOCATION, InstallPlan};
|
||||||
use clap::{ArgAction, Parser};
|
use clap::{ArgAction, Parser};
|
||||||
use eyre::WrapErr;
|
use eyre::WrapErr;
|
||||||
|
|
||||||
|
@ -23,7 +23,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,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>>,
|
||||||
|
@ -166,7 +168,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)
|
||||||
|
|
|
@ -16,6 +16,12 @@ pub trait Planner: std::fmt::Debug + Send + Sync + dyn_clone::DynClone {
|
||||||
fn describe(
|
fn describe(
|
||||||
&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)]
|
||||||
|
|
Loading…
Reference in a new issue