This commit is contained in:
2026-06-13 18:48:41 -05:00
parent 87018bc769
commit 9eb0daa5eb
8 changed files with 42 additions and 23 deletions
@@ -10,6 +10,10 @@ paths = [
source = 'payload\sample_app.cmd'
destination = '{LocalAppData}\CovenantSetupSample\bin\sample_app.cmd'
[[files]]
source = 'payload\post_install.ps1'
destination = '{LocalAppData}\CovenantSetupSample\post_install.ps1'
[[registry]]
key = 'HKCU\Software\CovenantSetupSample'
name = 'InstallRoot'
@@ -20,13 +24,17 @@ path = '{Desktop}\CovenantSetupSample.lnk'
target = '{LocalAppData}\CovenantSetupSample\bin\sample_app.cmd'
description = 'Launch the Covenant-Setup sample payload'
# The post-install script is copied into the install root above, so it is
# invoked by name with the working directory already pointing there. This keeps
# the script locatable for both direct and packaged (temp-extracted) installs,
# where the manifest's own directory is not the process working directory.
[[scripts]]
command = 'powershell'
args = [
'-ExecutionPolicy',
'Bypass',
'-File',
'payload\post_install.ps1'
'post_install.ps1'
]
working_directory = '{LocalAppData}\CovenantSetupSample'
+8 -4
View File
@@ -35,8 +35,12 @@ cargo run -- uninstall examples/journal.json --json
Expected effects:
- Creates `%LOCALAPPDATA%\CovenantSetupExample`
- Copies `sample_app.cmd` into the `bin` directory
- Writes `HKCU\Software\CovenantSetupExample\InstallRoot`
- Creates `%LOCALAPPDATA%\CovenantSetupSample`
- Copies `sample_app.cmd` into the `bin` directory and `post_install.ps1` into the install root
- Writes `HKCU\Software\CovenantSetupSample\InstallRoot`
- Creates a desktop shortcut
- Runs an inline PowerShell post-install command and records only the script execution in the journal
- Runs `post_install.ps1` from the install root, which writes a timestamped marker under `logs\`
Every directory, file, registry value, shortcut, and script execution is recorded in the
journal, so uninstall reverses all of them (and the `purge` spec removes the whole install
root, including the `logs\` directory the script created).
+2 -2
View File
@@ -82,7 +82,7 @@ pub(crate) fn build_packaged_installer(
manifest: &InstallManifest,
logger: &Logger,
) -> Result<(), AppError> {
fs::copy(current_exe, &exe_target)?;
fs::copy(current_exe, exe_target)?;
let bundled_manifest_name = expected_manifest_file_name(&manifest.app_name);
let bundle = EmbeddedBundle {
metadata: PackagedApp {
@@ -643,7 +643,7 @@ mod tests {
temp.path(),
&manifest,
&bundled_manifest_name,
&[generated_installer.clone()],
std::slice::from_ref(&generated_installer),
)
.unwrap();
files.sort_by(|left, right| left.relative_path.cmp(&right.relative_path));
+3 -3
View File
@@ -14,6 +14,7 @@ pub(crate) struct InstallRuntime {
pub(crate) uninstall_registry_root: RegistryRoot,
pub(crate) uninstall_registry_key: String,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn install(
manifest_path: &Path,
journal_path: Option<PathBuf>,
@@ -285,8 +286,8 @@ pub(crate) fn install(
);
let cancelled = matches!(err, AppError::Cancelled);
let mut rollback_error_msg = None;
if let Some(runtime) = &runtime_opt {
if !journal.actions.is_empty() {
if let Some(runtime) = &runtime_opt
&& !journal.actions.is_empty() {
let write_res = (|| -> Result<(), AppError> {
write_journal(&runtime.journal_path, &journal)?;
let read_back = fs::read_to_string(&runtime.journal_path)?;
@@ -328,7 +329,6 @@ pub(crate) fn install(
}
}
}
}
if cancelled && rollback_error_msg.is_none() {
let _ = finish_gui_progress(
+1
View File
@@ -58,6 +58,7 @@ pub(crate) fn start_tui_progress(ui_mode: UiMode, label: String) -> Option<TuiPr
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn start_gui_progress(
ui_mode: UiMode,
sys: &dyn Sys,
+1
View File
@@ -20,6 +20,7 @@ pub(crate) trait Sys: Send + Sync {
fn prompt_reboot_tui(&self) -> Result<bool, AppError>;
// (3) cleanup helper self-delete
#[allow(clippy::too_many_arguments)]
fn spawn_cleanup_helper(
&self,
target_exe: &Path,
+10 -5
View File
@@ -6,6 +6,7 @@ use std::os::windows::process::CommandExt;
use std::path::{Path, PathBuf};
use std::process::Command;
#[allow(clippy::too_many_arguments)]
pub(crate) fn uninstall(
journal_path: &Path,
elevate: bool,
@@ -267,10 +268,11 @@ pub(crate) fn cleanup(
sys.remove_file_with_fallback(&target_exe, &effective_logger)?;
reboot_required = target_exe.exists();
}
if let Some(install_root) = install_root {
if install_root.exists() && fs::read_dir(&install_root)?.next().is_none() {
win::remove_directory_if_exists(&install_root, &effective_logger)?;
}
if let Some(install_root) = install_root
&& install_root.exists()
&& fs::read_dir(&install_root)?.next().is_none()
{
win::remove_directory_if_exists(&install_root, &effective_logger)?;
}
reboot_required |= sys.schedule_helper_self_cleanup(&effective_logger)?;
if ui_mode == UiMode::Gui && !automation {
@@ -383,7 +385,10 @@ pub(crate) fn journal_requires_admin(
return Ok(true);
}
}
JournalAction::WriteRegistry { root, .. } if matches!(root, RegistryRoot::Hklm) => {
JournalAction::WriteRegistry {
root: RegistryRoot::Hklm,
..
} => {
return Ok(true);
}
_ => {}
+8 -8
View File
@@ -4,18 +4,18 @@ pub fn create_directory_recursive(path: &Path, logger: &Logger) -> Result<(), Ap
if path.as_os_str().is_empty() || path.exists() {
return Ok(());
}
if let Some(parent) = path.parent() {
if parent != path {
create_directory_recursive(parent, logger)?;
}
if let Some(parent) = path.parent()
&& parent != path
{
create_directory_recursive(parent, logger)?;
}
logger.unsafe_enter("CreateDirectoryW", json!({"path": path}));
let result = unsafe { CreateDirectoryW(PCWSTR(Utf16Arg::from_path(path).as_ptr()), None) };
logger.unsafe_exit("CreateDirectoryW", json!({"ok": result.is_ok()}));
if let Err(err) = result {
if !path.exists() {
return Err(err.into());
}
if let Err(err) = result
&& !path.exists()
{
return Err(err.into());
}
Ok(())
}