Skip to content

Offer IT-published .pkg, .dmg and .zip apps in the macOS Software Catalog - #5603

Open
Gabriel Dufresne (GabrielDuf) wants to merge 9 commits into
mainfrom
macos-pkg-dmg-catalog-feeds
Open

Gabriel Dufresne (GabrielDuf) wants to merge 9 commits into
mainfrom
macos-pkg-dmg-catalog-feeds

Conversation

@GabrielDuf

@GabrielDuf Gabriel Dufresne (GabrielDuf) commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What it adds

A macOS "Installer packages" manager (pkg), fed by the catalog itself. A catalog in the machine-wide SoftwareCatalog.json declares a pkgFeed: a folder or mounted share read directly, or an https folder read through the index.json that unigetui pkg-index <folder> writes. Every package in the feed joins that catalog.

  • Formats: .pkg, .dmg holding an app or a .pkg, and .zip holding an app. Identity, version, architectures and icon are read from the file (xar table of contents and payload, a read-only mounted image, or the zip entries).
  • Detection: by the package receipt, or by the app in place under its bundle id, so apps already deployed by Jamf or by hand are recognized and updates are offered.
  • Install and update: a private staged copy is checked against the index hash, its identity and Gatekeeper (Developer ID signed and notarized, unless the catalog sets allowUnsignedPackages), then installed as root behind the macOS administrator prompt. .dmg/.zip apps are swapped into /Applications so that a failure keeps the previous version. Uninstall is refused (new CanUninstall capability).
  • Without a password: sudo unigetui pkg-helper install-daemon sets up a root LaunchDaemon so standard users can install too. A client sends only catalog id, package id and version over a Unix socket. The helper resolves and checks the package itself, and refuses anything the catalog does not publish or that a standard user could change (catalog, feed, package file, UniGetUI itself). Without it, installs fall back to the prompt.
  • Catalog Editor: a Package feed dialog sets a catalog's feed and previews what it publishes and which files it skips, with the reason.

Shared changes: InstalledPackagesKeepTheirSource and Sources.CanBeEdited capabilities (replacing Chocolatey and manager-id special cases), and an unpinned catalog entry of a NuGet-based manager now asks the feed for its latest version instead of Version='', so its icon is found.

Docs: docs/PKG_FEEDS.md for administrators, docs/CLI.md, and a section in AGENTS.md.

Testing

  • UniGetUI.PackageEngine.Tests on macOS: all 99 pkg-feed tests and the NuGet tests pass, including real pkgbuild, productbuild, hdiutil and ditto fixtures, an install over the helper socket, and the refusal cases. The rest of the suite fails the same 107 tests as main on macOS (Windows-only areas).
  • Tried in the app on macOS with real vendor files: Zoom (.pkg), GitKraken and ChatGPT (.dmg, ChatGPT installed), and the Devolutions Mac downloads (.dmg and .pkg, detected from the existing installs with updates offered), plus the Package feed dialog.
  • dotnet format whitespace and dotnet format style (--verify-no-changes) pass, and the osx-arm64 NativeAOT publish adds no trim or AOT warnings.
  • Not run locally: the Windows-only UniGetUI.Tests (catalog model, Catalog Editor and identity tests) and the Chocolatey change, which this PR's CI covers. The package helper has not yet run as a real LaunchDaemon on a Mac.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved privileged-helper filesystem trust gaps and installation-selection and reliability defects block safe deployment.

9 open issues
What changed in this PR

Adds IT-published macOS apps to UniGetUI’s existing Software Catalog, letting users install software unavailable through conventional package managers.

Changes:

  • Adds a macOS installer manager for .pkg, .dmg, and .zip feeds, including detection, updates, indexing, and an optional privileged helper.
  • Integrates feed configuration and previews into the Catalog Editor, with capability-based uninstall and source-editing controls.
  • Adds catalog and NuGet regression tests, English strings, and administrator documentation.
File Description
src/​UniGetUI.Windows.slnx Includes the MacPkg project.
src/​UniGetUI.Tui/​Views/​Pages/​ManagersPage.cs Respects source-editing capability.
src/​UniGetUI.Tests/​SoftwareCatalogTests.cs Tests feed persistence and catalog usability.
src/​UniGetUI.Tests/​CatalogPackageIdentitySetTests.cs Tests source-independent installed identities.
src/​UniGetUI.Tests/​CatalogEditorTests.cs Tests feed editing, saving, and validation.
src/​UniGetUI.PackageEngine.Tests/​UniGetUI.PackageEngine.Tests.csproj References MacPkg for testing.
src/​UniGetUI.PackageEngine.Tests/​NuGetManifestLoaderTests.cs Tests latest-version lookup and cache expiry.
src/​UniGetUI.PackageEngine.PackageEngine/​UniGetUI.PackageEngine.PEInterface.csproj Adds the platform-conditioned manager reference.
src/​UniGetUI.PackageEngine.PackageEngine/​PEInterface.cs Registers MacPkg on macOS.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​UniGetUI.PackageEngine.Managers.MacPkg.csproj Defines the new manager project.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​PkgIndexCommand.cs Generates web-feed indexes and publishes icons.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​PkgHelperCommand.cs Adds daemon setup and privileged installation requests.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​PkgFeedInspector.cs Provides feed previews and diagnostics.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​MacPkg.cs Implements package discovery, inventory, and updates.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​InternalsVisibleTo.cs Exposes internals to engine tests.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​ZipApp.cs Reads app metadata and icons from ZIP files.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​XarArchive.cs Reads flat-package archives.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​TrustedPath.cs Checks filesystem ownership and permissions.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​Tool.cs Runs native tools with redirected input and output.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgReceipts.cs Reads installed package receipts.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgPayload.cs Extracts app resources from package payloads.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgMetadata.cs Interprets package identity and architecture metadata.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgInstaller.cs Stages, validates, and installs feed packages.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgHelperClient.cs Communicates with the privileged helper.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgFeedIndex.cs Defines source-generated feed serialization.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgFeed.cs Loads and caches folder and web feeds.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​PkgCatalogFeeds.cs Reads machine-wide feed declarations.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​FeedFile.cs Classifies supported installer formats.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​DiskImage.cs Mounts and inspects disk images.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​AppBundle.cs Reads bundle identity, versions, architectures, and icons.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Helpers/​MacPkgSourceHelper.cs Exposes catalog-defined, noneditable sources.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Helpers/​MacPkgOperationHelper.cs Routes managed installs and updates.
src/​UniGetUI.PackageEngine.Managers.MacPkg/​Helpers/​MacPkgDetailsHelper.cs Supplies package details, icons, and versions.
src/​UniGetUI.PackageEngine.Managers.Generic.NuGet/​Internal/​NuGetManifestLoader.cs Resolves unpinned manifests with expiring caching.
src/​UniGetUI.PackageEngine.Managers.Chocolatey/​Chocolatey.cs Declares source-independent installed inventory.
src/​UniGetUI.PackageEngine.Enums/​ManagerCapabilities.cs Adds uninstall, source-editing, and provenance capabilities.
src/​UniGetUI.Core.Tools/​Tools.cs Shares shell and AppleScript escaping helpers.
src/​UniGetUI.Avalonia/​Views/​SoftwarePages/​SoftwareCatalogPage.axaml Hides unsupported uninstall actions.
src/​UniGetUI.Avalonia/​Views/​SoftwarePages/​InstalledPackagesPage.cs Filters unsupported uninstall requests.
src/​UniGetUI.Avalonia/​Views/​SoftwarePages/​CatalogEditorPage.cs Opens and applies feed configuration.
src/​UniGetUI.Avalonia/​Views/​Pages/​SettingsPages/​PackageManagerPage.axaml.cs Respects source-editing capability.
src/​UniGetUI.Avalonia/​Views/​DialogPages/​PkgFeedEditor.axaml.cs Initializes the feed editor control.
src/​UniGetUI.Avalonia/​Views/​DialogPages/​PkgFeedEditor.axaml Adds feed settings and preview controls.
src/​UniGetUI.Avalonia/​ViewModels/​SoftwarePages/​SoftwareCatalogViewModel.cs Expands feeds into catalog tiles and labels.
src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​PkgFeedEditorViewModel.cs Implements feed validation and previews.
src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​CatalogEditorViewModel.cs Preserves editable feed settings.
src/​UniGetUI.Avalonia/​Program.cs Dispatches indexing and helper commands.
src/​UniGetUI.Avalonia/​Models/​SoftwareCatalog.cs Adds feed declarations and capability-based matching.
src/​UniGetUI.Avalonia/​Models/​CatalogPackageIdentitySet.cs Generalizes source-independent inventory matching.
src/​UniGetUI.Avalonia/​Infrastructure/​MacAdminFileInstaller.cs Reuses shared escaping helpers.
src/​UniGetUI.Avalonia/​Infrastructure/​AvaloniaAutoUpdater.cs Reuses shared shell quoting.
src/​UniGetUI.Avalonia.slnx Includes the MacPkg project.
src/​Languages/​lang_en.json Adds English feed and installation strings.
docs/​PKG_FEEDS.md Documents publishing, installation, and helper setup.
docs/​CLI.md Documents indexing and helper commands.
AGENTS.md Documents the macOS manager architecture.

Review effort: Balanced

Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/PkgInstaller.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/TrustedPath.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/TrustedPath.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/PkgFeed.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/Tool.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/MacPkg.cs
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/MacPkg.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/MacPkg.cs
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/PkgHelperCommand.cs
- Stage packages and mount disk images in folders created at once with
  mode 0700, with no shared parent another user could own.
- Trust a path only when each of its components, and each link it goes
  through, is protected, and when no access control entry lets a
  standard user change it.
- Give the feed index download one overall deadline, stop a package
  download that stalls, and stop a canceled tool before reporting it.
- Let the helper drop a client that stops reading instead of holding
  every later install.
- Honor catalog version pins, skip pinned builds that cannot run on the
  Mac, and find an update whichever feed offers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved privileged-installation security issues and operation reliability defects block approval.

4 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Compare updates using bundle versions, not installer versions

src/​UniGetUI.PackageEngine.Managers.MacPkg/​MacPkg.cs:193

[P2] Without a receipt, p.Installed is the app's bundle version, but p.Item.Version is the installer component's version. A component at 1.0 containing app 10.2 therefore produces a 10.1 → 1.0 update for installed app 10.1, which UpgradablePackagesLoader.IsPackageValid rejects as a downgrade. Keep update comparisons in the bundle-version domain while retaining the separate installer version for feed selection.

9 resolved since last review

Review effort: Balanced

Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/PkgInstaller.cs
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/PkgInstaller.cs
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/PkgHelperCommand.cs Outdated
Comment thread src/UniGetUI.PackageEngine.Managers.MacPkg/Internal/Tool.cs Outdated
…second review

- Behind the administrator prompt, run UniGetUI itself as root
  (pkg-helper install-item) to stage, check and install the package, so
  the user's own processes cannot swap it after the checks. A build
  standard users could replace keeps checking before the prompt.
- Authorize installs and preview feeds from the feed as it is now, never
  from its last known contents.
- Stop before installing when the operation was canceled while the feed
  was being read.
- Give a tool's whole exchange one deadline, and stop waiting for its
  pipes when a process it left running keeps them open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved privileged-feed authorization and concurrent installation issues remain, alongside update-detection and feed-handling defects.

2 open issues
5 previously missed issues

In code that hasn't changed since last review

Medium severity Clear stale previews and discard outdated feed inspection results

src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​PkgFeedEditorViewModel.cs:96

[P2] Changing Location leaves the previous feed's preview visible, and an in-flight inspection can also publish results after the address changes. This shows feed A's packages and trust warning under feed B's address even though Apply saves B. Clear the preview when Location changes and discard results and errors from inspections of an older location.

Medium severity Announce failed feed checks to screen readers

src/​UniGetUI.Avalonia/​ViewModels/​DialogPages/​PkgFeedEditorViewModel.cs:109

A failed Check feed operation only changes Summary, which is rendered as an ordinary TextBlock without a live-region announcement. Screen-reader users therefore receive no failure message when the asynchronous check finishes. Announce this error through AccessibilityAnnouncementService with Assertive priority, as the surrounding Catalog Editor does for its errors.

Medium severity Skip macOS icon conversion when indexing packages on Linux

src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​AppBundle.cs:87

[P2] Linux builds expose pkg-index, but indexing a readable .pkg or .zip with an embedded icon launches the macOS-only /usr/bin/sips here. Process.Start throws Win32Exception, which the feed/icon readers do not catch, aborting index generation. Skip icon conversion outside macOS so Linux can still index these packages without generated icons.

Medium severity Deduplicate ZIP icon entries before building the dictionary

src/​UniGetUI.PackageEngine.Managers.MacPkg/​Internal/​ZipApp.cs:89

[P2] ZIP archives can contain duplicate entry names, so duplicate Contents/Resources/AppIcon.icns entries make ToDictionary throw ArgumentException. Cold folder scans extract ZIP icons, and PkgFeed.IsUnreadable does not catch this exception, so one archive aborts the whole feed scan. Deduplicate icon entry names before constructing the dictionary.

Medium severity Keep receipt-free update comparisons in the app-version domain

src/​UniGetUI.PackageEngine.Managers.MacPkg/​MacPkg.cs:193

[P2] When receipt-free detection uses the installed app's bundle version, this constructor pairs it with the feed's package/component version, which can use a different numbering scheme. An app update from 2026.2 to 2026.3 inside package version 1.1 passes HasUpdate but is rejected as a downgrade by UpgradablePackagesLoader.IsPackageValid. Keep update comparisons in the app-version domain while retaining the feed's package version separately for installation target selection.

4 resolved since last review

Review effort: Balanced

Comment on lines +338 to +341
string staging = CoreTools.QuoteForPosixShell(Path.Combine(directory, $".{name}.unigetui-new"));
string previous = CoreTools.QuoteForPosixShell(Path.Combine(directory, $".{name}.unigetui-old"));
return $"if [ ! -e {destination} ] && [ -e {previous} ]; then /bin/mv {previous} {destination}; fi; "
+ $"/bin/rm -rf {staging} {previous}; "
if (feeds.Length == 0)
return Refuse($"No catalog with the id {request.Catalog} declares a package feed.");
var feed = feeds[0];
Uri location = feed.Location;

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants