covenant-setup is a Windows installer builder and install engine written in Rust.
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.jsonwritten 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.jsonas 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
- 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
windowscrate with unsafe isolated insrc/win.rs - Logs every unsafe boundary transition
The packager command is:
cargo run -- package path\to\install.toml --output distCurrent 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.
Direct engine commands:
cargo run -- --headless install path\to\install.toml
cargo run -- --headless uninstall path\to\journal.jsonPackaged installer behavior:
- Running the packaged installer with no subcommand performs install
- The installed app gets:
journal.jsonin the install rootcovenant-setup-uninstall.exein 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
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.
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.
Current TUI behavior includes:
Installing {app_name}orUninstalling {app_name}- animated walking dots from 0 to 5, cycling every 500ms
- final success / reboot-needed text prompts
The current manifest supports:
directoriesfilesregistryshortcutsscriptspurge
The sample manifest lives at examples/install.toml.
- 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
Systrait insrc/sys.rs; the productionWinSysimpl delegates to the real subsystems whileMockSysrecords every call for unit tests - Journaling currently records declared actions through
DeclaredTracker - The implementation is Windows-specific
- 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
The codebase currently builds and formats successfully with:
cargo fmt
cargo check
cargo build --releaseInteractive GUI/TUI flows now have a Windows VM smoke harness for packaged installer behavior, while broader automated coverage is still limited.
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 thex86_64-w64-mingw32-*toolchain that thewindows-gnutarget links against.wine— runs the resulting test binary. Already wired up viarunner = "wine"in.cargo/config.tomlfor thex86_64-pc-windows-gnutarget 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-gnuCaveats:
cargo run(and thepackage/install/uninstallflows generally) require real Win32 — Wine handles unit-test execution but is not a substitute for the Windows VM smoke harness.- The
windows-msvctarget (the default on Windows) is unaffected by this section.
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.exeHKCU\Software\CovenantSetupSelfTest\InstallRootDesktop\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.ps1Notes:
- The Vagrant provider is
hyperv, and the box you choose must support that provider. - The harness opens
vmconnect.exeaftervagrant upso 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\traceafter each smoke run. It includesguest-events.jsonl, scheduler/process/event snapshots, Rust heartbeat files namedinstaller-heartbeat-*.jsonl, and C# UI pipe logs namedcsharp-ui-pipe-*.jsonl. - The Windows box should auto-log the
vagrantuser into the desktop session for the visual install path to appear. - Set
COVENANT_HYPERV_SWITCHto 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
-SkipViewerif you do not want the harness to open the Hyper-V console window.
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
-HaltAfteror-DestroyAfterif you want the harness to stop the VM after the test run.