openvibers/openvibe.domains README.md

README.md

main 8.9 KB · 155 lines · markdown

1# OpenVibe.Domains
2
3> Name it, price it, run it.
4
5**Live site:** https://openvibe.domains · **API docs:** https://openvibe.services · **For AI:** https://openvibe.domains/llms.txt
6**Run it:** `npm ci && npm run dev` (Node 22 or newer) · **Tests:** `npm test`
7
8**Status:** v1 is the service skeleton (see [STATUS.json](STATUS.json)); the pages are plain placeholders.
9**Domain:** `openvibe.domains` · **Port:** 5070 · **Service id:** `domains` · **Env prefix:** `DOMAINS`
10**License:** AGPL-3.0-only (same as every OpenVibe service).
11
12OpenVibe.Domains is a stateless read-only front over two OpenVibe services. For a name it shows the registry's
13availability and every registrar's prices for that ending, with one-, three- and five-year totals; every ending has
14its own page; every domain has a page with its public registration history.
15
16There is no database, no sign-in, no session and no event stream. Nothing is stored and nothing is written.
17
18## Pages (v1)
19
20| Route | What it shows |
21|---|---|
22| `/` | the first page of OpenVibe.Host's price catalog (popular endings), plus OpenVibe.Tools' public "most looked up" and "expiring soon" lists when it answers |
23| `/search?q=` | registry status for the name, and every registrar's first-year, three-year and five-year totals, sorted by the three-year total; `noindex` because every query is a page |
24| `/tld/:tld` | one ending (`.dev`, `co.uk`) with every registrar's prices, the five-year total and the renewal-trap mark |
25| `/.<ending>` | 301 to `/tld/<ending>` (for example `/.dev` → `/tld/dev`) |
26| `/d/:domain` | a full domain: its public history from OpenVibe.Tools and its ending's prices from OpenVibe.Host |
27| anything else | the 404 page |
28
29All of it is server-rendered and readable without JavaScript. The shell is the OpenVibe Frame (`openvibe-shared/shell`):
30theme loader, navbar mounted into `#navbar-mount`, the shared server-rendered footer, service id `domains`.
31
32## The control panel (v2)
33
34`/panel` is a signed-in person's own (sign-in with OpenVibe.Network, client `domains`).
35
36- **Connect:** paste a Cloudflare API token (Edit zone DNS + Zone Read) or a Porkbun API key pair (create keys at
37 porkbun.com/account/api, then switch on API access for each domain). Domains verifies it, then stores it as a
38 secret in your own Network project (`OpenVibe.Domains`, ADR-056), granted to `domains` for calls to that provider
39 only (`https://api.cloudflare.com` or `https://api.porkbun.com`). It is revealed per call (`network.secret.use`)
40 and never kept here. Porkbun's pair is one JSON secret, parsed only inside its client.
41- **Zones and records:** zones are synced into `domains_zones` (Porkbun's domains are its zones); records are read
42 live.
43- **Proposals:** every change is a `domains.proposal@1`: create, update or delete records, why, what could break, and
44 7 days to decide. A registrar change is a nameserver list update (`NS`) or a DNSSEC DS create/delete (`DS`), applied
45 through the registrar API. Approve on `/panel/p/:id` or in the Action Center. Changes apply in order; a record or
46 nameserver list that moved since the proposal stops the run (stale). An applied (or part-applied) proposal rolls
47 back in one step.
48- **API:** `GET /api/v1/panel/connections`, `/api/v1/panel/zones`, `/api/v1/panel/zones/:conn/:zone/records`,
49 `GET|POST /api/v1/proposals`, `POST /api/v1/proposals/:id/decide|rollback` with your Network token; Network's Action
50 Center reads `GET /api/v1/actions?subject=` (`domains.actions.read`) and decides (`domains.proposal.decide`).
51
52## API
53
54Everything under `/api/v1`, read-only, CORS-open (`Access-Control-Allow-Origin: *`), cached publicly for five
55minutes. A bad name is RFC 9457 `application/problem+json`; an ending the catalog does not know is 404.
56
57| Route | Answer |
58|---|---|
59| `GET /api/v1/search?q=` | `{ name, status, rows }` — `name` is `{ label, tld, ascii, display }`, `status` is the registry's answer, `rows` are the priced registrar rows |
60| `GET /api/v1/tlds?q=&kind=&sort=&page=` | OpenVibe.Host's list as it answers it: `{ tlds, next_cursor }`. Sorts: `popular`, `tld`, `register`, `renew`, `five_year` |
61| `GET /api/v1/tlds/:tld` | `{ tld }` with its prices priced like the page |
62| `GET /api/v1/domains/:domain` | `{ name, history, prices }` — history from OpenVibe.Tools, prices for that domain's ending only |
63
64`/api/v1` and `/search` share a per-IP limit of 120 requests a minute (`express-rate-limit`).
65
66## What "priced" means
67
68OpenVibe.Host's check rows carry `register`, `renew`, `five_year`, `registrars`, `currency`, `checked_at`, `tld`,
69`display` and `kind`. Each row gains:
70
71- `y1` — first year: `register`;
72- `y3` — three years: `register + 2 × renew`;
73- `y5` — five years: the catalog's `five_year` when it has one, else `register + 4 × renew`;
74- `trap` — `renew ≥ 2 × register` **and** `renew − register ≥ 5`: the cheap-first-year renewal trap.
75
76A total that needs a missing price is `null`, never `NaN`. Money is rounded to cents. Only USD rows are compared and
77sorted (by `y3`, rows without a `y3` last); other currencies are kept after them and flagged `currency_other: true`.
78
79## The data it reads
80
81| Source | Endpoint | Used for | Cache |
82|---|---|---|---|
83| OpenVibe.Host | `GET /api/v1/catalog/domains?sort=…&cursor=…` | the catalog's ending list and the sitemap | 10 minutes |
84| OpenVibe.Host | `GET /api/v1/catalog/domains/:tld` | one ending's registrar prices and history | 10 minutes |
85| OpenVibe.Host | `GET /api/v1/catalog/domains/check?name=` | a name's registry status and priced endings | 2 minutes |
86| OpenVibe.Tools | `GET /api/v1/domains/` | the public most-looked-up and expiring-soon lists | 10 minutes |
87| OpenVibe.Tools | `GET /api/v1/domains/:domain` | a domain's public snapshots and changes | 10 minutes |
88
89Both clients take an injectable `fetchImpl`, time out after 8 seconds, and keep a small in-process `Map` cache with a
902,000-entry cap (oldest insertion out). A failure is `null` and one short log line — never an exception in a page.
91The upstream URLs come from `OV_HOST_INTERNAL_URL` / `OV_TOOLS_INTERNAL_URL`, or the internal origins in the pinned
92OpenVibe.Contracts manifests, with `https://openvibe.tools` as the Tools fallback.
93
94OpenVibe.Tools keeps RDAP's public registration facts only (registrar, dates, status codes, name servers, DNSSEC) and
95publishes a domain on its lists only after at least five lookups. This service adds no data of its own.
96
97## Configuration
98
99Copy [.env.example](.env.example) to the environment file (`/etc/openvibe/domains.env` in production):
100
101| Variable | Default | What |
102|---|---|---|
103| `NODE_ENV` | `development` | `production` selects the production base URL |
104| `PORT` | `5070` | listen port |
105| `HOST` | `127.0.0.1` | listen address |
106| `BASE_URL` | `https://openvibe.domains` in production | public origin used for canonical URLs, the sitemap and llms.txt |
107| `TRUST_PROXY` | `2` | how many proxies to trust (nginx and the CDN) |
108| `OV_HOST_INTERNAL_URL` | the Host manifest's `internalOrigin` | OpenVibe.Host's internal API |
109| `OV_TOOLS_INTERNAL_URL` | the Tools manifest's `internalOrigin` | OpenVibe.Tools' internal API |
110| `DOMAINS_RATE_LIMIT_PER_MIN` | `120` | the shared `/api/v1` and `/search` per-IP budget |
111| `DOMAINS_UPSTREAM_TIMEOUT_MS` | `8000` | the two data clients' timeout |
112
113## Run it
114
115```bash
116npm install
117npm test # every test/*.test.js, each in its own process, with stub upstreams
118npm run dev # http://localhost:5070
119npm start # production
120```
121
122The tests need no database, no network and no running OpenVibe site. `node --check` every `.js` file is the other
123gate.
124
125## Machine endpoints
126
127| Route | What |
128|---|---|
129| `/api/health` | `{ status: "ok", service: "openvibe-domains", version }` |
130| `/api/ready` | truthful readiness (`openvibe-shared/ready`): `ready` while the process serves; a Host or Tools failure makes `status: "degraded"` and names it, never 503 |
131| `/release.json`, `/release-metrics` | the release manifest and the tab collector (`openvibe-shared/release`) |
132| `/metrics` | Prometheus text format, loopback only (`openvibe-shared/metrics`) |
133| `/shared/*` | this service's own pinned OpenVibe Frame files |
134| `/robots.txt` | allow everything, name the sitemap |
135| `/sitemap.xml` | `/` and one `/tld/<ending>` per ending in every page of the Host catalog; `lastmod` from the release |
136| `/llms.txt` | what the site is, its pages and its API |
137
138## Deploy
139
140- `deploy/systemd/openvibe-domains.service` — user `ubuntu`, `WorkingDirectory=/opt/openvibe.domains`,
141 `EnvironmentFile=/etc/openvibe/domains.env`, port 5070.
142- `deploy/nginx/openvibe.domains.conf` — TLS, `www` → apex 301, proxy to `127.0.0.1:5070`, security headers, GET-only
143 `/api/v1`, loopback-only metrics.
144- CI: `.github/workflows/ci.yml` runs the shared syntax/contracts/security jobs; there is no database service.
145
146## License
147
148AGPL-3.0-only. See [LICENSE](LICENSE).
149
150<!-- versions:start -->
151- openvibe-contracts: v0.419.0
152- openvibe-sdk: v0.166.0
153- openvibe-shared: v4.241.0
154<!-- versions:end -->
155