> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Tailscale

# Tailscale (Gateway dashboard)

Equabot can auto-configure Tailscale **Serve** (tailnet) or **Funnel** (public) for the
Gateway dashboard and WebSocket port. This keeps the Gateway bound to loopback while
Tailscale provides HTTPS, routing, and (for Serve) identity headers.

## Modes

* `serve`: Tailnet-only Serve via `tailscale serve`. The gateway stays on `127.0.0.1`.
* `funnel`: Public HTTPS via `tailscale funnel`. Equabot requires a shared password.
* `off`: Default (no Tailscale automation).

## Auth

Set `gateway.auth.mode` to control the handshake:

* `token` (default when `EQUABOT_GATEWAY_TOKEN` is set)
* `password` (shared secret via `EQUABOT_GATEWAY_PASSWORD` or config)

When `tailscale.mode = "serve"` and `gateway.auth.allowTailscale` is `true`,
valid Serve proxy requests can authenticate via Tailscale identity headers
(`tailscale-user-login`) without supplying a token/password. Equabot only
treats a request as Serve when it arrives from loopback with Tailscale’s
`x-forwarded-for`, `x-forwarded-proto`, and `x-forwarded-host` headers.
To require explicit credentials, set `gateway.auth.allowTailscale: false` or
force `gateway.auth.mode: "password"`.

## Device pairing (Serve)

Remote devices connecting via Tailscale Serve still require **device pairing**,
the same as any non-local device. Tailscale identity headers handle
authentication, but the device itself must be paired before it can establish a
session.

On the first connection from a new browser or device:

1. The gateway creates a **pairing request** and closes the connection.
2. Approve the request from the **local Control UI** (or any already-paired session).
3. The remote device reconnects and succeeds.

Loopback connections (`127.0.0.1` / `::1`) auto-approve pairing silently.
Serve connections do not, because the real client IP is the remote tailnet peer.

## Config examples

### Tailnet-only (Serve)

```json5 theme={null}
{
  gateway: {
    bind: "loopback",
    tailscale: { mode: "serve" },
    trustedProxies: ["127.0.0.1"]
  }
}
```

Tailscale Serve acts as a local reverse proxy on `127.0.0.1`, injecting
`X-Forwarded-For` headers. Adding `trustedProxies: ["127.0.0.1"]` tells the
gateway to trust those headers for client IP detection. Without it, connections
log "Proxy headers detected from untrusted address" warnings and proxy headers
are ignored.

**Serve setup checklist:**

1. Set `gateway.tailscale.mode: "serve"` and `gateway.trustedProxies: ["127.0.0.1"]`.
2. Restart the gateway.
3. Open the Control UI from the remote device via `https://<magicdns>/`.
4. Approve the device pairing prompt from the local Control UI.
5. Verify: look for `[tailscale] serve enabled` in the gateway log and confirm
   no `token_mismatch` or `pairing required` errors.

Open: `https://<magicdns>/` (or your configured `gateway.controlUi.basePath`)

### Tailnet-only (bind to Tailnet IP)

Use this when you want the Gateway to listen directly on the Tailnet IP (no Serve/Funnel).

```json5 theme={null}
{
  gateway: {
    bind: "tailnet",
    auth: { mode: "token", token: "your-token" }
  }
}
```

Connect from another Tailnet device:

* Control UI: `http://<tailscale-ip>:18789/`
* WebSocket: `ws://<tailscale-ip>:18789`

Note: loopback (`http://127.0.0.1:18789`) will **not** work in this mode.

### Public internet (Funnel + shared password)

```json5 theme={null}
{
  gateway: {
    bind: "loopback",
    tailscale: { mode: "funnel" },
    auth: { mode: "password", password: "replace-me" }
  }
}
```

Prefer `EQUABOT_GATEWAY_PASSWORD` over committing a password to disk.

## CLI examples

```bash theme={null}
equabot gateway --tailscale serve
equabot gateway --tailscale funnel --auth password
```

## Notes

* Tailscale Serve/Funnel requires the `tailscale` CLI to be installed and logged in.
* `tailscale.mode: "funnel"` refuses to start unless auth mode is `password` to avoid public exposure.
* Set `gateway.tailscale.resetOnExit` if you want Equabot to undo `tailscale serve`
  or `tailscale funnel` configuration on shutdown.
* `gateway.bind: "tailnet"` is a direct Tailnet bind (no HTTPS, no Serve/Funnel).
* `gateway.bind: "auto"` prefers loopback; use `tailnet` if you want Tailnet-only.
* Serve/Funnel only expose the **Gateway control UI + WS**. Nodes connect over
  the same Gateway WS endpoint, so Serve can work for node access.

## Browser control server (remote Gateway + local browser)

If you run the Gateway on one machine but want to drive a browser on another machine, use a **separate browser control server**
and publish it through Tailscale **Serve** (tailnet-only):

```bash theme={null}
# on the machine that runs Chrome
equabot browser serve --bind 127.0.0.1 --port 18791 --token <token>
tailscale serve https / http://127.0.0.1:18791
```

Then point the Gateway config at the HTTPS URL:

```json5 theme={null}
{
  browser: {
    enabled: true,
    controlUrl: "https://<magicdns>/"
  }
}
```

And authenticate from the Gateway with the same token (prefer env):

```bash theme={null}
export EQUABOT_BROWSER_CONTROL_TOKEN="<token>"
```

Avoid Funnel for browser control endpoints unless you explicitly want public exposure.

## Tailscale prerequisites + limits

* Serve requires HTTPS enabled for your tailnet; the CLI prompts if it is missing.
* Serve injects Tailscale identity headers; Funnel does not.
* Funnel requires Tailscale v1.38.3+, MagicDNS, HTTPS enabled, and a funnel node attribute.
* Funnel only supports ports `443`, `8443`, and `10000` over TLS.
* Funnel on macOS requires the open-source Tailscale app variant.

## Learn more

* Tailscale Serve overview: [https://tailscale.com/kb/1312/serve](https://tailscale.com/kb/1312/serve)
* `tailscale serve` command: [https://tailscale.com/kb/1242/tailscale-serve](https://tailscale.com/kb/1242/tailscale-serve)
* Tailscale Funnel overview: [https://tailscale.com/kb/1223/tailscale-funnel](https://tailscale.com/kb/1223/tailscale-funnel)
* `tailscale funnel` command: [https://tailscale.com/kb/1311/tailscale-funnel](https://tailscale.com/kb/1311/tailscale-funnel)
