diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3be154a..6ae4dcd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -140,8 +140,6 @@ jobs: name: windows-${{ matrix.arch }}-msi path: | build/out/*.msi - build/out/Install-Prempti.ps1 - build/out/Uninstall-Prempti.ps1 if-no-files-found: error build-test-macos: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0d3da9b..b93fee8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -143,8 +143,6 @@ jobs: name: windows-${{ matrix.arch }} path: | build/out/*.msi - build/out/Install-Prempti.ps1 - build/out/Uninstall-Prempti.ps1 if-no-files-found: error publish: diff --git a/CLAUDE.md b/CLAUDE.md index 85bfe30..614d09d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -362,7 +362,7 @@ Windows has no user-level systemd or launchd equivalent, so Prempti uses a **Pow - **Path separators**: all runtime paths (broker socket, library_path, rules_files, http_output URL) are normalized to forward slashes at config-generation time. The plugin also canonicalizes paths to forward slashes for rule matching (stripping the Windows `\\?\` long-path prefix that `std::fs::canonicalize` sometimes adds). - **AF_UNIX**: both `broker.sock` and `supervisor.sock` are real Unix domain sockets (Windows 10+ has kernel `AF_UNIX` support). The `uds_windows` crate provides Rust bindings. - **Plugin library**: `coding_agent.dll` on Windows (vs `.so` on Linux and `.dylib` on macOS). The Windows packager copies the DLL into `%LOCALAPPDATA%\prempti\share\` and the post-install script renders a `library_path` in `falco.coding_agents_plugin.yaml` that points at the absolute path with forward slashes. -- **Fail-safety on MSI uninstall**: the MSI declares a deferred `REMOVE=ALL` custom action (`installers/windows/Package.wxs`) that runs `uninstall.ps1` before `RemoveFiles`, so Apps & Features, `msiexec /x` and the bundled helper all stop the service, remove the hook, drop the Run-key entry and clean `bin\` from the user `PATH`. `Return="ignore"` on the CA keeps a user-edited `settings.json` from blocking the uninstall. +- **Fail-safety on MSI uninstall**: the MSI declares a deferred `REMOVE=ALL` custom action (`installers/windows/Package.wxs`) that runs `uninstall.ps1` before `RemoveFiles`, so Apps & Features and `msiexec /x` both stop the service, remove the hook, drop the Run-key entry and clean `bin\` from the user `PATH`. `Return="ignore"` on the CA keeps a user-edited `settings.json` from blocking the uninstall. - **`ctl start` detachment**: the launcher is a long-lived descendant (it waits on the supervisor, which waits on Falco), so a direct `CreateProcess` of the launcher is tracked by the caller's PowerShell job object and keeps a captured pipeline (`& ctl start 2>&1`) open until everything exits. To break that chain, `service_start` invokes PowerShell's `Start-Process` (ShellExecute), producing a grandchild that's fully independent of the caller. Caveat: `Start-Process -Wait ctl start` from a script still hangs because `-Wait` follows the whole process tree — the captured form `& ctl start 2>&1` is the one to use. - **Post-install auto-start fail-safety**: `postinstall.ps1` starts the service via `Start-Process` after registering the hook, polls up to 5s for Falco, and falls through to a `Write-Warning` if it doesn't see Falco in time. Install still completes; the user recovers with `premptictl start` manually. If Falco fails to start at all (port conflict, missing DLL, etc.) the user is not silently left with a registered hook and a dead broker. diff --git a/README.md b/README.md index fb616ef..70db8f9 100644 --- a/README.md +++ b/README.md @@ -106,13 +106,9 @@ The installer copies all components to `~/.prempti/`, starts a systemd user serv ### Windows -From the [latest release](https://github.com/falcosecurity/prempti/releases/latest), download **both** the `.msi` for your CPU architecture and the `Install-Prempti.ps1` helper, then run: +From the [latest release](https://github.com/falcosecurity/prempti/releases/latest), download the `.msi` for your CPU architecture and double-click it (or run `msiexec /i prempti--windows-.msi`). -```powershell -powershell -ExecutionPolicy Bypass -File Install-Prempti.ps1 -``` - -The helper runs the MSI, deploys all components to `%LOCALAPPDATA%\prempti\`, adds `bin\` to your user `PATH`, registers the Claude Code hook, registers an auto-start entry for subsequent logins, and starts the service immediately so Claude Code is protected without any extra step. +The MSI deploys all components to `%LOCALAPPDATA%\prempti\`, adds `bin\` to your user `PATH`, registers the Claude Code hook, registers an auto-start entry for subsequent logins, and starts the service immediately so Claude Code is protected without any extra step. > [!NOTE] > Pick the MSI that matches your CPU: `prempti--windows-x64.msi` on Intel/AMD64, `prempti--windows-arm64.msi` on Windows ARM64. The x64 MSI can install under emulation on ARM64 hosts but prefer the native ARM64 MSI for best performance. See [`installers/windows/`](installers/windows/) for build prerequisites and details. @@ -190,12 +186,9 @@ premptictl start Any of these paths works — they all run the same cleanup custom action: -- ```powershell - powershell -ExecutionPolicy Bypass -File Uninstall-Prempti.ps1 - ``` - (bundled with the release), -- Apps & Features, -- `msiexec /x `. +- Apps & Features (recommended), +- `msiexec /x prempti--windows-.msi` (if you still have the MSI file), +- `msiexec /x ` (the GUID is in `HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\` under the Prempti entry). The MSI removes the Claude Code hook, the auto-start entry, and the `bin\` `PATH` entry before removing files, so Claude Code is not left in a fail-closed state. @@ -353,7 +346,7 @@ Requires: Rust (latest stable), Visual Studio 2022+ with C++ workload, CMake 3.2 powershell -ExecutionPolicy Bypass -File installers\windows\package.ps1 ``` -Output: `build/out/prempti--windows-.msi` (plus `Install-Prempti.ps1` and `Uninstall-Prempti.ps1` helpers). +Output: `build/out/prempti--windows-.msi`. > Falco is built from source on the first run (~10 min). Subsequent builds use the cached binary. diff --git a/installers/windows/README.md b/installers/windows/README.md index c2da6b6..69f7fad 100644 --- a/installers/windows/README.md +++ b/installers/windows/README.md @@ -168,18 +168,10 @@ powershell -ExecutionPolicy Bypass -File installers\windows\package.ps1 -SkipRus > **Migrating from `coding-agents-kit`?** Prempti does not migrate or remove existing `coding-agents-kit` installations. Uninstall `coding-agents-kit` first to avoid duplicate services or stale Claude Code hooks. -### Recommended: `Install-Prempti.ps1` - -The `Install-Prempti.ps1` helper (emitted next to the MSI in `build\out\`) is the recommended path. It runs the MSI silently and, if the product is already installed, opens the MSI maintenance UI instead of forcing a silent reinstall: +Double-click the MSI, or run from PowerShell: ```powershell -powershell -ExecutionPolicy Bypass -File build\out\Install-Prempti.ps1 -``` - -### Manual `msiexec` - -```powershell -msiexec /i build\out\prempti--windows-arm64.msi +msiexec /i build\out\prempti--windows-.msi ``` The MSI runs `postinstall.ps1` automatically via a deferred custom action. No manual follow-up script is required. The post-install step: @@ -217,15 +209,19 @@ premptictl start ### Uninstalling -Any of the three paths works — the MSI runs `uninstall.ps1` as a deferred custom action whenever `REMOVE=ALL`, so Apps & Features, `msiexec /x`, and the bundled helper all stop the service, remove the Claude Code hook, remove the auto-start Run-key entry, and clean `bin\` from the user `PATH` before removing files. +Any of these paths works — the MSI runs `uninstall.ps1` as a deferred custom action whenever `REMOVE=ALL`, so each one stops the service, removes the Claude Code hook, removes the auto-start Run-key entry, and cleans `bin\` from the user `PATH` before removing files. -The bundled helper is still the most convenient: +- **Apps & Features** → Prempti → Uninstall (recommended). +- **By MSI path** (if you still have the file): -```powershell -powershell -ExecutionPolicy Bypass -File Uninstall-Prempti.ps1 -``` + ```powershell + msiexec /x prempti--windows-.msi + ``` +- **By product code** (the GUID is in `HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\` under the Prempti entry): -> **Older 0.1.x builds**: MSI packages from before the uninstall custom action was added required the helper script — without it, Apps & Features would leave the Claude Code hook registered and brick Claude Code until `premptictl hook remove` was run manually. + ```powershell + msiexec /x + ``` ## Running Tests diff --git a/installers/windows/package.ps1 b/installers/windows/package.ps1 index 1361449..48154c7 100644 --- a/installers/windows/package.ps1 +++ b/installers/windows/package.ps1 @@ -246,53 +246,5 @@ if (-not $wix) { throw 'WiX Toolset not found. Install via: dotnet tool install if ($LASTEXITCODE -ne 0) { throw 'WiX build failed.' } -# --------------------------------------------------------------------------- -# Emit companion uninstall script -# --------------------------------------------------------------------------- - -$uninstallScript = Join-Path $OutputDir 'Uninstall-Prempti.ps1' -@" -# Uninstall Prempti $Version -# Run cleanup first: remove hook and auto-start registration -`$prefix = Join-Path `$env:LOCALAPPDATA 'prempti' -`$cleanup = Join-Path `$prefix 'scripts\uninstall.ps1' -if (Test-Path `$cleanup) { - & powershell -NoProfile -ExecutionPolicy Bypass -File `$cleanup -Prefix `$prefix -} -# Remove files via MSI -`$p = Start-Process msiexec -ArgumentList '/x', '$ProductCode', '/quiet' -Wait -PassThru -if (`$p.ExitCode -ne 0 -and `$p.ExitCode -ne 1605) { Write-Error "Uninstall failed (exit `$(`$p.ExitCode))" } -Write-Host "Uninstall complete" -"@ | Set-Content $uninstallScript -Encoding UTF8 - -# --------------------------------------------------------------------------- -# Emit install helper script (runs MSI + postinstall) -# --------------------------------------------------------------------------- - -$installScript = Join-Path $OutputDir 'Install-Prempti.ps1' -@" -# Install Prempti $Version -`$msi = Join-Path `$PSScriptRoot '$MsiName' -`$productCode = '$ProductCode' - -# If already installed, open normal MSI maintenance UI (Repair/Uninstall) -# instead of forcing a silent reinstall. -`$installer = New-Object -ComObject WindowsInstaller.Installer -`$state = `$installer.ProductState(`$productCode) -if (`$state -eq 5) { - Start-Process msiexec -ArgumentList '/i', `$msi -Wait | Out-Null - exit 0 -} - -`$p = Start-Process msiexec -ArgumentList '/i', `$msi, '/quiet' -Wait -PassThru -if (`$p.ExitCode -ne 0) { Write-Error "MSI install failed (exit `$(`$p.ExitCode))"; exit 1 } -# postinstall.ps1 runs automatically via the MSI deferred custom action -# (see installers\windows\Package.wxs). No manual follow-up is required. -Write-Host "Prempti installation complete" -"@ | Set-Content $installScript -Encoding UTF8 - Write-Host "" Write-Host "MSI created: $(Join-Path $OutputDir $MsiName)" -Write-Host "" -Write-Host "Install: powershell -ExecutionPolicy Bypass -File Install-Prempti.ps1" -Write-Host "Uninstall: powershell -ExecutionPolicy Bypass -File Uninstall-Prempti.ps1"