diff --git a/README.md b/README.md index 5ad222b..5da1cf0 100644 --- a/README.md +++ b/README.md @@ -1,260 +1,122 @@ # tunmux -`tunmux` is a WireGuard config-file VPN CLI written in Rust for **macOS**. It -connects from a standard WireGuard `.conf` file (or a saved profile) and routes -host traffic through the tunnel (direct mode). +`tunmux` is a command-line WireGuard VPN client for macOS, written in Rust. -## What It Does +## Why -- Connect/disconnect from WireGuard `.conf` files and saved profiles -- Save, list, and remove reusable profiles -- Run connect/disconnect hooks (connectivity, external-IP, DNS-leak checks) -- Support multiple WireGuard backends: `userspace` (default), `wg-quick`, `kernel` +Install once, forget about it. -On macOS there is no in-kernel WireGuard, so every backend runs on the embedded -`gotatun` userspace engine through a built-in helper; no separate `gotatun` CLI -install is required. The `kernel` backend brings the tunnel up from a regenerated -minimal config, while `userspace`/`wg-quick` use the `.conf` as-is. +tunmux is built for one main use case: a split tunnel that permanently connects +your workstation to "home" — your LAN, your servers, your internal DNS — as a +launchd daemon that is simply always there and never gets in the way. You run +`make install` once; from then on the tunnel comes up at login, survives +network roaming and sleep/wake, and continuously reconciles routes and DNS +against whatever network you are currently on. -## Platform And Requirements +Because reconciliation is continuous, the tunnel does not need the conditional +on/off triggers ("On-Demand" rules, location-based activation) that split +tunnels usually require. It stays up everywhere and adapts instead of asking. +It is meant as a frictionless, dependable alternative to `WireGuard.app` from +the Apple App Store, for people who would rather manage the tunnel from the +command line — or not manage it at all. -- macOS (Apple Silicon or Intel) -- Rust (stable, edition 2021) -- `sudo` access for privileged operations (`tunmux privileged --serve`) +## Origins -The privileged service runs as root and performs the operations that need -elevated permissions (bringing tunnels up/down, reading the WireGuard control -socket). It can be started on demand (autostart) or via `launchd` socket -activation (see `etc/me.pansen.tunmux.privileged.plist`). +This project is a fork of [CaddyGlow/tunmux](https://github.com/CaddyGlow/tunmux). +Thanks to the original author, who pursued a different goal with the project — +it serves here as the technical base. -## Build +## Install ```bash -cargo build -``` - -To catch formatting issues before they hit CI, install the repo's git hooks once per clone: - -```bash -make hooks -``` - -This points `core.hooksPath` at `scripts/hooks`, which runs `cargo fmt --all --check` on commit. - -## Release CI (Tag-Based) - -Pushing a `v*` tag (for example `v1.2.3`) triggers `.github/workflows/release.yml` to: -- run `cargo test --locked` -- build release binaries for macOS targets via `.github/workflows/manual-build.yml` -- upload tarballs and SHA256 files to a GitHub Release for that tag - -Binary version output follows the tag in CI builds: - -```bash -tunmux --version -``` - -## Quick Start - -Connect from a config file, check status, disconnect: - -```bash -tunmux connect wgconf --file ./my-tunnel.conf -tunmux status -tunmux disconnect --provider wgconf -``` - -Choose a backend (default is `userspace`): - -```bash -tunmux connect wgconf --file ./my-tunnel.conf --backend wg-quick -``` - -Before testing the userspace data plane, disable WireGuard.app On-Demand and -deactivate matching tunnels. Verify `scutil --nc list` has no connected -`com.wireguard.macos` entry. - -## Command Map - -Top-level commands: - -```bash -tunmux status -tunmux connect wgconf [flags] -tunmux disconnect [instance] [--provider wgconf] [--all] -tunmux wg -tunmux hook run -tunmux hook debug [instance] [--provider wgconf] [--event ifup|ifdown] -tunmux wgconf <...> +make install TUNMUX_PROFILE=/path/to/your.conf ``` -`wgconf` flows: - -```bash -tunmux connect wgconf --file ./my-tunnel.conf --backend wg-quick -tunmux connect wgconf --file ./my-tunnel.conf --save-as office -tunmux connect wgconf --profile office -tunmux connect wgconf --file ./ipv4-only.conf --backend kernel --disable-ipv6 -tunmux connect wgconf --file ./my-tunnel.conf --backend kernel --mtu 1280 -tunmux wgconf save --file ./my-tunnel.conf --name backup -tunmux wgconf list -tunmux wgconf remove backup -tunmux wgconf status -tunmux disconnect --provider wgconf -``` +This does three things: -Both the top-level form (`tunmux connect wgconf ...`) and the provider-prefixed -form (`tunmux wgconf connect ...`) are supported. +- Builds the release binary and installs it to `/usr/local/bin/tunmux`. +- Registers a **privileged launchd daemon** (socket-activated, runs as root) + that performs the operations needing elevation: bringing tunnels up and + down and talking to the WireGuard control interface. It starts on demand + and idles otherwise. +- Registers a **per-user login agent** that connects your profile at login + and re-checks every 60 seconds. The connect is idempotent: if the tunnel + is already up it is a no-op, if it dropped it is brought back. -Disconnect semantics: - -```bash -tunmux disconnect --all # all active connections -tunmux disconnect --provider wgconf --all # wgconf only -tunmux disconnect # exact instance -tunmux disconnect --provider wgconf # provider-scoped single/list behavior - -# short forms -tunmux disconnect -a -tunmux disconnect -p wgconf -a -``` +Access to the privileged daemon is limited to a dedicated `tunmux` group, +which the install creates and adds you to (a re-login may be needed for the +membership to take effect). -Common short forms: -- `connect`: `-b` (backend) -- `disconnect`: `-p` (provider), `-a` (all) +After that there is nothing to babysit. `make uninstall` removes everything +cleanly, including any DNS override. -Use verbose logs when needed: +## What It Does While You Forget About It -```bash -tunmux -v connect wgconf --file ./my-tunnel.conf -tunmux --debug wgconf disconnect -RUST_LOG=debug tunmux disconnect --all -``` +- Keeps the tunnel connected across network roaming (Wi-Fi → Ethernet, + Wi-Fi A → Wi-Fi B) and sleep/wake, without a manual reconnect. +- Continuously reconciles **routes** against the live network: tunnel routes + that would hijack the currently active LAN are dropped, so the split tunnel + behaves correctly whether you are at home, in the office, or tethered. +- Reconciles **DNS** as well as it can, so lookups for your internal names + keep resolving through the tunnel as the network underneath changes. +- Can confirm that a connection is doing its job: basic connectivity over + IPv4 and IPv6, traffic actually leaving through the tunnel, and DNS not + leaking to other resolvers. -Force or disable ANSI color in logs: +## How It Works -```bash -TUNMUX_LOG_COLOR=always tunmux status -TUNMUX_LOG_COLOR=never tunmux status -``` +macOS has no in-kernel WireGuard. Every backend therefore runs on a bundled +userspace WireGuard engine, [gotatun](https://github.com/mullvad/gotatun), +through a built-in helper, so there is nothing extra to install. -## Flags +tunmux is split into two parts. The command you run as your normal user handles +configuration and status. The separate privileged daemon, running as root, +performs the operations that need elevated permissions. Running a root service +is a real privilege boundary, so it is kept small and does only the operations +that require it. -`--disable-ipv6` is accepted only for the `kernel` backend, and only when the -selected WireGuard config has no IPv6 interface address. +Only one connection is active at a time. While it is up, routing and DNS +follow the current network. -`--mtu` applies to the `kernel` and `userspace` backends. `wgconf` reads `MTU =` -from `[Interface]`; an explicit `--mtu` overrides it. +## Backends -`--if-missing` exits `0` without reconnecting when the same source is already the -live tunnel; a different live source still errors. +There are three ways to bring the tunnel up: -## Direct Mode Details +- `userspace` (default) and `wg-quick` use your config as written. +- `kernel` brings the tunnel up from a regenerated minimal config. -- one direct connection is active at a time -- host traffic is routed through that WireGuard tunnel -- stored internally as `_direct` connection state -- routing and DNS adapt live to network changes (roam, suspend/resume) without a - reconnect +All three run on the same embedded userspace engine; the backend only changes +how the tunnel is set up. ## Configuration -`tunmux` reads optional defaults from: +tunmux reads optional defaults from `$XDG_CONFIG_HOME/tunmux/config.toml` (typically `~/.config/tunmux/config.toml`). The file is optional; without it, sensible defaults apply. It covers the default backend, the optional checks, and how the privileged daemon is started and stopped. Anything set in the config can be overridden per command on the command line. -`~/.config/tunmux/config.toml` +## Running Alongside the WireGuard App -Example: +Do not run tunmux at the same time as the official WireGuard app with On-Demand +enabled for the same tunnel. The two will compete over the connection. Turn off +On-Demand and deactivate matching tunnels in the app first. -```toml -[general] -backend = "userspace" # userspace (default), wg-quick, or kernel -hooks = { ifup = ["builtin:connectivity", "builtin:external-ip"], ifdown = [] } -privileged_transport = "socket" # socket or stdio -privileged_autostart = true -privileged_autostart_timeout_ms = 5000 -privileged_authorized_group = "tunmux" -privileged_autostop_mode = "never" # never, command, timeout -privileged_autostop_timeout_ms = 30000 +## Requirements -[wgconf] -hooks = { ifup = [], ifdown = [] } -``` +- macOS on Apple Silicon. +- A stable Rust toolchain to build from source. +- `sudo` access for the install and privileged operations. -CLI flags override config values. - -Hook behavior: -- `general.hooks` runs first, then `wgconf` hooks run after it. -- `ifup` runs after successful connect; `ifdown` runs after successful disconnect. -- Built-ins are opt-in via hook entries: - - `builtin:connectivity`: ping IPv4 (`1.1.1.1`) and IPv6 (`2606:4700:4700::1111`) - - `builtin:external-ip`: fetch external IP via `https://ipinfo.io` and `https://v6.ipinfo.io` - - `builtin:dns-detection`: query `https://-.ipleak.net/dnsdetection/` - with a 40-char random host label and incrementing probe number (`-1` to `-10`), - then run reverse DNS lookup for each recovered resolver IP -- Hook commands run with env vars such as `TUNMUX_HOOK_EVENT`, `TUNMUX_PROVIDER`, - `TUNMUX_INSTANCE`, `TUNMUX_BACKEND`, `TUNMUX_INTERFACE`, `TUNMUX_SERVER`, - `TUNMUX_ENDPOINT`, plus `TUNMUX_DNS_SERVERS` when VPN DNS servers are known. -- `dns-detection` reverse DNS lookup prefers VPN-configured DNS servers first. -- Manual builtin checks: `tunmux hook run connectivity`, `tunmux hook run external-ip`, - or `tunmux hook run dns-detection`. -- Debug helper: `tunmux hook debug ` prints the exact env payload used for hooks - (`--event ifup|ifdown`, default `ifup`). - -## Privileged Service - -Privileged operations are handled by: +## Building ```bash -sudo tunmux privileged --serve --authorized-group +cargo build ``` -Supported transports: -- `socket` (default): Unix socket control channel (`/Library/Application Support/tunmux/run/ctl.sock`) -- `stdio`: one-shot helper process over stdin/stdout - -Autostart can launch the privileged service when needed (if enabled in config), -or `launchd` can socket-activate it via `etc/me.pansen.tunmux.privileged.plist`. +## Development -Example sudoers entries (adjust binary path for your install): +The repository includes git hooks that check formatting before each commit. +Enable them once per clone: ```bash - ALL=(root) NOPASSWD: /usr/local/bin/tunmux privileged --serve --authorized-group tunmux - ALL=(root) NOPASSWD: /usr/local/bin/tunmux privileged --serve --autostarted --authorized-group tunmux - ALL=(root) NOPASSWD: /usr/local/bin/tunmux privileged --serve --autostarted --authorized-group tunmux --idle-timeout-ms * -``` - -For stdio mode: - -```bash - ALL=(root) NOPASSWD: /usr/local/bin/tunmux privileged --serve --stdio --autostarted --authorized-group tunmux -``` - -## Data Layout - -User data under `~/.config/tunmux/`: - -```text -~/.config/tunmux/ - config.toml - connections/ - _direct.json - .json - wgconf/ - profiles/ - .conf -``` - -Privileged runtime state: - -```text -/Library/Application Support/tunmux/ - run/ - ctl.sock - wg/ - /.conf - -/var/log/tunmux/ - .log +make hooks ``` ## License