Files
wild-central/docs/registrations.md
Paul Payne d009e095c0 docs: Add README and service registration reference
- README.md: project overview, two sources of truth (config vs
  registrations), quick start, development, architecture
- docs/registrations.md: complete API reference for service
  registrations — fields, types, defaults, what Central does
  per combination, examples for each use case

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-09 20:11:23 +00:00

146 lines
5.7 KiB
Markdown

# Service Registrations
Wild Central provides DNS, proxy routing, TLS, and DDNS for services on the LAN. External consumers (Wild Cloud, Wild Works) register their domains with Central's API, and Central handles the networking.
## Registration API
```
POST /api/v1/services Register a domain
GET /api/v1/services List all registrations
GET /api/v1/services/{domain} Get registration details
PATCH /api/v1/services/{domain} Update a registration
DELETE /api/v1/services/{domain} Remove a registration
```
The domain is the unique key. One registration per domain.
## Fields
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `domain` | string | yes | — | FQDN to route. Unique key. |
| `source` | string | no | `"manual"` | Who registered this: `wild-cloud`, `wild-works`, `manual`. |
| `backend.address` | string | yes | — | Target host:port (e.g., `192.168.8.240:443`). |
| `backend.type` | string | yes | — | `tcp-passthrough` or `http`. |
| `subdomains` | bool | no | `false` | If true, also routes `*.domain` traffic to this backend. |
| `reach` | string | yes | — | `internal` or `public`. |
| `tls` | string | no | inferred | `passthrough` or `terminate`. Defaults based on type. |
### `backend.type`
**`tcp-passthrough`** — Layer 4. Central passes TLS traffic through to the backend without terminating it. The backend handles its own TLS (e.g., a k8s cluster with traefik and its own wildcard cert). HAProxy routes by SNI.
**`http`** — Layer 7. Central terminates TLS (using a certbot-provisioned certificate) and reverse-proxies HTTP to the backend. HAProxy routes by Host header.
### `subdomains`
Controls whether `*.domain` traffic is also routed to this backend.
- **`false`** (default): Only exact-match `domain` is routed. Use for custom domains like `payne.io` or `civilsociety.dev` where you only want that specific hostname.
- **`true`**: Both `domain` and `*.domain` are routed. Use for instance primary domains like `cloud.payne.io` where apps are served at subdomains (`matrix.cloud.payne.io`, `vaultwarden.cloud.payne.io`).
### `reach`
Controls DNS visibility and external access.
- **`internal`**: Domain resolves only on the LAN (dnsmasq `local=/` + `address=/`). Not added to DDNS. Not routable from the internet.
- **`public`**: Domain resolves on the LAN (dnsmasq `address=/`) AND externally via DDNS A record. Routable from the internet through Central's HAProxy.
### `tls`
- **`passthrough`**: Central does not terminate TLS — the backend handles it. Default for `tcp-passthrough`.
- **`terminate`**: Central provisions a TLS certificate (via certbot + Cloudflare DNS-01) and terminates TLS at HAProxy. Default for `http`.
## What Central does per registration
| | `tcp-passthrough` | `http` |
|---|---|---|
| **Internal DNS** | `address=/<domain>/<backend-IP>` — LAN clients connect directly to the backend | `address=/<domain>/<central-IP>` — LAN clients connect to Central's HAProxy |
| **DNS (internal reach)** | Also `local=/<domain>/` — prevents upstream DNS forwarding | Same |
| **External DNS (public reach)** | DDNS A record at Cloudflare | Same |
| **HAProxy** | L4 SNI → backend. `subdomains: true` adds `*.domain` matching. | L7 Host header → reverse proxy to backend. Always exact match. |
| **TLS** | Passthrough — backend handles TLS | Terminate — Central provisions cert via certbot |
## Examples
### Wild Cloud k8s instance (primary domain with subdomains)
```json
{
"domain": "cloud.payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"},
"subdomains": true,
"reach": "public"
}
```
Result:
- DNS: `cloud.payne.io` → 192.168.8.240, `*.cloud.payne.io` → 192.168.8.240
- HAProxy: SNI `cloud.payne.io` and `*.cloud.payne.io` → passthrough to 192.168.8.240:443
- DDNS: A record for `cloud.payne.io` → public IP
- TLS: handled by k8s traefik (passthrough)
### Wild Cloud k8s instance (internal-only domain)
```json
{
"domain": "internal.cloud.payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"},
"subdomains": true,
"reach": "internal"
}
```
Result:
- DNS: `local=/internal.cloud.payne.io/` + `address=/internal.cloud.payne.io/192.168.8.240`
- No DDNS record (internal only)
- HAProxy: SNI matching for internal domain (in case traffic comes through Central)
### Custom app domain (exact match, no subdomains)
```json
{
"domain": "payne.io",
"source": "wild-cloud",
"backend": {"address": "192.168.8.240:443", "type": "tcp-passthrough"},
"subdomains": false,
"reach": "public"
}
```
Result:
- DNS: `address=/payne.io/192.168.8.240`
- HAProxy: SNI exact match `payne.io` only (NOT `*.payne.io`)
- DDNS: A record for `payne.io` → public IP
### Wild Cloud app (HTTP reverse proxy, internal only)
```json
{
"domain": "wild-cloud.payne.io",
"source": "wild-cloud",
"backend": {"address": "127.0.0.1:5055", "type": "http"},
"reach": "internal"
}
```
Result:
- DNS: `local=/wild-cloud.payne.io/` + `address=/wild-cloud.payne.io/192.168.8.151`
- HAProxy: L7 Host `wild-cloud.payne.io` → reverse proxy to 127.0.0.1:5055
- TLS: Central provisions cert via certbot
- No DDNS (internal only)
## What is NOT a registration
Central's own services are driven by Central config, not registrations:
- **Central's UI domain** (`cloud.central.domain` in config)
- **VPN** (`cloud.vpn.*` in config) — WireGuard endpoint, firewall port
- **Firewall rules** (`cloud.nftables.*` in config)
- **DHCP** (`cloud.dnsmasq.dhcp.*` in config)
- **CrowdSec**, certbot credentials, DDNS provider config
These are managed through Central's own UI and config file. They don't go through the registration API.