DreamLake

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.

Status: metadata only

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 <TAB> lists registered tunnels — see Completion.

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.

`$secret:name` inline strings do not resolve

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 — attaching a tunnel to a provider.
  • Secrets — storing WireGuard private keys.
  • Mounts — the parallel registry for shared filesystems, with the same metadata-only status.
  • Completion — --tunnel <TAB> dynamic completion.