Files
tsunagi/dist/macos
Aleksandr BogomiakovandClaude c184ef0914 Rewrite README as a short project front page; add a Nix flake
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>
2026-10-03 17:47:44 +01:00
..

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 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.