Merge nucleic/vivid-glass-urchin-xoym into main
This commit is contained in:
+51
-7
@@ -21,8 +21,9 @@ Three constraints shape everything below:
|
||||
`VZError.virtualMachineLimitExceeded` from `start()`. Concurrency is therefore
|
||||
2, permanently, and the config value is clamped rather than trusted.
|
||||
2. **Virtualization needs a GUI session and a signed bundle.** The daemon runs as
|
||||
a LaunchAgent in a logged-in user session, from inside an ad-hoc-signed `.app`
|
||||
carrying `com.apple.security.virtualization`.
|
||||
a LaunchAgent in a logged-in user session, from inside a signed `.app` carrying
|
||||
`com.apple.security.virtualization` — Developer ID when a certificate is
|
||||
available, ad-hoc otherwise (see "Verified facts", item 10).
|
||||
3. **Gitea decides which job a runner claims, not us.** We supply capacity; the
|
||||
server matches. Trying to pin a specific job to a specific VM would mean
|
||||
reimplementing Gitea's matching rules, and would be wrong the moment they
|
||||
@@ -45,7 +46,7 @@ Three constraints shape everything below:
|
||||
┌──────────────────────────────── Host (Apple Silicon Mac, macOS 26+) ─────────────────────────────┐
|
||||
│ │
|
||||
│ LaunchAgent (user session, auto-login, login.keychain unlocked) │
|
||||
│ └── GiteaMacosRunner.app (ad-hoc signed, com.apple.security.virtualization, LSUIElement) │
|
||||
│ └── GiteaMacosRunner.app (signed, com.apple.security.virtualization, LSUIElement) │
|
||||
│ │ │
|
||||
│ │ NSApplication(.prohibited).run() ── main thread, required by Virtualization │
|
||||
│ │ │
|
||||
@@ -382,7 +383,8 @@ disposable and isolated, not on the job being constrained inside it.
|
||||
and nothing else.
|
||||
* Networking is **NAT**, not bridged. Guests can reach the LAN and Gitea, but are
|
||||
not first-class hosts on it. Bridged networking would require the restricted
|
||||
`com.apple.vm.networking` entitlement, which ad-hoc signing cannot grant — a
|
||||
`com.apple.vm.networking` entitlement, which needs an Apple-approved
|
||||
provisioning profile and which ad-hoc signing cannot grant at all — a
|
||||
constraint that happens to align with what we want anyway.
|
||||
* **SSH host keys are not verified.** The peer is a VM this process booted
|
||||
moments ago on a link no other machine shares; pinning would break on every
|
||||
@@ -527,9 +529,10 @@ timeout. The `--manual-setup` fallback is out of v1 scope (§4).
|
||||
|
||||
**10. Headless Virtualization requires an `NSApplication` run loop with
|
||||
`.prohibited` activation policy, inside a signed `.app` bundle** carrying
|
||||
`com.apple.security.virtualization`. Ad-hoc signing (`codesign -s -`) suffices.
|
||||
Bridged networking would additionally need a restricted entitlement; NAT does
|
||||
not.
|
||||
`com.apple.security.virtualization`. That entitlement is unrestricted: ad-hoc
|
||||
signing (`codesign -s -`) grants it, and a Developer ID certificate grants it
|
||||
with no provisioning profile. Bridged networking would additionally need a
|
||||
restricted entitlement; NAT does not.
|
||||
→ *Consequence:* `CommandDaemon` starts `NSApplication` and runs the orchestrator
|
||||
in a detached `Task`. This applies to **every** command that starts a VM, not
|
||||
just the daemon: `vm boot`, `image build`, and `image provision` all go through
|
||||
@@ -550,6 +553,24 @@ downgrade that only surfaces as a failed VM start. And `bundle` must copy
|
||||
`.app` without them is a working binary with a broken `image build`,
|
||||
`service install`, and `config init`.
|
||||
|
||||
**10a. Which signature is used decides whether the app's code identity is stable
|
||||
across rebuilds.** A Developer ID signature's designated requirement is anchored
|
||||
to the team (`… and certificate leaf[subject.OU] = <TEAM_ID>`), so every build
|
||||
is the same program to macOS. An ad-hoc signature has no anchor, so identity
|
||||
falls back to the main executable's Mach-O UUID, which the linker regenerates on
|
||||
essentially every link.
|
||||
→ *Consequence:* this is not cosmetic, because macOS Local Network privacy is
|
||||
keyed on exactly that UUID (Fact 16). Under ad-hoc signing a grant is
|
||||
withdrawn by the next `make install`; under Developer ID it persists. `make sign`
|
||||
therefore selects a `Developer ID Application` identity matching `TEAM_ID` when
|
||||
the keychain has one and falls back to ad-hoc with a warning when it does not —
|
||||
the fallback is required because CI builds inside a throwaway guest with neither
|
||||
keychain nor certificate. The Developer ID path also passes `--options runtime
|
||||
--timestamp`, so the bundle is notarizable later without re-signing; notarization
|
||||
itself is skipped, since it governs distribution to other Macs and this app is
|
||||
built and installed in place. `Doctor.checkCodeSignature` reports which path was
|
||||
taken and warns on ad-hoc.
|
||||
|
||||
**11. macOS 15+ requires an unlocked `login.keychain` to start a VM.**
|
||||
→ *Consequence:* the service **must** be a LaunchAgent in the auto-logged-in
|
||||
user's session, never a LaunchDaemon (which has no session and no unlocked
|
||||
@@ -589,3 +610,26 @@ changing the MAC address or ECID.**
|
||||
prohibition collides directly with the per-slot MAC scheme from Fact 12, so
|
||||
adopting it would require per-slot saved states and a careful look at DHCP lease
|
||||
reuse — not a drop-in optimization.
|
||||
|
||||
**16. macOS 15+ Local Network privacy can block host→guest connections, and
|
||||
there is no reliable way to grant it interactively here.** Per
|
||||
[TN3179](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy)
|
||||
it is not TCC — the check is a Network Extension packet filter, so it is absent
|
||||
from `TCC.db`, cannot be queried, cannot be reset, and it "uses your main
|
||||
executable UUID as part of its implementation". A denial returns `EHOSTUNREACH`
|
||||
(errno 65), indistinguishable from a genuinely unreachable host. Three things
|
||||
then conspire against the interactive grant: a LaunchAgent has no UI to show the
|
||||
prompt in; a run started from a shell is attributed to the **responsible
|
||||
process**, so the prompt and the System Settings row belong to Terminal rather
|
||||
than to this app, and granting it to Terminal does not carry to the agent; and
|
||||
under ad-hoc signing the UUID keying (Fact 10a) withdraws the grant on the next
|
||||
rebuild.
|
||||
→ *Consequence:* the deterministic fix is the subnet allowlist
|
||||
(`com.apple.network.local-network`, keys `AllowedEthernetLocalNetworkAddresses`
|
||||
and `AllowedWiFiLocalNetworkAddresses`), which is keyed on the network rather
|
||||
than the app and is read at boot — so it needs a reboot, not a service restart.
|
||||
`Doctor.checkLocalNetwork` reads those keys and requires coverage of
|
||||
`192.168.64.0/18`, not a single /24, because the NAT subnet is chosen at runtime
|
||||
and slides to the next free /24. `SSHExec.localNetworkHint` appends the same
|
||||
guidance to connection failures, since errno 65 gives the operator nothing to go
|
||||
on by itself.
|
||||
|
||||
Reference in New Issue
Block a user