Merge nucleic/vivid-glass-urchin-xoym into main

This commit is contained in:
2026-08-07 16:38:56 -07:00
parent 902bea5091
commit 26739f9487
12 changed files with 1214 additions and 174 deletions
+48 -29
View File
@@ -441,6 +441,11 @@ disabling sleep (`sudo pmset -a sleep 0 disablesleep 1`) so the Mac comes back i
after a power event without a human present. This does mean the disk is effectively unlocked at
boot — appropriate for a dedicated CI machine, not for a shared workstation.
On a terminal, `service install` also asks whether to configure Local Network access (§2.6) when it
is not already, defaulting to no. It never blocks: a scripted install with no terminal prints a
pointer and carries on. `--grant-local-network allowlist|prompt|none` decides it up front instead of
being asked.
`service uninstall` removes the LaunchAgent; it does not delete images or config.
### 2.6 macOS 15+ Local Network privacy prompt
@@ -449,32 +454,41 @@ Starting with macOS 15, a process that contacts other hosts on the local network
one-time Local Network permission prompt. A LaunchAgent that is denied (or that never gets a human
to click Allow) cannot reach the guest's NAT address, so VMs boot but SSH never connects.
**On a CI host, use the subnet allowlist.** It is the only deterministic option — no prompt, no GUI
session, and nothing to redo after a rebuild:
`service install` offers to configure this, and it can also be done at any time:
```sh
sudo defaults write com.apple.network.local-network \
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/18"
sudo defaults write com.apple.network.local-network \
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/18"
gitea-macos-runner permissions status # what is configured, and what to do about it
gitea-macos-runner permissions grant # configure it
```
Then **reboot** — these are read at boot, so restarting the service alone is not enough.
`grant` has two methods. Both are one command; neither needs anything pasted.
**Do not pin this to a single /24.** Virtualization.framework's NAT starts at `192.168.64.0/24` but
picks the subnet at runtime and steps to the next free one when that range is already in use, so the
same host can hand out `192.168.65.x` tomorrow. An allowlist naming only `192.168.64.0/24` then
looks configured while silently blocking every guest — the failure surfaces as `No route to host`
(errno 65) on the SSH connection, not as a permission error. The `/18` above spans
`192.168.64.0`–`192.168.127.255`, which covers the drift; if you would rather not think about
ranges at all, the RFC 1918 set `"10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16"` also works. `doctor`
reports `local network access` as a **pass** once it sees an allowlist that covers that span, and as
a **warning** when an allowlist exists but does not. Both keys are documented by Apple in
[TN3179](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy)
and are the workaround [Tart's FAQ](https://tart.run/faq/) recommends for the same problem.
**`--method allowlist` (the default) is what a CI host wants.** It writes a subnet allowlist — the
one deterministic option: no prompt, no GUI session, and nothing to redo after a rebuild. It asks
for your sudo password, reports which preferences file the write actually landed in, and then offers
to **reboot**, which is required: these values are read at boot, so restarting the service alone is
not enough. Pass `--no-reboot` to defer that.
**Why not just approve the prompt?** Because on this host there is usually nothing able to show it,
and when something does, it is attributed to the wrong program.
The default grant is all of RFC 1918 — `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16` — the same
set [Tart](https://tart.run/faq/) and orchard use. Narrow it with `--subnet`, repeatable, but **do
not pin it to a single /24**: Virtualization.framework's NAT starts at `192.168.64.0/24` but picks
the subnet at runtime and steps to the next free one when that range is already in use, so the same
host can hand out `192.168.65.x` tomorrow. An allowlist naming only `192.168.64.0/24` then looks
configured while silently blocking every guest — and the failure surfaces as `No route to host`
(errno 65) on the SSH connection, not as a permission error. `192.168.64.0/18` spans
`192.168.64.0`–`192.168.127.255`, which is the narrowest entry that covers the drift. `doctor`
reports `local network access` as a **pass** once it sees an allowlist covering that span, and as a
**warning** when an allowlist exists but does not. Both keys are documented by Apple in
[TN3179](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy).
**`--method prompt` takes effect immediately, with no reboot**, and is the better choice on a Mac
you are sitting in front of. It launches the installed `.app` through LaunchServices — which is what
makes the app its own responsible process — provokes the real system alert, and reports whether the
grant took. It needs `make install` to have run, a GUI session to show the alert in, and a Developer
ID signature for the grant to survive the next rebuild; `permissions status` reports that last one.
**Why the prompt needs that much machinery.** Left to itself, on this host there is usually nothing
able to show it, and when something does, it is attributed to the wrong program.
An app appears under **System Settings → Privacy & Security → Local Network** only *after* it has
attempted a connection to a guest, so an empty list on a fresh install is expected and means
@@ -490,14 +504,19 @@ miss:
approving it there does not carry over to the LaunchAgent. (If you are hunting for a row that
seems missing, look for Terminal rather than for "Gitea macOS Runner".)
> **And under ad-hoc signing it is not durable anyway.** Local Network privacy does not use TCC; per
> TN3179 it "uses your main executable UUID as part of its implementation", and the linker mints a
> fresh `LC_UUID` on essentially every rebuild. So `make install` after a code change presents as a
> new app that must be approved again — and macOS offers no way to reset a Local Network decision
> back to undetermined, so stale entries accumulate. Signing with a Developer ID certificate fixes
> the churn (see [Code signing](#code-signing)); the subnet allowlist above, keyed on the network
> rather than on the app, sidesteps the whole mechanism and is the recommendation for an unattended
> machine either way.
`permissions grant --method prompt` exists to thread that needle: it starts the app through
LaunchServices rather than from the shell, so the app is its own responsible process and the
decision is recorded against *its* identity — the same identity the LaunchAgent runs under.
> **Under an ad-hoc signature it is not durable anyway.** Local Network privacy does not use TCC;
> per TN3179 it "uses your main executable UUID as part of its implementation", and the linker mints
> a fresh `LC_UUID` on essentially every rebuild. So `make install` after a code change presents as
> a new app that must be approved again — and macOS offers no way to reset a Local Network decision
> back to undetermined, so stale entries accumulate. A Developer ID signature fixes the churn, since
> the identity is then anchored to the certificate rather than to the binary (see
> [Code signing](#code-signing)) — that is what makes `--method prompt` worth using at all. The
> subnet allowlist, keyed on the network rather than on the app, sidesteps the whole mechanism and
> remains the recommendation for an unattended machine.
---
@@ -531,7 +550,7 @@ The checks, in order:
| `runner download url` | The `gitea-runner` release asset is reachable |
| `token file permissions` | Warns — not fails — when a token file is group- or world-readable |
| `guest ssh` | Reachability of the most recent guest lease, when there is one. Warns on a timeout, which is most often a stale 24-hour lease for a guest that is already gone |
| `local network access` | Passes when a subnet allowlist covers `192.168.64.0/18`; warns when an allowlist exists but is scoped too narrowly; otherwise an informational note about the macOS 15+ Local Network prompt (§2.6) |
| `local network access` | Passes when a subnet allowlist covers `192.168.64.0/18`; warns when an allowlist exists but is scoped too narrowly; otherwise an informational note about the macOS 15+ Local Network prompt. Fix either with `permissions grant` (§2.6) |
If the config file is missing or invalid, the host checks still run and the rest
are skipped — which is exactly the state a first-time operator is in. Resolve