Linux and macOS
Low ports differ by platform. Here is the privileged step each one needs, what it changes, and where grove keeps its state.
Grove prints the privileged step in full and runs it only at a prompt or with --yes. The steps are argv lists, printed and executed from the same slice, so what was pasted and what grove ran cannot differ.
Linux
Linux’s floor on privileged ports is per-port, not per-address, so lowering it is all that’s needed. Install writes one line into a file named for grove and stages it; installing it is the privileged step:
sudo cp <staged> /etc/sysctl.d/60-grove.conf
sudo sysctl -p /etc/sysctl.d/60-grove.conf
The line is net.ipv4.ip_unprivileged_port_start=80: 80 rather than 443 so grove can also answer plain http and send it to https. The machine reads the file again at every boot, and the daemon runs as you rather than as root. With the floor lowered, the daemon binds 127.0.0.4:443 and 127.0.0.4:80 directly.
grove uninstall offers to remove that file and put the floor back to 1024. Only grove’s own file is taken back: a floor lowered some other way is the machine’s setting.
macOS
macOS can’t lower anything, so the daemon binds 10443 and 10080 and a pf rule redirects. The rule is scoped to 127.0.0.4 rather than the whole interface: an earlier unscoped version took every loopback connection on 443, so a lando proxy could bind the port and never be reached.
macOS also needs the address itself. It configures only 127.0.0.1 on the loopback interface, so 127.0.0.4 has to be added as an alias, and a reboot removes it. grove install stages a small root-owned launchd job that puts the alias and the pf rules back at boot. That’s the address and the firewall rule coming back, not grove: nothing starts a daemon at boot on either platform.
Removing a trust root on macOS asks for authorization, so grove uninstall does too.
Where state lives
| Linux | macOS | |
|---|---|---|
| State directory | $XDG_STATE_HOME/grove, or ~/.local/state/grove |
~/Library/Application Support/grove |
| Control socket | $XDG_RUNTIME_DIR/grove/control.sock, or control.sock in the state directory |
control.sock in the state directory |
The state directory holds the CA, ports.json, and daemon.log. GROVE_STATE_DIR and GROVE_SOCKET override each, and the socket one matters on macOS: a unix socket path caps out near 104 bytes there, and a deep home directory can blow it on its own.
Trying a change without disturbing the daemon in use
One daemon serves the machine, and grove restart with the defaults stops the one serving your real worktrees. Point GROVE_STATE_DIR and GROVE_SOCKET into a temp directory and GROVE_LISTEN and GROVE_HTTP_LISTEN at high ports, and a second daemon runs beside the live one.