# TailMux - Getting Started

Date: 2026-05-05

## Purpose

This is the handoff document for a new TailMux contributor. Start here before reading the deeper design docs.

TailMux solves one specific problem: keep more than one Tailscale tailnet reachable from one Mac and route each request by hostname suffix without allowing cross-profile fallback.

## Mental Model

TailMux does not create a second native macOS VPN route. It keeps routing at the application/proxy layer:

```text
client keeps hostname -> TailMux suffix router -> selected profile -> selected tailnet
```

The hostname is the routing key. If the request has already become a raw IP address, TailMux denies it by default because the profile ownership is no longer obvious.

## Runtime Choice

Use `backend: "tsnet"` for new work.

`tsnet` embeds a Tailscale node inside TailMux. TailMux starts one embedded server per profile, with a separate state directory, hostname, node key, and optional auth key environment variable.

`backend: "tailscaled"` still exists for compatibility. It runs one external userspace `tailscaled` process per profile and forwards through profile-specific HTTP/SOCKS listeners. Keep it documented because it is useful for comparison and low-level debugging, but do not treat it as the primary path.

## Local Setup

1. Use the repository root:

```bash
cd "/Users/claudioquaglia/CQ Fabrication/tailmux"
```

2. Verify Go:

```bash
go version
```

The module declares `go 1.26.4`.

3. Run tests:

```bash
make test
```

4. Build:

```bash
make build
```

This CLI build does not replace or launch the macOS menu-bar app. To test a local
menu-bar build while Production is installed, use the canonical guarded workflow
in [11-macos-local-dev-session.md](11-macos-local-dev-session.md). Do not launch
the generated app or CLI ad hoc.

5. Generate config:

```bash
bin/tailmux init
```

If a config already exists, inspect it instead of overwriting it:

```bash
tailmux config validate
```

## Recommended Config

Use `examples/tailmux.example.yaml` as the source for new local configs.

Important fields:

- `router.http_proxy`: single TailMux HTTP/CONNECT proxy endpoint.
- `router.pac_listen`: local endpoint serving `/proxy.pac`.
- `profiles.<name>.backend`: use `tsnet` for the embedded path.
- `profiles.<name>.hostname`: readable device name shown in the Tailscale admin approval page.
- `profiles.<name>.suffixes`: profile-owned DNS suffixes.
- `profiles.<name>.state_dir`: isolated Tailscale identity storage.
- `profiles.<name>.port`: stable profile UDP port for peer-to-peer path discovery.

Each suffix must be owned by exactly one profile. Overlapping suffixes are rejected.

## Start And Stop

Start in the foreground:

```bash
tailmux up
```

Expected listeners:

- router: `127.0.0.1:43100`
- PAC: `127.0.0.1:43180`

Stop with `Ctrl-C` if the command is foregrounded. If it was launched inside the local `tmux` helper session used during development:

```bash
tmux kill-session -t tailmuxd
```

Verify TailMux is stopped:

```bash
lsof -nP -iTCP:43100 -iTCP:43180
pgrep -fl tailmux
```

## Browser Setup

Safari uses the macOS network proxy settings. Configure the active network service to use this automatic proxy configuration URL:

```text
http://127.0.0.1:43180/proxy.pac
```

The PAC sends only configured profile-owned suffixes to TailMux. Public web traffic remains `DIRECT`.

The Codex in-app browser may already resolve direct traffic through the active environment and should not be used as proof that Safari is configured. Safari must be verified separately through the PAC path.

## Common Commands

Profile state:

```bash
tailmux profile list
tailmux profile login personal
tailmux profile logout personal
tailmux profile status personal
tailmux status
```

Routing checks:

```bash
tailmux test claudios-mac-mini-1.tail2c15f0.ts.net
tailmux connect claudios-mac-mini-1.tail2c15f0.ts.net 13200
```

PAC:

```bash
tailmux pac print
tailmux pac serve
```

Shell proxy exports:

```bash
tailmux env
```

GUI/TUI:

```bash
tailmux gui serve
tailmux tui --target http://claudios-mac-mini-1.tail2c15f0.ts.net:13200/live-auctions --netcheck --watch
```

## Performance Diagnostics

Use `diag path` first. It answers: which profile owns this hostname, is the peer visible in status JSON, is the path direct or DERP, and how long the HTTP probe took through the TailMux router.

```bash
tailmux diag path http://claudios-mac-mini-1.tail2c15f0.ts.net:13200/live-auctions --netcheck
```

Use `diag fetch` for repeated timing samples:

```bash
tailmux diag fetch \
  --modes router,embedded,direct \
  --samples 5 \
  --timeout 25s \
  --read-limit 4194304 \
  http://claudios-mac-mini-1.tail2c15f0.ts.net:13200/live-auctions
```

For SSE or other long-lived streams, measure the first body byte:

```bash
tailmux diag fetch \
  --modes router,direct \
  --samples 5 \
  --first-byte-only \
  http://claudios-mac-mini-1.tail2c15f0.ts.net:13200/events/stream
```

Current local finding: after the proxy streaming fix, TailMux flushes unknown-length and chunked dynamic responses immediately. In the last measured run, the TailMux router was not the bottleneck; router fetches were faster than the official direct client sample for the same page. Re-measure before making new performance claims.

## Known Design Constraints

- No cross-profile fallback.
- No raw IP routing unless a future explicit static map is designed.
- No transparent system-wide routing yet.
- The CLI does not mutate macOS network settings automatically. The menu-bar app
  can manage only TailMux-owned PAC settings; see
  [06-macos-login-procedure.md](06-macos-login-procedure.md).
- No bridge between tailnets.
- No reliance on the official GUI client identity for TailMux profile routing.

## Quality Bar

Before handing work back:

```bash
make test
make vet
git diff --check
```

Run `make lint` when the local `golangci-lint` supports the current Go version. Otherwise use:

```bash
go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest run ./...
```

For proxy or routing changes, add or update tests in:

- `internal/rules`
- `internal/proxy`
- `internal/embedded`
- `internal/pathdiag`

For GUI/TUI service changes, add or update tests in:

- `cmd/tailmux/gui_test.go`
- `cmd/tailmux/tui_test.go`
