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
+59 -46
View File
@@ -23,12 +23,12 @@ gitea-macos-runner service status
| --- | --- | --- |
| VM won't start; entitlement / `com.apple.security.virtualization` error | Running an unsigned binary, or one outside the signed `.app` bundle | `make sign` (or re-run `make install`); invoke the installed bundle, never `.build/release/…` |
| `virtualMachineLimitExceeded` at boot | macOS allows at most **2** concurrent macOS VMs | Set `scheduler.maxConcurrentVMs` ≤ 2; kill stray VMs from earlier runs |
| VM boots but never gets an IP | DHCP lease not yet written, or Local Network privacy denial (macOS 15+) | Check `/var/db/dhcpd_leases`; grant Local Network permission or pre-authorize the subnet |
| Runner not listed under Privacy & Security → Local Network | Expected — the list is populated only after the app first attempts a local connection, and a run started from a shell is attributed to Terminal, not to the app | Allowlist the subnet with `defaults write com.apple.network.local-network` and reboot; the interactive grant does not reach the LaunchAgent |
| VM boots but never gets an IP | DHCP lease not yet written, or Local Network privacy denial (macOS 15+) | Check `/var/db/dhcpd_leases`; `gitea-macos-runner permissions grant` |
| Runner not listed under Privacy & Security → Local Network | Expected — the list is populated only after the app first attempts a local connection, and a run started from a shell is attributed to Terminal, not to the app | `gitea-macos-runner permissions grant` (allowlist, then reboot), or `--method prompt` to raise the alert as the app rather than as Terminal |
| SSH times out on a freshly built image | Guest macOS < 27, so provisioning options were ignored and Setup Assistant is waiting | Rebuild the image from a macOS **27+** IPSW |
| VMs boot in a loop; every teardown says `reason=cancelled` and nothing is logged between the lease and the teardown | `scheduler.bootTimeoutSeconds` is below the guest's *worst-case* boot on a contended host, so each clone is killed while still starting — and each replacement makes the next one slower | Raise `scheduler.bootTimeoutSeconds` (default 900) and reduce the number of concurrent guests; see [The daemon boots VMs forever](#the-daemon-boots-vms-forever-and-every-teardown-says-reasoncancelled) |
| `ssh failed: cannot connect … No route to host) (errno: 65)` part-way through provisioning | macOS 15+ Local Network privacy blocking the app — the grant is keyed on the executable's UUID, so `make install` withdraws it | Allowlist the subnet (`192.168.64.0/18`) and **reboot**; see [SSH fails with "No route to host" mid-run](#ssh-fails-with-no-route-to-host-errno-65-mid-run) |
| Allowlist is set but guests are still unreachable | It names `192.168.64.0/24` while the NAT has moved to `192.168.65.x` | Widen it to `192.168.64.0/18` and reboot; `doctor` now warns about too-narrow allowlists |
| `ssh failed: cannot connect … No route to host) (errno: 65)` part-way through provisioning | macOS 15+ Local Network privacy blocking the app — the grant is keyed on the executable's UUID, so `make install` withdraws it | `gitea-macos-runner permissions grant`, then **reboot**; see [SSH fails with "No route to host" mid-run](#ssh-fails-with-no-route-to-host-errno-65-mid-run) |
| Allowlist is set but guests are still unreachable | It names `192.168.64.0/24` while the NAT has moved to `192.168.65.x` | `gitea-macos-runner permissions grant` (defaults to all of RFC 1918) and reboot; `doctor` warns about too-narrow allowlists |
| `SecKeyCreateRandomKey` / "Interaction is not allowed" | `login.keychain` is locked — no GUI session | Run as a LaunchAgent in an unlocked GUI session; enable auto-login |
| Job stays queued forever | Label mismatch, or the daemon isn't running/reaching Gitea | Use bare label names in `runs-on`; match `runner.labels`; check daemon logs |
| `actions/checkout` fails instantly | Node.js missing from the guest image | `gitea-macos-runner image provision <name>` |
@@ -105,26 +105,24 @@ prompt**: a LaunchAgent that was never granted permission (or was denied) cannot
An entry with a recent `lease` timestamp and the guest's MAC means networking is fine and the
problem is timing — raise `scheduler.bootTimeoutSeconds`.
2. No entry at all: pre-authorize the VM subnet, then **reboot** (these are read at boot):
2. No entry at all: check and fix the permission.
```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
gitea-macos-runner permissions grant # then reboot when it offers
```
The `/18` is deliberate: the NAT subnet is chosen at runtime and slides to the next free /24
(192.168.65.x, .66.x, …) when one is taken, so a pinned `192.168.64.0/24` breaks the day it
moves. `doctor` reports `local network access` as a pass once it sees an allowlist covering that
span. This is the deterministic fix for an unattended host —
see [Local Network: the app is not listed in System Settings](#local-network-the-app-is-not-listed-in-system-settings)
for why the interactive grant is not.
`grant` pre-authorizes the VM subnets and offers to reboot, which is required — the allowlist is
read at boot. It grants all of RFC 1918 by default; `--subnet` narrows it, but nothing narrower
than `192.168.64.0/18` is safe, because the NAT subnet is chosen at runtime and slides to the next
free /24 (192.168.65.x, .66.x, …) when one is taken, so a pinned `192.168.64.0/24` breaks the day
it moves. `doctor` reports `local network access` as a pass once it sees an allowlist covering
that span.
Do not reach for the interactive grant instead. Booting a VM by hand from a Terminal makes the
prompt (and the **System Settings → Privacy & Security → Local Network** row) belong to
*Terminal* rather than to this app, and approving it there does not carry over to the
LaunchAgent.
This is the deterministic fix for an unattended host. On a Mac with someone in front of it,
`permissions grant --method prompt` applies immediately with no reboot —
see [Local Network: the app is not listed in System Settings](#local-network-the-app-is-not-listed-in-system-settings)
for what it does and why granting the prompt by hand does not work.
---
@@ -156,23 +154,36 @@ gitea-macos-runner vm boot --image default
prompt and the Settings row belong to Terminal, and allowing it there does **not** carry over to the
LaunchAgent, which is the process that actually needs it. Meanwhile the LaunchAgent itself has no UI
to show a prompt in, so from it the connection is denied outright and surfaces as `No route to host`
(errno 65) rather than as a permission error. Between the two, there is no reliable way to grant
this interactively on an unattended host.
(errno 65) rather than as a permission error.
**Fix — deterministic, and what to use on any host running the LaunchAgent.** Allowlist the VM
subnet instead. It is keyed on the network rather than on the app, so no prompt is involved, and
nothing needs redoing:
subnets instead. The allowlist is keyed on the network rather than on the app, so no prompt is
involved and nothing needs redoing:
```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 grant
```
Reboot afterwards. `doctor` then reports `local network access` as a **pass**. Both keys are
Apple's, documented in TN3179; the same pair is what [Tart's FAQ](https://tart.run/faq/) recommends
for this exact problem on CI hosts.
That asks for your sudo password, writes both of Apple's keys (documented in TN3179 — the same pair
[Tart's FAQ](https://tart.run/faq/) recommends for this exact problem on CI hosts), reports which
preferences file the write landed in, and offers to reboot. Reboot is required: the values are read
at boot. `doctor` then reports `local network access` as a **pass**.
**On a Mac you are sitting at,** `permissions grant --method prompt` is the alternative, and it
needs no reboot. It launches the installed `.app` through LaunchServices instead of exec'ing it from
the shell, which is exactly what makes the app its own responsible process — so the alert, and the
Settings row it creates, belong to the app rather than to Terminal, and the decision applies to the
LaunchAgent. It requires `make install` to have run, a GUI session, and a Developer ID signature to
be durable (see below); `permissions status` reports all three.
**If `--method prompt` reports "still blocked" and you never saw an alert,** macOS most likely
already has a decision on file for the app. It prompts exactly once per app identity and then
answers from that record forever — silently, with `EHOSTUNREACH`, and with no supported way to reset
it back to undetermined. The app *is* being evaluated under its own identity at that point (you can
confirm with `log show --last 2m --predicate 'subsystem == "com.apple.networkextension"'`, which
names the bundle ID on every attempt); the system simply is not asking. Switch the row on in
**System Settings → Privacy & Security → Local Network**, or use the allowlist, which bypasses the
per-app check entirely.
> **And an ad-hoc signed bundle cannot hold the grant anyway.** TN3179 notes that "local network
> privacy uses your main executable UUID as part of its implementation". An ad-hoc signature has no
@@ -636,27 +647,29 @@ Two details make it look intermittent rather than like a permission problem:
responsible process, so the app's own identity is not what is being evaluated when you launch it
by hand — and a grant given to Terminal does nothing for the LaunchAgent.
**Fix.** Allowlist the subnet — it is keyed on the network, not on the app, so no rebuild can
withdraw it and no prompt has to be answered:
**Fix.** Allowlist the subnets — the allowlist is keyed on the network, not on the app, so no
rebuild can withdraw it and no prompt has to be answered:
```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"
sudo reboot
gitea-macos-runner permissions grant
```
The values are only read at boot, so **the reboot is not optional** — until it happens, `defaults
read com.apple.network.local-network` shows the new setting while the filter still behaves as
before.
It writes both of Apple's keys with sudo, verifies the values read back, and offers to reboot. The
values are only read at boot, so **the reboot is not optional** — until it happens, `defaults read
com.apple.network.local-network` shows the new setting while the filter still behaves as before.
Use `/18`, not `/24`. Virtualization.framework's NAT starts at `192.168.64.0/24` but chooses the
subnet at runtime and steps to the next free /24 when that one is in use, so hosts drift to
`192.168.65.x` and beyond. An allowlist naming a single /24 that the NAT has since moved off is the
worst case: it reads as configured, `doctor` used to call it a pass, and every guest connection
still fails with errno 65. `doctor` now warns instead when the allowlist does not cover
`192.168.64.0`–`192.168.127.255`.
The default grant is all of RFC 1918. If you narrow it with `--subnet`, use `/18`, not `/24`.
Virtualization.framework's NAT starts at `192.168.64.0/24` but chooses the subnet at runtime and
steps to the next free /24 when that one is in use, so hosts drift to `192.168.65.x` and beyond. An
allowlist naming a single /24 that the NAT has since moved off is the worst case: it reads as
configured, `doctor` used to call it a pass, and every guest connection still fails with errno 65.
`doctor` now warns instead when the allowlist does not cover `192.168.64.0`–`192.168.127.255`, and
`permissions grant` warns at the point you ask for something that narrow.
**If you are at the machine and would rather not reboot,** `permissions grant --method prompt`
launches the installed app through LaunchServices so the system alert is attributed to the app
rather than to Terminal, and takes effect immediately. It needs a GUI session and a Developer ID
signature to stick across rebuilds.
**Verifying.** After the reboot, `gitea-macos-runner doctor` should show `local network access` as a
pass naming the range. Re-run the command that failed; nothing else needs redoing, and