# Tunnels

A **Tunnel** is a stored network passage the launcher will bring up
before reaching a Provider. Today the only accepted kind is
`wireguard` — supplied either as a wg-quick INI string or as the
structured `{ interface, peers }` equivalent. Providers point at a tunnel
by name.

> **Warning:** Tunnel storage — add / list / show / update / remove, plus attaching one
> to a Provider — is live. **Activation** is not: nothing in the control
> plane invokes `wg-quick up` before SSH'ing to a provider. You can
> register tunnels and attach them today; the launcher wire-up is a
> follow-up.

## CLI

```bash
# Register from a wg-quick config file.
lakeshore tunnels add ny-lab \
  --kind wireguard \
  --config-file ~/wg/ny-lab.conf

# …or from an inline INI string.
lakeshore tunnels add ny-lab \
  --kind wireguard \
  --conf '[Interface]
PrivateKey = …
Address = 10.0.0.2/32

[Peer]
PublicKey = …
Endpoint = vpn.example.com:51820
AllowedIPs = 10.0.0.0/24'

lakeshore tunnels list [--json]
lakeshore tunnels show ny-lab [--json]
lakeshore tunnels remove ny-lab      # refuses while a provider references it
```

`--kind` defaults to `wireguard`, which is also the only accepted value.
The name must match `^[a-z0-9][a-z0-9-]*$`.

## Attaching to a Provider

```bash
lakeshore providers update bos14 --tunnel ny-lab     # attach
lakeshore providers update bos14 --tunnel ""         # detach
```

Tab-completion on `--tunnel ` lists registered tunnels — see
[Completion](/cli/completion.md).

## Storage shape

The server treats `config` as opaque: it does not validate wg-quick
keys. Both shapes round-trip:

```json
// structured — what the CLI's INI parser emits (camelCase keys)
{
  "interface": { "privateKey": "…", "address": ["10.0.0.2/32"] },
  "peers": [
    {
      "publicKey": "…",
      "endpoint": "vpn.example.com:51820",
      "allowedIPs": ["10.0.0.0/24"]
    }
  ]
}

// pass-through INI
{ "conf": "[Interface]\nPrivateKey = …\n…" }
```

`tunnels add --config-file *.conf` parses the INI into the structured
shape; `--config-file *.yaml` or `*.json` is uploaded as-is. The parser
camelCases keys (`AllowedIPs` → `allowedIPs`, `PreUp` → `preUp`, and
all-caps keys like `DNS` → `dns`), splits `Address`, `DNS`, and
`AllowedIPs` on commas into lists, and keeps integer values
(`ListenPort`, `PersistentKeepalive`) as numbers. An unknown section
header is a parse error.

## Secret references

Private keys in a wg-quick config are sensitive. Store the key as a
Secret and reference it with a `$secret` marker:

```json
{
  "interface": {
    "privateKey": { "$secret": "wg-ny-lab-private" },
    "address": ["10.0.0.2/32"]
  },
  "peers": [ … ]
}
```

```bash
cat ~/wg/ny-lab.key | lakeshore secrets add wg-ny-lab-private --kind opaque
lakeshore tunnels add ny-lab --kind wireguard --config-file ./ny-lab.json
```

Every referenced secret must already exist in the same namespace — the
server rejects the write with a 422 otherwise.

> **Warning:** The reference format is the JSON object `{"$secret": "<name>"}`. A bare
> `PrivateKey = $secret:wg-ny-lab-private` line inside a `.conf` file is
> parsed as a literal string — the INI parser does not convert it, and the
> secret walker only recognises the object form. Supply a structured
> YAML/JSON config when you want a `$secret` reference.

## HTTP

```
POST   /v1/namespaces/:ns/tunnels
GET    /v1/namespaces/:ns/tunnels
GET    /v1/namespaces/:ns/tunnels/:name
PATCH  /v1/namespaces/:ns/tunnels/:name
DELETE /v1/namespaces/:ns/tunnels/:name
```

## Read next

- [Providers](https://docs.dreamlake.ai/lakeshore/providers) — attaching a tunnel to a provider.
- [Secrets](/api/auth-and-secrets.md#secrets) — storing WireGuard private
  keys.
- [Mounts](/get-started/mounts.md) — the parallel registry for shared
  filesystems, with the same metadata-only status.
- [Completion](/cli/completion.md) — `--tunnel ` dynamic completion.
