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>
3.5 KiB
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
sudo tsng up
In another terminal, the control commands talk to it over the system control
socket and do not need sudo:
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
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.
# 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:
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
utuninterface, configured withifconfig(addresses, MTU, up) and a subnetroutefor the overlay range. The kernel assigns theutunNname; the agent adopts whatever it is given. Autunis 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 aportso 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.255route through the overlay interface. The matching inbound-UDP firewall allowance is not implemented yet (it needs apfanchor);tsng statusreports 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.