From bb86a4273727a517de077aa3eca811a5b64c7d7c Mon Sep 17 00:00:00 2001 From: jrtxio Date: Fri, 23 Oct 2026 16:55:48 +0800 Subject: [PATCH] feat(package): macos-resource-files manifest key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Downstream projects need files at the macOS bundle's Contents/Resources root — the standard home for InternetAccessPolicy.plist, which Little Snitch-class firewalls read and surface to the user as the app's network policy. The `resources` key deliberately stages under Contents/Resources/app to keep paths cross-platform, so there was no sealable way to reach the Resources root. `macos-resource-files` copies unique project-relative files verbatim into Contents/Resources before signing; a name colliding with a rivet-managed bundle file (the packaged icon) fails the package, and a missing file fails the package rather than shipping an incomplete policy. Documents the key in docs/configuration.md. --- CHANGELOG.md | 9 +++++++++ docs/configuration.md | 1 + rivet-cli/package.rkt | 18 ++++++++++++++++++ rivet-cli/project.rkt | 21 +++++++++++++++++++++ 4 files changed, 49 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1884ed0..257de57c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Unreleased + +### Added + +- `macos-resource-files`: a rivet.rktd key listing project-relative files + copied verbatim into the macOS bundle's `Contents/Resources` root before + signing — the standard home for `InternetAccessPolicy.plist`, which + firewall apps surface as the app's network policy. Collisions with + rivet-managed bundle names fail the package. ## 0.13.0 - Add an Android selected-device connection coordinator joining live diff --git a/docs/configuration.md b/docs/configuration.md index 4de6e4cc..14238b44 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -123,6 +123,7 @@ strings. Only declare languages the application itself already ships. - `macos-icon` — an optional project-relative `.icns` file copied into the app bundle and declared through `CFBundleIconFile`. A package without the key warns: the bundle then ships without a Dock/Finder icon. - `linux-icon` — an optional project-relative `.png` file installed as the desktop icon (`/usr/share/pixmaps`, referenced by the generated `.desktop` entry) and used as the AppImage top-level icon. AppImage packaging fails closed without it; deb and rpm warn and install without a desktop icon when the key is omitted. - `linux-formats` — an optional subset of `("deb" "rpm" "appimage")` selecting which native Linux installer formats `raco rivet release` builds. The default builds all three; the signed tar.gz update payload is always produced regardless. +- `macos-resource-files` — a list of unique project-relative files copied verbatim into the macOS bundle's `Contents/Resources` root before signing. The motivating consumer is `InternetAccessPolicy.plist`: Little Snitch-class firewalls read it from the Resources root and surface its per-host purpose and deny consequences to the user. Names collide fail closed against rivet-managed bundle files (the packaged icon). - `dmg-background` — an optional project-relative `.png` used as the DMG window background. Declaring it switches `raco rivet release` to the branded drag-to-install layout: the volume carries an `/Applications` symlink and the DMG window gets a fixed 660x400 geometry with both icons placed. Render the PNG at 1320x800 (2x) so it stays sharp on Retina displays; Finder scales it to the window. Without the key the DMG stays a plain single-folder image. - `windows-license` — an optional project-relative `.rtf` file shown on the MSI wizard's license page. Without the key, the release pipeline renders the repository `LICENSE` text (or `LICENSE.txt`/`COPYING`) into an RTF automatically; a project with neither ships a neutral notice pointing at the license that installs with the application. - `windows-installer-bitmaps` — an optional hash with `banner` and `dialog` values naming project-relative `.bmp` files for the MSI wizard (493x58 and 493x312 respectively). Unset values keep the wizard's built-in artwork. diff --git a/rivet-cli/package.rkt b/rivet-cli/package.rkt index 06c95062..c5c7235f 100644 --- a/rivet-cli/package.rkt +++ b/rivet-cli/package.rkt @@ -2,6 +2,7 @@ (require racket/file racket/format + racket/list racket/path racket/string racket/system @@ -344,6 +345,23 @@ (build-path resources packaged-icon-name) #t)) + ;; Extra Resources-root files (InternetAccessPolicy.plist and friends). + ;; Copied before the seal like every other bundle input; a name that + ;; would collide with a rivet-managed file fails the package here. + (define managed-resource-names + (list* "AppIcon.icns" (map (lambda (f) (path->string (file-name-from-path f))) + (project-macos-resource-files project)))) + (unless (= (length managed-resource-names) + (length (remove-duplicates managed-resource-names))) + (error 'package-macos! "macos-resource-file name collides with a rivet-managed bundle file")) + (for ([resource-file (in-list (project-macos-resource-files project))]) + (define source (project-path project resource-file)) + (unless (file-exists? source) + (error 'package-macos! "macos-resource-file does not exist: ~a" resource-file)) + (define target-name + (path->string (file-name-from-path source))) + (copy-file source (build-path resources target-name) #t)) + (define racket-framework (build-path frameworks "Racket.framework")) (copy-macos-bundle! (build-path stage "Frameworks" "Racket.framework") racket-framework) diff --git a/rivet-cli/project.rkt b/rivet-cli/project.rkt index 8b1dacfc..7d13305b 100644 --- a/rivet-cli/project.rkt +++ b/rivet-cli/project.rkt @@ -31,6 +31,7 @@ project-macos-localizations project-linux-icon project-dmg-background + project-macos-resource-files project-windows-license project-windows-installer-bitmaps project-linux-formats @@ -228,6 +229,19 @@ (and (memq key '(banner dialog)) (path-string-has-extension? item ".bmp"))))) "hash whose optional banner and dialog values are project-relative .bmp paths") + (optional 'macos-resource-files + (lambda (v) + (define relative-file? + (lambda (item) + (and (string? item) + (not (string=? item "")) + (not (string-prefix? item "/")) + (not (regexp-match? #rx"(^|/)\\.\\." item))))) + (and (list? v) + (not (null? v)) + (andmap relative-file? v) + (= (length v) (length (remove-duplicates v))))) + "unique project-relative file paths copied to the macOS bundle's Contents/Resources root") (optional 'linux-icon (lambda (v) (path-string-has-extension? v ".png")) @@ -351,6 +365,13 @@ (define (project-dmg-background project) (project-ref project 'dmg-background (lambda () #f))) +;; Extra files copied verbatim into the macOS bundle's Contents/Resources +;; root (staged before signing, like every other bundle input). The +;; motivating consumer is InternetAccessPolicy.plist — Little Snitch-class +;; firewalls surface it to the user as the app's network policy. +(define (project-macos-resource-files project) + (project-ref project 'macos-resource-files (lambda () '()))) + ;; The MSI license page shows this RTF verbatim. Projects that do not ;; configure one get their repository LICENSE text rendered into RTF. (define (project-windows-license project)