DNS Module

The portable, **provider-abstracted** core that reads and writes DNS records for our farm domains. A small `DnsProvider` interface with a **deSEC** adapter first and Dynadot kept as an optional second backend, so we are never locked to one registrar's API. Every surface in Calling the Module wraps this one core.

## Why Abstract the Provider

DNS APIs differ wildly. Dynadot's is a quirky `command=` call that overwrites the whole zone; deSEC's is clean per-record REST. Rather than bind to either, the module defines an interface — the same idea as Go's `libdns` — and writes a thin adapter per provider:

interface DnsProvider { getRecords(zone): Promise<Rrset[]> upsertRecord(zone, { subname, type, ttl, records }): Promise<void> deleteRecord(zone, subname, type): Promise<void> }

## deSEC First

The deSEC adapter is almost trivial because the API is REST-clean — per-record RRset CRUD, one token:

GET/POST/PATCH/DELETE https://desec.io/api/v1/domains/{zone}/rrsets/ Authorization: Token <desec-token>

No get-everything-then-rewrite, no XML. Automatic DNSSEC comes for free on the deSEC side.

## Two Runtimes, One Core

- A **TypeScript core** (`@hitchhiker/dns`) for The Guide, the CLI, and Node. - A **Rust component on WASM on Spin** — native WebAssembly, the canonical sandboxed service. It exercises Rust→WASM on the Spin server; a TypeScript→WASM variant can be added later to compare the toolchains.

## Secrets

The deSEC token is captured with the keychain-secret discipline and handed to each runtime as an environment variable or a Spin variable — never in the repository, a wiki page, or a chat log. On Spin the outbound sandbox means a leaked component reaches nothing but deSEC.

## See - Farm DNS Automation — why this exists - deSEC Migration — the provider move - Calling the Module — where it runs - WASM on Spin — the Rust adapter