Public Access
Give the desktop a macOS head, signed from the first release
The same application, the same Velopack and the same two-phase person-run release as Windows, with four things forced to differ. Signing is a precondition rather than an improvement: Gatekeeper refuses an un-notarized download outright instead of warning about it, so there was never the "unsigned for now" that ADR 0013 decision 8 argues for on Windows, and release-macos.sh refuses to start without the identities. The packaging split is narrower than it first looked, and the old claim at the foot of ci.yml is why it was worth checking rather than assuming. vpk cross-compiles when told to: 'vpk [osx] bundle' builds a real .app on any platform, and CI now publishes osx-arm64 and bundles it on every main and tag build, which is what catches a restore graph with no macOS native asset. There is no '[osx] pack' off a Mac, and that part is correct — pack drives codesign, notarytool and stapler, which exist nowhere else. The dylib signing loop in the script looks redundant beside vpk's own pass and is not. vpk signs with 'codesign --deep', which is the shape Apple documents as wrong for nested code, and platform-flags has recorded a notarization rejection that names no file since before any of this existed. Signing each native binary inside-out first leaves that pass nothing to get wrong. MacDeviceKeyStore reaches ADR 0007's conclusion through different hardware: a P-256 key in the Secure Enclave under an access control requiring user presence, so the platform enforces the gate rather than this process — which is the whole point of that ADR's amendment. The enclave holds no other kind of key, hence ECIES where Windows uses RSA-OAEP, and the shape that falls out is better than the Windows one: sealing needs only the public half and is silent, so only unlock prompts. IsSupported probes rather than infers, because three ordinary Macs answer no — an Intel machine without a T2, one with no login password, and every unsigned development build, since enclave keys need a signing identity. Two decisions worth stating because they are reversible. arm64 only: a second channel is small work and nobody here has an Intel Mac to walk Phase 18 on, and an x64 package would be the only artefact in this repository reaching users unverified. And the pack id stays DodoSSH.Desktop even though vpk names the bundle after it, so /Applications holds DodoSSH.Desktop.app: decision 2's reasoning binds harder here, because a pack id of DodoSSH would put Velopack's install root on top of ClientPaths.DataDirectory and let an uninstall take the user's un-synced outbox with it. CFBundleDisplayName puts the product name back in front of a person. Measured rather than assumed, since none of it is obvious: the publish and the bundle were both run, LSMinimumSystemVersion is 12.0 because that is the minos in the apphost's own LC_BUILD_VERSION, and vpk copies a custom Info.plist verbatim with no substitution at all — which is why the plist is a template the script renders and not a committed file. What is not done is the half that needs the hardware. There is no macOS runner, so nothing past "it bundles" has ever run. Phase 18 is the whole of the verification, and the two checks most likely to fail are the terminal against WKWebView and the enclave interop, neither of which has executed once.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
The Info.plist for the macOS bundle, with the version left as a placeholder.
|
||||
|
||||
◆ A TEMPLATE RATHER THAN A FILE, BECAUSE vpk COPIES A CUSTOM PLIST VERBATIM.
|
||||
|
||||
Measured, not assumed: `vpk [osx] bundle --plist` performs no substitution of any kind. It logs
|
||||
"Bundle using provided Info.plist" and copies the bytes. That is also why it refuses --plist and
|
||||
--bundleId together — with a plist supplied, every key is the caller's problem.
|
||||
|
||||
So a committed Info.plist would carry whatever version it was written with into every release
|
||||
afterwards, and the failure is quiet in the worst way: Velopack's own release index would carry the
|
||||
right version, the updater would compare correctly and update correctly, and only the About window,
|
||||
Finder's Get Info panel and any crash report would claim the build was something else. Nobody
|
||||
reads those on the day of a release. scripts/release-macos.sh substitutes @VERSION@ into a copy
|
||||
and passes that.
|
||||
|
||||
◆ WHY A CUSTOM PLIST AT ALL, WHEN vpk WRITES A PERFECTLY GOOD ONE.
|
||||
|
||||
Three keys it does not write, each of which is a real defect without it:
|
||||
|
||||
CFBundleDisplayName The bundle on disk is DodoSSH.Desktop.app, because the pack id must not
|
||||
be DodoSSH — see scripts/release-macos.sh for the directory collision
|
||||
that rule prevents. On Windows the pack id is invisible; on macOS it
|
||||
names the thing in /Applications and in the Dock. This key is what puts
|
||||
"DodoSSH" back in front of a person while the bundle keeps the id.
|
||||
|
||||
LSMinimumSystemVersion Without it macOS will happily launch this on a release the runtime was
|
||||
never built for, and the user gets a dyld crash rather than a sentence.
|
||||
|
||||
NSHumanReadableCopyright Shown in the About panel. Absent, the panel shows a blank line.
|
||||
-->
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!--
|
||||
CFBundleName is what the menu bar shows and is capped at 15 characters by convention;
|
||||
CFBundleDisplayName is what Finder and the Dock show. Both say DodoSSH, and the bundle
|
||||
directory does not. See the note above.
|
||||
-->
|
||||
<key>CFBundleName</key>
|
||||
<string>DodoSSH</string>
|
||||
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>DodoSSH</string>
|
||||
|
||||
<!--
|
||||
Reverse-DNS under the domain this project actually controls. It is the identity Gatekeeper,
|
||||
the notary service and the keychain all key off, so it is as irreversible as the Windows pack
|
||||
id: changing it makes an update a different application, and it orphans anything the previous
|
||||
identifier stored — including the Secure Enclave key MacDeviceKeyStore holds, which is scoped
|
||||
to this identifier and cannot be migrated because its whole point is that it never leaves the
|
||||
enclave.
|
||||
-->
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>dev.dodotech.dodossh</string>
|
||||
|
||||
<!--
|
||||
The apphost the publish produced, named for the product by <AssemblyName> in the csproj rather
|
||||
than for the project. Must match --mainExe or the bundle launches nothing.
|
||||
-->
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>DodoSSH</string>
|
||||
|
||||
<!--
|
||||
Both version keys take the numeric core only — 1.2.3 and never 1.2.3-rc.1 — because Apple
|
||||
defines them as one to three dot-separated integers and notarization rejects what it cannot
|
||||
parse. The full version, prerelease suffix and all, is in Velopack's release index, and that
|
||||
is the one the updater compares. These two are for Finder and for Gatekeeper.
|
||||
|
||||
They are the same value rather than the usual marketing/build split, because there is no build
|
||||
counter here that a release does not already bump.
|
||||
-->
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@VERSION@</string>
|
||||
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@VERSION@</string>
|
||||
|
||||
<!--
|
||||
The file name inside Contents/Resources, which is where --icon puts it. With a custom plist
|
||||
nothing rewrites this key, so a rename of the asset that forgets this line produces a bundle
|
||||
showing the generic application icon and no error anywhere.
|
||||
-->
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>dodossh.icns</string>
|
||||
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
|
||||
<!--
|
||||
12.0, and it is read off the binaries rather than off a support matrix. The apphost and
|
||||
libcoreclr.dylib in a net10.0 osx-arm64 publish both carry LC_BUILD_VERSION with minos 12.0.0,
|
||||
so 12.0 is the oldest release these bytes are built to load on.
|
||||
|
||||
Microsoft's *support* statement for .NET 10 is higher than this, and that difference is
|
||||
deliberate rather than overlooked: this key decides whether macOS refuses to launch the app at
|
||||
all, and refusing on a release where it would in fact have run is the worse of the two errors.
|
||||
A user on an unsupported-but-working macOS gets the application; the support matrix governs
|
||||
what gets fixed if it misbehaves there, which is a different question.
|
||||
-->
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
|
||||
<!--
|
||||
Without this the window is drawn at 1x and scaled up, which on a Retina display turns the
|
||||
terminal — the one surface in this application that is nothing but small text — into a blur.
|
||||
-->
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
|
||||
<!--
|
||||
False, and stated rather than left out. An agent application has no Dock icon and no menu bar;
|
||||
this one is an ordinary windowed application and the default is already false, but the key
|
||||
being absent is indistinguishable from somebody having removed it.
|
||||
-->
|
||||
<key>LSUIElement</key>
|
||||
<false/>
|
||||
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© DodoTech. MIT licensed.</string>
|
||||
</dict>
|
||||
</plist>
|
||||
Reference in New Issue
Block a user