Skip to content

JMR-dev/covenant-setup

Repository files navigation

covenant-setup

covenant-setup is a Windows installer builder and install engine written in Rust.

Why a different Windows installer/uninstaller packager?

Windows has a mess when it comes to managing program lifecycles. Developers can leave files everywhere on install, the OS lets you do ANYTHING if you elevate to admin, and the uninstall process has no idea what files and registry entries were actually created during the install, leaving behind a mess and contributing to registry rot.

This packager aims to take a different approach by

  • Recording the install actions it applies (files, directories, registry entries, shortcuts, and script execution) into a journal.json written alongside the installed application. This journal is then used during uninstall to reverse those recorded actions and clean up associated state.
  • Take a "leave the campground better than you found it" approach - this Eagle Scout practices Leave No Trace.
  • Taking a "trust but verify model" to program installs and uninstalls by journaling engine-applied mutations and logging script execution in order to respect the user.
  • Using the journal.json as a manifest of the actions the installer performed during install and post-install processing.

Its current shape is:

  • a packager that takes a developer-authored install.toml
  • a single-file installer runtime with the app payload embedded into the .exe
  • an installed uninstaller path that reuses the same Rust engine

Current Capabilities

  • Packages an app from a manifest into a single installer executable
  • Installs files, directories, registry values, shortcuts, and post-install scripts
  • Journals applied mutations to support deterministic uninstall
  • Uninstalls in reverse order and purges declared registry/path namespaces
  • Registers the installed app in Windows Installed Apps / Add-Remove Programs
  • Creates an installed uninstaller executable in the app root
  • Uses a C# WinForms presentation process for GUI progress and prompts
  • Sends GUI state over named-pipe IPC from the Rust engine to the C# UI
  • Uses Win32 APIs through the windows crate with unsafe isolated in src/win.rs
  • Logs every unsafe boundary transition

Packaging Model

The packager command is:

cargo run -- package path\to\install.toml --output dist

Current output:

  • dist\covenant-setup-installer.exe

That installer is a single executable. The manifest and payload files are embedded into the binary and extracted to a temporary working directory at runtime.

When the .NET SDK is available, the Rust build publishes a self-contained C# WinForms UI helper and embeds it into the Rust executable. Without that helper the installer still builds, but --headed falls back to terminal progress.

Install and Uninstall Model

Direct engine commands:

cargo run -- --headless install path\to\install.toml
cargo run -- --headless uninstall path\to\journal.json

Packaged installer behavior:

  • Running the packaged installer with no subcommand performs install
  • The installed app gets:
    • journal.json in the install root
    • covenant-setup-uninstall.exe in the install root
    • an uninstall registry entry under ...\CurrentVersion\Uninstall\...

Installed-app uninstall behavior:

  • Windows Installed Apps launches the installed uninstaller executable
  • The engine removes payload files first
  • A cleanup helper from %TEMP% removes the running uninstaller after it exits
  • If immediate cleanup is impossible, file removal falls back to delete-on-reboot

UI Behavior

There is now one installer/uninstaller path. UI mode must be explicit for interactive runs.

Explicit flags:

  • --headless: force TUI
  • --headed: force GUI
  • --json: suppress UI and emit machine-readable events

If --headed is requested but the C# UI helper is not bundled and no Covenant.Setup.Ui.exe sidecar exists next to the installer, the engine falls back to --headless.

GUI

Current GUI behavior includes:

  • C# WinForms prompts for confirmation and completion
  • a progress window with:
    • progress bar
    • current operation text
    • scrolling operations log
  • reboot prompt when uninstall requires reboot to finish some cleanup

The Rust install engine owns all business logic and file/registry mutations. The C# process is presentation-only and receives JSON messages over a Windows named pipe.

TUI

Current TUI behavior includes:

  • Installing {app_name} or Uninstalling {app_name}
  • animated walking dots from 0 to 5, cycling every 500ms
  • final success / reboot-needed text prompts

Manifest Scope

The current manifest supports:

  • directories
  • files
  • registry
  • shortcuts
  • scripts
  • purge

The sample manifest lives at examples/install.toml.

Architecture Notes

  • Core engine flow is in src/main.rs
  • Windows FFI wrappers are isolated in src/win.rs
  • External-boundary calls (Win32, GUI prompts, reboot/cleanup spawning, embedded-bundle probe) flow through the Sys trait in src/sys.rs; the production WinSys impl delegates to the real subsystems while MockSys records every call for unit tests
  • Journaling currently records declared actions through DeclaredTracker
  • The implementation is Windows-specific

Current Limitations

  • The GUI helper is embedded as a self-contained C# WinForms executable, which makes the installer binary substantially larger
  • The installer is not yet generating branded/custom themed installer screens
  • The manifest schema is still MVP-level and does not cover all production installer concerns
  • Script execution logs the script invocation; internal script mutations are not observed beyond declared purge coverage
  • The packager currently embeds payload in an appended raw bundle; this is functional but not yet compressed, signed, or tamper-resistant
  • No signing, MSI generation, compression, delta updates, or patching pipeline exists yet
  • No automated test suite has been added yet for end-to-end installer scenarios

Verification Status

The codebase currently builds and formats successfully with:

cargo fmt
cargo check
cargo build --release

Interactive GUI/TUI flows now have a Windows VM smoke harness for packaged installer behavior, while broader automated coverage is still limited.

Linux Cross-Build (Optional)

Day-to-day work happens on Windows. This section documents an opt-in path for type-checking and running unit tests from a Linux host (useful for CI containers or quick iteration without a VM).

Prerequisites (Ubuntu 24.04 names):

  • dotnet-sdk-10.0 — optional for embedding the C# UI helper; without it, headed mode falls back to headless.
  • mingw-w64 — provides the x86_64-w64-mingw32-* toolchain that the windows-gnu target links against.
  • wine — runs the resulting test binary. Already wired up via runner = "wine" in .cargo/config.toml for the x86_64-pc-windows-gnu target only.
  • rustup target add x86_64-pc-windows-gnu.

The dotnet SDK needs EnableWindowsTargeting=true to cross-build a net10.0-windows project from Linux:

EnableWindowsTargeting=true cargo check --target x86_64-pc-windows-gnu
EnableWindowsTargeting=true cargo test  --target x86_64-pc-windows-gnu

Caveats:

  • cargo run (and the package/install/uninstall flows generally) require real Win32 — Wine handles unit-test execution but is not a substitute for the Windows VM smoke harness.
  • The windows-msvc target (the default on Windows) is unaffected by this section.

Windows VM Smoke Test

A Windows Hyper-V Vagrant VM now lives in Vagrantfile, and the host harness in scripts/run-windows-vm-smoke.ps1 packages covenant-setup, boots the VM, opens Hyper-V's console viewer, and runs the packaged installer inside the guest's interactive desktop session.

The self-install manifest used for this path lives at vm/self-test/install.toml. The guest verifies that install produced:

  • %LOCALAPPDATA%\CovenantSetupSelfTest\bin\covenant-setup.exe
  • %LOCALAPPDATA%\CovenantSetupSelfTest\journal.json
  • %LOCALAPPDATA%\CovenantSetupSelfTest\covenant-setup-uninstall.exe
  • HKCU\Software\CovenantSetupSelfTest\InstallRoot
  • Desktop\Covenant Setup Self Test.lnk

It then immediately invokes the installed uninstaller with the generated journal and verifies that the install root, payload, journal, uninstaller, shortcut, application registry key, and Installed Apps registration are removed.

Run the smoke test from the repo root:

$env:COVENANT_WINDOWS_BOX = "gusztavvargadr/windows-11"
$env:COVENANT_HYPERV_SWITCH = "Default Switch"
.\scripts\run-windows-vm-smoke.ps1

Notes:

  • The Vagrant provider is hyperv, and the box you choose must support that provider.
  • The harness opens vmconnect.exe after vagrant up so the guest desktop stays visible during the install.
  • The default Vagrant synced folder is disabled to avoid SMB credential prompts; the harness uploads the installer and guest scripts over WinRM instead.
  • The guest install is launched through an interactive scheduled task because WinRM sessions are not desktop-visible.
  • The packaged installer now has a hidden automation mode that suppresses blocking GUI prompts while leaving the C# progress window visible for the VM smoke test.
  • The guest scripts write a trace bundle under dist\vagrant-self-test\trace after each smoke run. It includes guest-events.jsonl, scheduler/process/event snapshots, Rust heartbeat files named installer-heartbeat-*.jsonl, and C# UI pipe logs named csharp-ui-pipe-*.jsonl.
  • The Windows box should auto-log the vagrant user into the desktop session for the visual install path to appear.
  • Set COVENANT_HYPERV_SWITCH to the Hyper-V virtual switch name you want Vagrant to use.
  • The harness writes its verification artifact to dist\vagrant-self-test\guest-result.json.
  • Use -SkipViewer if you do not want the harness to open the Hyper-V console window.

VM Coverage Harness

In addition to the single-scenario smoke test, scripts/run-windows-vm-coverage.ps1 walks every scenario manifest under vm\<scenario>\install.toml (self-test, uac, hklm-registry, reboot, bundled-exec) and delegates the in-guest assertions to scripts\windows-vm\coverage\<scenario>.ps1. The scenarios exercise the elevation, MoveFileEx pending-rename / Restart Manager, HKLM-only registry, and bundled embedded-installer code paths that the unit tests stub out via MockSys.

  • Use -HaltAfter or -DestroyAfter if you want the harness to stop the VM after the test run.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors