# atlibs-marque An [atlibs](https://knotless.dev/johnandersen777.bsky.social/atlibs) wasm module (knotless://johnandersen777.bsky.social/atlibs) and a page that edit the DNS records of the domains an account registered through [Marque](https://marque.at), the AT Protocol registrar. ## How Marque keeps DNS The lexicons are published by the `at.marque` authority (`_lexicon.marque.at` TXT -> `did:plc:nckosudltxrtrjkt4zz4jy5y`, `com.atproto.lexicon.schema` records on its PDS): - `at.marque.domain`: one record per domain in the owner's repo, keyed by the FQDN (`domain`, `status`, `registeredAt`, `expiresAt`, `nameServers`, `dnssec`, `autoRenew`, ...). Marque's partner flow writes it into the repo of the DID that paid for the domain; that record is what this module takes as "the account owns this domain". - `at.marque.dns`: the domain's whole zone in ONE record with the same key: `domain`, `subject` (a strongRef to the `at.marque.domain` record), `records` (`at.marque.dns#entry`: `name` relative to the apex or `@`, `recordType` A/AAAA/CNAME/MX/TXT/SRV/NS/CAA, `value`, `ttl`, `priority` for MX and SRV) and `createdAt`. [Marque's docs](https://docs.marque.at/partner/orders-records/) say its nameserver consumes the public `at.marque.dns` record, so an edit is only a repo write: no XRPC call to Marque. (`at.marque.dns.getRecords`, a query on `did:web:marque.at#marque_registrar` for the records it actually serves, exists but no Marque permission set grants it; this module does not call it.) ## The module `modules/marque`, session `records/marque.json`, scope `atproto repo:at.marque.dns?action=create&action=update`: it can write DNS zones and nothing else (not `at.marque.domain`, and it never deletes a zone). | entry | input | does | |---|---|---| | `list_domains` | | the account's `at.marque.domain` records (paged) | | `list_records` | `domain` | the domain's zone: `records`, `uri`, `cid`, the domain's `status` | | `create_record` | `domain name type value [ttl] [priority]` | adds a record (TTL defaults to 3600) | | `update_record` | `domain name type value [newName] [newValue] [ttl] [priority]` | changes the record named by name, type and value; blank keeps its own | | `delete_record` | `domain name type value` | removes that record | Reads are public reads of the account's own PDS, so listing works signed out for any DID. Writes read the domain and zone records, refuse a domain with no `at.marque.domain` record in the repo, and put the zone back with `swapRecord` (a concurrent edit fails with "try again" instead of being lost), keeping fields Marque added (such as `baseRevision`) and pointing `subject` at the domain record as it is now. A domain with no zone yet gets one with `createRecord`. Calls: `listRecords`, `getRecord`, `createRecord`, `putRecord`; `anyRecords: ["at.marque.dns"]`, as Marque writes the zone first. Checked in the module before any call: the name (`@`, or relative labels of `a-z0-9-_`, a leading `*` label, no trailing dot; SRV names `_service._proto`; no NS at `@`, those are the domain's nameservers), the value per type (IPv4, IPv6, hostnames, SRV `weight port target`, CAA `flags tag value`, TXT 1 to 4096 bytes without control characters), TTL 1 to 2^31-1, priority 0 to 65535 on MX and SRV only; and against the zone: no exact duplicates, a CNAME alone at its name. Values are stored as Marque stores them: hostnames lowercase without the trailing dot, IPv6 compressed. ## Layout - `modules/marque`: the module (Rust, against atlibs' `abi` SDK), its `module.json` and `test.mjs` - `web/marque`: a page: sign in, your domains, a domain's records with add, edit and remove - `records/marque.json`: the session record ## Set up atlibs is a sibling checkout: Cargo fetches the `abi` crate from git, and the tests and the page import its JavaScript from `../atlibs`. ```bash git clone https://knotless.dev/johnandersen777.bsky.social/atlibs ../atlibs && git -C ../atlibs checkout -q 88b2ba8 ``` ## Build and test ```bash cargo wasm node --test modules/*/test.mjs web/*/*.test.mjs (cd ../atlibs && cargo host) # the atlibs CLI, for the Rust host node --import ../atlibs/rust/host/swap.mjs --test modules/*/test.mjs # the same, on the Rust host ``` `web/marque/marque.test.mjs` boots the page in headless Chrome (skipped without Chrome). ## Run ```bash python3 -m http.server 8000 --bind 127.0.0.1 --directory web/marque ``` Open http://127.0.0.1:8000/ (`?module=bobvmbuilder.bsky.social/marque` runs the published module record instead of your build). From a shell or a server, the same entries (reads need no sign-in): ```bash atlibs run modules/marque list_domains '{"did":"did:plc:..."}' atlibs run modules/marque list_records '{"did":"did:plc:...","domain":"example.com"}' atlibs login you.example atlibs run modules/marque create_record '{"domain":"example.com","name":"_atlibs-test","type":"TXT","value":"hello"}' --as you.example ``` `atlibs stage --target spa --origin https://you.example/app/ --out dist modules/marque` stages the module and its OAuth client for a page.