Zaroz
Operar servicios

Dominios personalizados, enrutado por rutas, subdominios de juego y Domain Connect

Un servicio siempre es accesible en una dirección gratuita de la plataforma. Esta guía explica cómo hacer que responda en un nombre propio. Cuáles de estas opciones tiene tu servicio depende de su producto, así que consulta antes sus capacidades: custom_domain y external_access aparecen cuando las admite. Los ejemplos usan el ayudante de Usar la API de Zaroz.

QuieresUsa
www.example.com delante de una web o APIDominio personalizado
Que example.com/api llegue a un servicio distinto de example.comEnrutado por rutas
play.tujuego.zaroz.cloud para un servidor de juegosSubdominio de juego
Que tu proveedor de DNS cree los registros por tiDomain Connect

Los dominios que necesitan certificado usan TLS automático, así que el nombre debe apuntar ya a Zaroz en el DNS antes de que se pueda emitir. Un dominio pending es normal mientras esperas al DNS; no es un error.

Dominios personalizados

Demuestra que el dominio es tuyo

Salvo que el nombre sea una sola etiqueta bajo un dominio padre que Zaroz mismo registró para ti, la organización debe demostrar antes que controla el dominio con un registro DNS TXT: consulta Verificar un dominio. Los nombres dentro de la zona DNS de Zaroz se rechazan para siempre, y un nombre que ya tiene otra organización se rechaza con la misma respuesta que uno que no has demostrado, de modo que esta API no sirve para averiguar quién tiene qué.

Comprobar, asociar, esperar, desasociar

Pregunta primero si un nombre se podría asociar; no cambia nada.

curl

TerminalCode
curl -sS "$ZAROZ_API/service-instances/$SERVICE/custom-domains/availability?hostname=www.example.com" -H "$AUTH" # {"status": "available"} o taken | invalid | platform_zone | not_verified curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/custom-domains" -H "$AUTH" \ -H "Content-Type: application/json" -d '{"hostname": "www.example.com"}' # {"id": "...", "hostname": "www.example.com", "status": "pending", "url": "https://www.example.com"} curl -sS "$ZAROZ_API/service-instances/$SERVICE/custom-domains" -H "$AUTH" curl -sS -X DELETE "$ZAROZ_API/service-instances/$SERVICE/custom-domains/$ROUTE" -H "$AUTH"

TypeScript

TypeScriptCode
import { call } from "./zaroz"; const avail = await call("GET", `/service-instances/${SERVICE}/custom-domains/availability?hostname=www.example.com`); if (avail.body.status !== "available") throw new Error(avail.body.status); await call("POST", `/service-instances/${SERVICE}/custom-domains`, { hostname: "www.example.com" }); // pending hasta que el DNS apunte aquí y se emita el certificado for (let i = 0; i < 60; i++) { const { body } = await call("GET", `/service-instances/${SERVICE}/custom-domains`); const mine = body.domains.find((d: any) => d.hostname === "www.example.com"); console.log(mine.status); if (mine.status !== "pending") break; await new Promise((r) => setTimeout(r, 30_000)); }

Python

Code
import time from zaroz import call _, avail = call("GET", f"/service-instances/{SERVICE}/custom-domains/availability?hostname=www.example.com") assert avail["status"] == "available", avail["status"] call("POST", f"/service-instances/{SERVICE}/custom-domains", {"hostname": "www.example.com"}) for _ in range(60): # pending hasta que el DNS apunte aquí y se emita el certificado _, body = call("GET", f"/service-instances/{SERVICE}/custom-domains") mine = next(d for d in body["domains"] if d["hostname"] == "www.example.com") print(mine["status"]) if mine["status"] != "pending": break time.sleep(30)
  • Crea un CNAME (www.example.com hacia el host de la plataforma que el listado devuelve como platform_host) en tu proveedor de DNS. Un dominio desnudo (example.com) no puede tener CNAME: usa el registro ALIAS, ANAME o de aplanado de CNAME de tu proveedor hacia el mismo destino.
  • status pasa de pending a active (ruta y certificado en marcha) o failed (normalmente el certificado: no se puede emitir hasta que el DNS apunte aquí; corrige el registro y se reintenta).
  • Asociar es idempotente: un nombre ya asociado a este servicio se vuelve a aplicar y se devuelve. Un servicio admite un número limitado de dominios (limit en el listado), y uno que no publica HTTP no puede tener ninguno.
  • Desasociar elimina la ruta, el certificado y tu reclamación del nombre, así que puedes volver a asociarlo más tarde. El host gratuito de la plataforma no se puede desasociar.

Dominios en un endpoint HTTPS

Un Workload publica webs y APIs como endpoints HTTPS (consulta Puertos públicos y endpoints y Añadir un puerto público). Sus dominios se gestionan en el endpoint:

curl

TerminalCode
E=/projects/$PROJECT/service-instances/$SERVICE/endpoints curl -sS -X POST "$ZAROZ_API$E/$ENDPOINT/domains" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"domain": "www.example.com"}' # {"id": "<id de ruta>", "domain": "www.example.com", "status": "pending", # "dns": {"record_type": "CNAME", "name": "www.example.com", "target": "<host gratuito>"}} curl -sS -X DELETE "$ZAROZ_API$E/$ENDPOINT/domains/$ROUTE" -H "$AUTH"

TypeScript

TypeScriptCode
const E = `/projects/${PROJECT}/service-instances/${SERVICE}/endpoints/${ENDPOINT}`; const d = await call("POST", `${E}/domains`, { domain: "www.example.com" }); console.log(d.body.dns); // crea exactamente este registro en tu proveedor de DNS

Python

Code
E = f"/projects/{PROJECT}/service-instances/{SERVICE}/endpoints/{ENDPOINT}" _, d = call("POST", f"{E}/domains", {"domain": "www.example.com"}) print(d["dns"]) # crea exactamente este registro en tu proveedor de DNS

La respuesta te dice el registro DNS exacto que crear (dns). Un rechazo nombra su motivo antes de los dos puntos en details: invalid_domain (400), o platform_domain, domain_taken, domain_not_verified, domain_limit, not_ready (422). Un código failure explica un dominio failed.

Enrutado por rutas

Cuando un dominio está en un endpoint HTTPS puedes enviar algunas rutas a otro sitio, de modo que app.com llegue a un servicio y app.com/api a otro. El destino debe ser un endpoint HTTPS abierto de un servicio del mismo proyecto. Gana la ruta coincidente más larga; el endpoint al que está asociado el dominio responde todo lo demás. La llamada reemplaza todo el conjunto, y una lista vacía elimina todas las rutas.

curl

TerminalCode
curl -sS -X PUT "$ZAROZ_API$E/$ENDPOINT/domains/$ROUTE/paths" -H "$AUTH" -H "Content-Type: application/json" -d '{ "paths": [ {"path": "/api", "strip_prefix": true, "target_service_instance_id": "<uuid del servicio de la API>", "target_endpoint_id": "<uuid de su endpoint https>"} ] }' # ¿cómo enruta ahora este dominio y adónde iría /api/users? curl -sS "$ZAROZ_API$E/$ENDPOINT/domains/$ROUTE/routing?path=/api/users" -H "$AUTH"

TypeScript

TypeScriptCode
const routing = await call("PUT", `${E}/domains/${ROUTE}/paths`, { paths: [ { path: "/api", strip_prefix: true, target_service_instance_id: API_SERVICE, target_endpoint_id: API_ENDPOINT }, ], }); for (const r of routing.body.rules) console.log(r.order, r.rule, r.status); // serving | starting | shadowed | broken const sample = await call("GET", `${E}/domains/${ROUTE}/routing?path=/api/users`); console.log(sample.body.sample); // { path, winning_order, upstream_path: "/users" }

Python

Code
_, routing = call("PUT", f"{E}/domains/{ROUTE}/paths", {"paths": [ {"path": "/api", "strip_prefix": True, "target_service_instance_id": API_SERVICE, "target_endpoint_id": API_ENDPOINT}, ]}) for r in routing["rules"]: print(r["order"], r["rule"], r["status"]) # serving | starting | shadowed | broken _, sample = call("GET", f"{E}/domains/{ROUTE}/routing?path=/api/users") print(sample["sample"]) # {"path", "winning_order", "upstream_path": "/users"}

Con strip_prefix: true el destino recibe /users; con false recibe /api/users. Cada regla tiene un status: serving, starting (su destino aún no está activo), shadowed (otra regla con la misma ruta gana siempre, así que esta nunca sirve) o broken (su destino no puede responder). Rechazos: invalid_path y target_not_found (400; un destino inexistente, cerrado o de otro proyecto responden igual), y path_limit, duplicate_path, target_not_http (422).

Subdominios de juego

Un servidor de juegos puede tomar un nombre gratuito bajo una zona de la plataforma (<nombre>.<zona>), que apunta a su dirección pública, con un registro SRV para los juegos que leen el puerto de ahí. Un servicio tiene uno.

curl

TerminalCode
curl -sS "$ZAROZ_API/service-instances/$SERVICE/game-subdomains" -H "$AUTH" # {"limit": 1, "subdomains": [], "zones": [{"id": "<uuid de zona>", "name": "<zona>"}]} curl -sS "$ZAROZ_API/service-instances/$SERVICE/game-subdomains/availability?parent_domain_id=$ZONE&subdomain=myserver" -H "$AUTH" curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/game-subdomains" -H "$AUTH" -H "Content-Type: application/json" \ -d '{"parent_domain_id": "<uuid de zona>", "subdomain": "myserver"}' # 201 {"hostname": "myserver.<zona>", "status": "active", "has_srv_record": true, ...}

TypeScript

TypeScriptCode
const list = await call("GET", `/service-instances/${SERVICE}/game-subdomains`); const zone = list.body.zones[0]; const free = await call("GET", `/service-instances/${SERVICE}/game-subdomains/availability?parent_domain_id=${zone.id}&subdomain=myserver`); if (free.body.status === "available") { const sub = await call("POST", `/service-instances/${SERVICE}/game-subdomains`, { parent_domain_id: zone.id, subdomain: "myserver", }); console.log(sub.body.hostname, sub.body.status); }

Python

Code
_, listing = call("GET", f"/service-instances/{SERVICE}/game-subdomains") zone = listing["zones"][0] _, free = call("GET", f"/service-instances/{SERVICE}/game-subdomains/availability" f"?parent_domain_id={zone['id']}&subdomain=myserver") if free["status"] == "available": _, sub = call("POST", f"/service-instances/{SERVICE}/game-subdomains", {"parent_domain_id": zone["id"], "subdomain": "myserver"}) print(sub["hostname"], sub["status"])

status es pending (creando los registros), active, failed (se reintenta solo) o deleting. PATCH .../game-subdomains/{id} con un nombre nuevo lo reemplaza (la respuesta trae un id nuevo) y DELETE lo libera. Un nombre sigue ocupado un tiempo después de que su titular anterior lo liberó. Rechazos: zone_unavailable, invalid_subdomain, subdomain_taken, already_has_subdomain (422).

Para un servidor de FiveM detrás de tu propio dominio, consulta dominio propio en la lista de servidores.

Domain Connect

Si tu proveedor de DNS admite Domain Connect, Zaroz puede enviarte allí para que apruebes los registros exactos en lugar de copiarlos tú. Nunca escribe tu DNS por sí mismo. Solo lo ofrecen los productos que declaran una plantilla (servidores de juegos con un registro SRV), y un despliegue puede no ejecutarlo en absoluto: comprueba domain_connect en qué ofrece el despliegue.

curl

TerminalCode
curl -sS "$ZAROZ_API/service-instances/$SERVICE/domain-connect" -H "$AUTH" # {"supported": true, "links": []} curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/domain-connect/discover" -H "$AUTH" \ -H "Content-Type: application/json" -d '{"domain": "example.com", "host": "play"}' # {"id": "...", "status": "pending", "apply_url": "https://dns-provider.example/...", "hostname": "play.example.com"} # envía el navegador del cliente a apply_url y después: curl -sS -X POST "$ZAROZ_API/service-instances/$SERVICE/domain-connect/$LINK/verify" -H "$AUTH" curl -sS -X DELETE "$ZAROZ_API/service-instances/$SERVICE/domain-connect/$LINK" -H "$AUTH"

TypeScript

TypeScriptCode
const state = await call("GET", `/service-instances/${SERVICE}/domain-connect`); if (state.body.supported) { const link = await call("POST", `/service-instances/${SERVICE}/domain-connect/discover`, { domain: "example.com", host: "play", // "" apunta el dominio raíz al servidor }); console.log("Abre", link.body.apply_url); // cuando el cliente apruebe en su proveedor de DNS: const v = await call("POST", `/service-instances/${SERVICE}/domain-connect/${link.body.id}/verify`); console.log(v.body.status); // active | pending | failed }

Python

Code
_, state = call("GET", f"/service-instances/{SERVICE}/domain-connect") if state["supported"]: _, link = call("POST", f"/service-instances/{SERVICE}/domain-connect/discover", {"domain": "example.com", "host": "play"}) # "" apunta el dominio raíz al servidor print("Abre", link["apply_url"]) # cuando el cliente apruebe en su proveedor de DNS: _, v = call("POST", f"/service-instances/{SERVICE}/domain-connect/{link['id']}/verify") print(v["status"]) # active | pending | failed

verify comprueba el DNS en vivo: active cuando el registro SRV está publicado y apunta al host y puerto esperados, failed cuando se encuentra un registro distinto, pending mientras no se haya propagado nada. Liberar un enlace no toca tu proveedor de DNS; quita allí los registros tú mismo. Un servicio admite como máximo 20 enlaces, y un proveedor sin flujo síncrono no se puede usar (crea los registros a mano).

Puertos en bruto con un dominio HTTP

Un servicio con acceso externo también puede publicar un dominio delante de un puerto TCP, con certificado gestionado, mediante POST /service-instances/{id}/external-access/{allocation_id}/http (cuerpo {"domain": "..."}). Sigue las mismas reglas que un dominio personalizado (DELETE en la misma ruta lo elimina). Abrir, cerrar y la cuota de los propios puertos están en Puertos públicos y endpoints.

Last modified on