# A custom domain for a playground project

A project published on the playground (https://jumphp.net) can have **its own address**, e.g. `monapp.example.org`, served like
`bin/jumphp deploy` does: the project's site (`site/`) at the root (`{{DOMAIN}}` = the domain), the game or application in
`/play/`. The HTTPS certificate comes by itself at the first visit.

## For the project's owner

1. Studio → the project → **Custom domain** → the domain → **Add the domain**.
2. At the domain's DNS host, two records (the studio shows them, ready to copy):
   ```
   monapp.example.org.          CNAME  apps.jumphp.net.
   _jumphp.monapp.example.org.  TXT    "jumphp-verify=<the studio's token>"
   ```
   - the CNAME points to the server, **without proxy** (Cloudflare: grey cloud, "DNS only"): the server must see the TLS connection
     to get the certificate;
   - the TXT proves the domain is yours;
   - a root domain (no subdomain) cannot have a CNAME: give it the same IP addresses (A / AAAA) as `apps.jumphp.net`.
3. **Check the DNS** (propagation can take minutes; the studio says what is missing: the TXT, or the CNAME).
4. Depending on the playground's settings, the administrator **approves** the domain ("Waiting for the administrator"), or it is
   online as soon as it is verified.
5. Open `https://monapp.example.org/`: the first visit makes the certificate (a few seconds).

Disabling the domain cuts it at once; Enable brings it back. A later failed check (CNAME removed) takes it offline. The domain always
serves the **published version**, and nothing of the portal (no studio, no sign-in, no cookie). The game asks its TCP tickets from its
own origin and opens `wss://<the domain>/tcp`: nothing to set.

## For the playground's administrator

Administration → **Partner sites and domains**: every domain, its state, **Check the DNS**, **Approve** / remove the approval,
**Block** (the owner cannot restore it), **Delete**.

How it is served: Traefik passes **only the served domains** through to the playground's Caddy (its HTTP provider reads
`http://jumphp-playground-internal:9180/api/domains/traefik`: a TCP `HostSNI(...)` passthrough router to `jumphp-playground-tls@docker`
and a port-80 router to `jumphp-playground-http@docker`). No low-priority `HostSNI(`*`)` catch-all: Traefik applies TCP routers
**before** the HTTP routers of the same entry point, so a catch-all would take the TLS of every other site of that Traefik. Caddy
(`docker/Caddyfile`) has an `https://` site (`CUSTOM_DOMAINS_SITE`) with `tls { on_demand }` and
`on_demand_tls { ask http://127.0.0.1:9180/api/domains/ask }`; `/api/domains/ask?domain=x` answers 200 only for a served domain
(verified, on, not blocked, approved when approval is required, project published).

Settings (`deploy/.env`): `CUSTOM_DOMAINS_SITE` (`https://` turns it on; default serves nothing), `CUSTOM_DOMAINS_TARGET`
(`apps.jumphp.net`), `CUSTOM_DOMAINS_APPROVAL` (`1`: the administrator approves each domain; `0`: the DNS check is enough),
`TRAEFIK_ENTRYPOINT`, `TRAEFIK_HTTP_ENTRYPOINT`.

Once: `apps.jumphp.net A <server IP>` (DNS only), Traefik's static configuration
`providers.http.endpoint: "http://jumphp-playground-internal:9180/api/domains/traefik"` (`pollInterval: "15s"`), then
`CUSTOM_DOMAINS_SITE=https://` in `deploy/.env` and a redeploy of the playground.
