Merge nucleic/mellow-dewy-falcon-rjhr into main
This commit is contained in:
+14
-8
@@ -401,16 +401,22 @@ session, and nothing to redo after a rebuild:
|
||||
|
||||
```sh
|
||||
sudo defaults write com.apple.network.local-network \
|
||||
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/24"
|
||||
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/18"
|
||||
sudo defaults write com.apple.network.local-network \
|
||||
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/24"
|
||||
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/18"
|
||||
```
|
||||
|
||||
Then **reboot** — these are read at boot, so restarting the service alone is not enough. Adjust the
|
||||
range to match the subnet Virtualization.framework's NAT hands out on your host (check
|
||||
`/var/db/dhcpd_leases` after a VM boots); if you would rather not pin it, 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 can see an allowlist. Both keys are documented by Apple in
|
||||
Then **reboot** — these are read at boot, so restarting the service alone is not enough.
|
||||
|
||||
**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.
|
||||
|
||||
@@ -464,7 +470,7 @@ The checks, in order:
|
||||
| `registration token` | A static token resolves, or one can be fetched when `fetchRegistrationTokenViaAPI` is on |
|
||||
| `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 |
|
||||
| `local network access` | Passes when a subnet allowlist is set; 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 (§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
|
||||
|
||||
+69
-6
@@ -26,6 +26,8 @@ gitea-macos-runner service status
|
||||
| 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; it cannot be pre-approved | Boot one VM by hand from a GUI Terminal to create the entry, or (better on CI) allowlist the subnet with `defaults write com.apple.network.local-network` |
|
||||
| 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 |
|
||||
| `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 |
|
||||
| `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,13 +107,15 @@ prompt**: a LaunchAgent that was never granted permission (or was denied) cannot
|
||||
|
||||
```sh
|
||||
sudo defaults write com.apple.network.local-network \
|
||||
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/24"
|
||||
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/18"
|
||||
sudo defaults write com.apple.network.local-network \
|
||||
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/24"
|
||||
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/18"
|
||||
```
|
||||
|
||||
Match the range to what your host's NAT actually hands out. `doctor` reports `local network
|
||||
access` as a pass once it can see this. This is the deterministic fix for an unattended host —
|
||||
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.
|
||||
|
||||
@@ -151,9 +155,9 @@ on the network rather than on the app, so no prompt is involved and nothing need
|
||||
|
||||
```sh
|
||||
sudo defaults write com.apple.network.local-network \
|
||||
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/24"
|
||||
AllowedEthernetLocalNetworkAddresses -array "192.168.64.0/18"
|
||||
sudo defaults write com.apple.network.local-network \
|
||||
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/24"
|
||||
AllowedWiFiLocalNetworkAddresses -array "192.168.64.0/18"
|
||||
```
|
||||
|
||||
Reboot afterwards. `doctor` then reports `local network access` as a **pass**. Both keys are
|
||||
@@ -513,3 +517,62 @@ explicitly — that state is unrecoverable, and the fix is `image delete` follow
|
||||
An image that is already `provisioned` is untouched; `image build` still refuses with
|
||||
`image '<name>' already exists`. To re-run provisioning on a finished image, use
|
||||
`gitea-macos-runner image provision <name>` instead.
|
||||
|
||||
---
|
||||
|
||||
## SSH fails with "No route to host" (errno 65) mid-run
|
||||
|
||||
**Symptom.** A command that had *just* talked to the guest successfully suddenly cannot reach it —
|
||||
most visibly `image provision`, which waits for SSH, reports the first provisioning step, and then
|
||||
dies on the upload:
|
||||
|
||||
```
|
||||
first boot + guest provisioning…
|
||||
provisioning: system configuration (provision.sh)
|
||||
error: provisioning failed: could not upload provision.sh from …/provision.sh:
|
||||
ssh failed: cannot connect to 192.168.65.232:22: … No route to host) (errno: 65)
|
||||
```
|
||||
|
||||
**Cause.** On macOS 15 and newer, an app that has not been granted Local Network access does not get
|
||||
a "permission denied": the packet filter answers **`EHOSTUNREACH` — errno 65, "No route to host"**,
|
||||
which is indistinguishable from a guest that is genuinely off the network. Guests here live on a
|
||||
host-private NAT link that is reachable whenever the VM is up, so on this path errno 65 is far more
|
||||
often the privacy filter than a routing problem.
|
||||
|
||||
Two details make it look intermittent rather than like a permission problem:
|
||||
|
||||
- **The grant is keyed on the executable's UUID.** Per
|
||||
[TN3179](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy),
|
||||
Local Network privacy "uses your main executable UUID as part of its implementation", and the
|
||||
linker mints a fresh `LC_UUID` on essentially every build. A `make install` after a code change
|
||||
therefore presents a program macOS has never seen, whose permission is undetermined again — even
|
||||
though the binary you ran ten minutes ago worked.
|
||||
- **Processes started over SSH are exempt.** Running the same command through `ssh you@host …`
|
||||
succeeds while running it from a GUI Terminal fails. A remote-shell success proves nothing about
|
||||
the interactive path.
|
||||
|
||||
**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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
**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
|
||||
`image provision` is idempotent, so a partially completed run is safe to repeat.
|
||||
|
||||
Reference in New Issue
Block a user