Skip to content

Drive the macOS uninstaller from the package receipt - #104

Open
miharp wants to merge 1 commit into
OpenVoxProject:mainfrom
miharp:macos-uninstaller-shared-dirs
Open

miharp wants to merge 1 commit into
OpenVoxProject:mainfrom
miharp:macos-uninstaller-shared-dirs

Conversation

@miharp

@miharp miharp commented Oct 21, 2026 •

Copy link
Copy Markdown

Fixes OpenVoxProject/openbolt#344.

Problem

resources/macos/uninstaller.tool.erb removes every directory the project declares, with one rm -Rf. Directories shared between packages are wiped along with the other package's files:

  • OpenBolt declares /opt/puppetlabs/bin, so "Uninstall OpenBolt" removes openvox-agent's puppet and facter links there.
  • openvox-agent declares /opt/puppetlabs (its install_root), so its uninstaller removes /opt/puppetlabs/bolt as well.

The current script also never removes /etc/paths.d/50-bolt, since only directories are listed.

Change

The uninstaller now works from the installer receipt, the way pkgutil-based cask uninstalls do:

  1. Delete every file and symlink listed in pkgutil --files <id>.
  2. For each directory the project declares, rm -Rf it only if pkgutil --file-info shows no other installed package claiming it. Shared directories are kept and reported, for example Keeping /opt/puppetlabs/bin: it is also used by org.voxpupuli.openvox-agent.
  3. rmdir the receipt's remaining directories, deepest first, so directories left empty (such as /usr/local/share/doc/<name>) go away. Operating-system directories (/etc, /opt, /usr/local, /Library, ...) are never removed.
  4. pkgutil --forget <id>.

Step 2 keeps today's behaviour for directories a package owns alone: logs, caches and configuration written after installation are still cleaned up.

If no receipt exists the script exits with an error instead of guessing at paths.

Verification

  • bundle exec rake check passes (565 examples, rubocop clean). A new spec renders the template and checks the generated script is valid bash and uses the receipt rather than unconditional rm -Rf.
  • End-to-end in a clean macOS 26 (Tahoe) Tart VM with openvox-agent-9.1.0 and openbolt-5.6.0 installed from the published DMGs, using uninstallers rendered from the real openbolt and openvox project configs with this branch:
    1. Stock OpenBolt uninstaller first, to confirm the bug: /opt/puppetlabs/bin disappears, /opt/puppetlabs/bin/puppet stops resolving, /etc/paths.d/50-bolt is left behind.
    2. New OpenBolt uninstaller with the agent installed: /opt/puppetlabs/bolt, /opt/puppetlabs/bin/bolt, /etc/paths.d/50-bolt and /usr/local/share/doc/openbolt removed; /opt/puppetlabs/bin kept with the agent's puppet and facter links (Keeping /opt/puppetlabs/bin: it is also used by org.voxpupuli.openvox-agent); receipt forgotten; puppet --version still works.
    3. New agent uninstaller with OpenBolt installed: /opt/puppetlabs and /opt/puppetlabs/bin kept; /opt/puppetlabs/puppet, /etc/puppetlabs, /var/log/puppetlabs, the launchd plist and /etc/paths.d/puppet-agent removed; bolt --version still works.
    4. New OpenBolt uninstaller with nothing else installed: /opt/puppetlabs gone entirely, no receipts left.
    5. Run again with no receipt: exits 1 with No installer receipt found for org.voxpupuli.openbolt; nothing to remove.

No change is needed in openbolt or openvox to pick this up: both install vanagon from the branch given to the shared build_vanagon.yml workflow (default main), so the first macOS build after this merges ships the new uninstaller. The Uninstall app on the DMG is assembled by openvox-release-infra, which moves vanagon's generated <project>-uninstaller.tool out of the DMG and into the applet, so that pipeline needs no change either.

Assisted by Claude.

The generated uninstaller removed every directory the project declared
with `rm -Rf`. Directories shared between packages, such as
/opt/puppetlabs/bin, which both openvox-agent and openbolt install
into, were wiped along with the other package's files. Uninstalling
OpenBolt on a Mac with openvox-agent installed removed the agent's
`puppet` and `facter` links; the agent's uninstaller declares all of
/opt/puppetlabs and would take OpenBolt with it.

Remove exactly what the installer receipt records instead: every file
and symlink is deleted, then each declared directory is removed
recursively only when `pkgutil --file-info` shows no other installed
package claiming it, then any receipt directories left empty are
removed, and finally the receipt is forgotten. Operating-system
directories such as /etc, /opt and /usr/local are never removed.

Directories a package owns alone still go completely, so logs, caches
and configuration written after installation are cleaned up as before.

If no receipt exists the uninstaller now exits with an error instead
of guessing.

Fixes OpenVoxProject/openbolt#344

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Michael Harp <mike@mikeharp.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS "Uninstall OpenBolt" app removes /opt/puppetlabs/bin, breaking an installed OpenVox agent

1 participant