# Running a Self-hosted relay

> Run moq-relay on your own infrastructure and enroll it in your Organization.

URL: https://www.caprid.com/docs/guides/self-hosted-relays/
Source: https://github.com/muxinc/switchboard/blob/main/docs/public/self-hosted-relays.md

A Self-hosted relay is a relay you run yourself and enroll in your
Organization. It talks to Switchboard over the public internet at
`https://switchboard.mux.global`, so you don't need a private network, a VPN,
or a custom build.

The relay is `moq-relay`, from the open-source
[moq-dev/moq](https://github.com/moq-dev/moq) project, version 0.15 or later.
The [moq-relay README](https://github.com/moq-dev/moq/tree/main/rs/moq-relay)
covers installing it from packages or Docker, and has example configurations.
The fastest way to start is the `moqdev/moq-relay` Docker image, shown in
[step 3](#3-configure-the-relay).

Once it's enrolled, your relay calls Switchboard for three things:

| What | When | Endpoint |
|---|---|---|
| Enrollment | Once, when you set it up | `POST /cluster/enroll` |
| Peer discovery | About once a second | `GET /cluster/connect` |
| Admission | For every session, and every 30 seconds while it lasts | `POST /auth` |

Switchboard answers Admission from the location nearest your relay, and keeps
answering while the rest of Switchboard is down or being deployed. If a peer
discovery poll fails, your relay keeps the peers it already has.

A Self-hosted relay carries only your Organization's Namespaces. Whatever
token a Client brings, the relay won't let it publish or subscribe outside
`acme/`, or whatever your Namespaces are.

## 1. Get a Relay enrollment token

You need an API key for your Organization, and the Organization's id from
`GET /v1/organizations`. Get the key from the dashboard at
`https://app.caprid.com/app`, or see [Credentials](https://www.caprid.com/docs/guides/credentials/).
The examples below use Organization `4`, which owns the Namespace `acme`.

```bash
curl -X POST https://app.caprid.com/v1/organizations/4/enrollments \
  -H "Authorization: Bearer $SB_API_KEY" \
  -d '{"url": "https://relay1.example.com:4443/", "region": "iad", "gateway": false}'
```

The response includes the token, which is shown only this once:

```json
{"id": "7c1e...", "token": "sbe_...", "expires_at": 1790301600, ...}
```

The token fixes everything about the relay's Node, and the relay can't change
any of it later:

- `url` is where other relays connect to it, and where Clients connect too,
  unless you set `client_url`.
- `region` is which of your Regions the relay joins. Relays in the same
  Region all connect to each other. If you leave it out, the relay joins
  `default`. Region names are yours alone, so your `iad` has nothing to do
  with anyone else's.
- `gateway` is whether the relay may be picked to carry its Region's traffic
  to your other Regions.
- `client_url` is optional. Set it when Clients reach the relay at a
  different address from the one its peers dial, for example when `url` is a
  private name. Whatever hands relay addresses to your Clients can read it
  from the Node.

The token expires after an hour if nobody uses it. To change that, set
`expires_in`, in seconds, up to a day. To revoke it early, call
`DELETE /v1/organizations/4/enrollments/{id}`.

## 2. Exchange it for a Node key

On the relay's host, run this once:

```bash
curl -X POST https://switchboard.mux.global/cluster/enroll \
  -d '{"token": "sbe_..."}' > enrollment.json

jq -r .node_key enrollment.json > /etc/moq/node.key   # sbn_...
jq '{cluster, auth}' enrollment.json
```

The second `jq` prints the settings for `relay.toml`:

```json
{
  "cluster": {
    "node": "https://relay1.example.com:4443/",
    "connect_api": "https://switchboard.mux.global/cluster/connect?jwt=sbn_..."
  },
  "auth": {"url": "https://switchboard.mux.global/auth?jwt=sbn_..."}
}
```

The token works only once, and this response is the only time you see the
Node key, so keep it. If you lose the Node key, enroll the relay again as a
new Node, and delete the old one with
`DELETE /v1/organizations/4/nodes?url=...`.

## 3. Configure the relay

Write `/etc/moq/relay.toml`:

```toml
[listen]
# Where Clients and peers connect. The certificate has to be valid for the
# host in [cluster] node.
bind = "[::]:4443"
tls.cert = ["/etc/moq/cert.pem"]
tls.key = ["/etc/moq/key.pem"]

[cluster]
# The values enrollment returned, unchanged.
node = "https://relay1.example.com:4443/"
connect_api = "https://switchboard.mux.global/cluster/connect?jwt=sbn_..."
# The Node key, as a file. The relay presents it to the peers it connects to.
token = "/etc/moq/node.key"

[auth]
# Switchboard decides every session here.
url = "https://switchboard.mux.global/auth?jwt=sbn_..."
```

`relay.toml` contains the Node key twice, in those URLs. Protect it the same
way you protect `node.key`.

Don't set `[connect] tls.insecure`. That setting turns off certificate checks
for the relay's calls to Switchboard as well as to its peers, and
`switchboard.mux.global` has a publicly trusted certificate. Use it only if
your relays present self-signed certificates to each other.

To run the relay with Docker, put `relay.toml`, `node.key` and your
certificate in `/etc/moq`, then run:

```bash
docker run -d --name moq-relay --restart unless-stopped \
  -p 4443:4443/udp \
  -v /etc/moq:/etc/moq:ro \
  moqdev/moq-relay:0.17.9 /etc/moq/relay.toml
```

The image takes the path to its config file as its only argument. QUIC runs
over UDP, so open UDP port 4443, or whichever port you bound, in your
firewall.

## 4. Check that it works

- **Polling.** A few seconds after the relay starts,
  `GET /v1/organizations/4/nodes` lists the Node with `"live": true`.
- **Peers.** With two or more relays in a Region, each relay's log shows it
  connecting to the others.
- **Admission.** A Client token from `POST /v1/namespaces/acme/tokens` works
  through the relay. A token for another Organization's Namespace, or no
  token, is refused.

## Remove a relay

`DELETE /v1/organizations/4/nodes?url=https://relay1.example.com:4443/`
deletes the Node and its Node key. Switchboard refuses the relay on its next
poll, and its peers drop it. To bring the relay back, enroll it again.

## Current limits

- Your Regions can't link to Mux's relays yet. Until they can, a Namespace's
  `platform_peering` setting has no effect.
- To move a relay to another Region, or to change whether it can be a
  gateway, enroll it again.
