The README is now a landing page: the idea (a mesh VPN with no server, joined with a network name and a secret), install and platform tables, exit nodes, and a short build section. Implementation detail stays in docs/. The flake builds tsng and the tray from source and ships a NixOS module (services.tsunagi). Dist install notes now show the real `join -n -s` syntax, and two links that pointed into the old README are redirected. Co-Authored-By: Claude <noreply@anthropic.com>
97 lines
3.5 KiB
Markdown
97 lines
3.5 KiB
Markdown
# Running tsunagi on macOS
|
|
|
|
The agent needs **root** on macOS: creating a `utun` interface and writing
|
|
`/etc/resolver` are both root-only, and macOS has no per-capability grant like
|
|
Linux. There are two ways to run it.
|
|
|
|
## Quick start — `sudo`
|
|
|
|
```sh
|
|
sudo tsng up
|
|
```
|
|
|
|
In another terminal, the control commands talk to it over the system control
|
|
socket and do **not** need `sudo`:
|
|
|
|
```sh
|
|
tsng status
|
|
tsng join -n <network-name> -s <tsn1…secret>
|
|
```
|
|
|
|
The agent uses fixed system paths, so the commands find it regardless of whose
|
|
shell started `sudo`:
|
|
|
|
| what | path |
|
|
|---|---|
|
|
| mandatory state | `/var/db/tsunagi` |
|
|
| disposable cache | `/var/db/tsunagi/cache` |
|
|
| control socket | `/var/run/tsunagi/agent.sock` |
|
|
|
|
## Homebrew
|
|
|
|
```sh
|
|
brew tap house-of-vanity/tap
|
|
brew install --cask tsunagi-gui # the tray app, and the agent it needs
|
|
# or just the command line agent:
|
|
brew install tsunagi
|
|
sudo brew services start tsunagi # the agent, as a root daemon at boot
|
|
```
|
|
|
|
The cask starts the service for you. The agent runs as root, and its control
|
|
socket is open to the `admin` group, so the CLI and the tray work without
|
|
`sudo` for an administrator.
|
|
|
|
## Native deployment — root LaunchDaemon
|
|
|
|
For an always-on agent that starts at boot, install the bundled
|
|
[`cy.hexor.tsunagi.plist`](cy.hexor.tsunagi.plist).
|
|
|
|
```sh
|
|
# Put the binary where the plist expects it (edit the plist for another path).
|
|
sudo install -m 0755 target/release/tsng /usr/local/bin/tsng
|
|
|
|
# Install the daemon. launchd refuses a daemon plist that is not owned by
|
|
# root:wheel or that is group/world-writable.
|
|
sudo install -m 0644 -o root -g wheel \
|
|
dist/macos/cy.hexor.tsunagi.plist \
|
|
/Library/LaunchDaemons/cy.hexor.tsunagi.plist
|
|
|
|
# Load and start it.
|
|
sudo launchctl bootstrap system /Library/LaunchDaemons/cy.hexor.tsunagi.plist
|
|
sudo launchctl enable system/cy.hexor.tsunagi
|
|
```
|
|
|
|
Manage it afterwards with:
|
|
|
|
```sh
|
|
sudo launchctl kickstart -k system/cy.hexor.tsunagi # restart
|
|
sudo launchctl bootout system/cy.hexor.tsunagi # stop and unload
|
|
tail -f /var/log/tsunagi.log # logs
|
|
```
|
|
|
|
The control commands (`status`, `join`, `network`, `dns`) work the same against
|
|
the daemon as against a `sudo tsng up`, over the same socket.
|
|
|
|
## What the agent does to the system
|
|
|
|
Everything it installs is tagged to its own interface, computed from local
|
|
state, and reversible — it is removed when a network leaves, broadcast is turned
|
|
off, or the agent stops:
|
|
|
|
- a **`utun` interface**, configured with `ifconfig` (addresses, MTU, up) and a
|
|
subnet `route` for the overlay range. The kernel assigns the `utunN` name; the
|
|
agent adopts whatever it is given. A `utun` is torn down with the process, so
|
|
a crash never leaves one behind.
|
|
- `/etc/resolver/<network>` files pointing the system resolver at the overlay
|
|
DNS server for each network's zone, with a `port` so the server stays off 53.
|
|
Each file carries a marker line; the agent only ever replaces or removes files
|
|
it wrote, never one you created by hand.
|
|
- while broadcast is on, a `255.255.255.255` **route** through the overlay
|
|
interface. The matching inbound-UDP firewall allowance is **not** implemented
|
|
yet (it needs a `pf` anchor); `tsng status` reports that half as incomplete
|
|
and names what to allow by hand if your host firewall drops discovery replies.
|
|
|
|
Nothing taken from a remote peer ever becomes a path, a command argument or an
|
|
OS setting: the interface name comes from the kernel, addresses and ranges from
|
|
signed state the agent already verified.
|