Merge nucleic/vivid-glass-urchin-xoym into main
This commit is contained in:
+59
-46
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user