dns #
Query DNS, and manage DNS records and zones at hosting providers, from the shell. Every
provider is a plugin. The app and the plugins are bundles,
and they install with bundle.
Install bundle if you don't yet have it. Then click the link to install dns: nzip:pipobscure.com/dns
Or, from a terminal:
bundle install @pipobscure.com/dns
bundle search --for @pipobscure.com/dns # the provider plugins
bundle install --for @pipobscure.com/dns @pipobscure.com/desec # one of them
dns login example.com --provider desec
dns list example.com
See packages/dns/README.md for using it.
This repository #
packages/dns |
the app, @pipobscure/dns: the dns command, and a library other apps can take as a plugin |
packages/provider/* |
the provider plugins this project publishes, one package each (@pipobscure/dns-<provider>) |
tools/pack.ts |
builds every bundle into build/ |
tools/smoke.ts |
runs the built app with every built plugin installed |
tools/release.sh |
the release, run on the spindle for a version tag |
.tangled/workflows |
CI and release, on tangled's spindle |
Nothing here is published to npm. Every package is private, and it ships as a bundle.
The sources are erasable-syntax TypeScript that node runs as it is, from disk and from inside
an archive. There is no build step: what is in a bundle is what is in src/.
npm ci
npm run dns -- providers # the app from source, with every plugin linked from its folder
npm test # every package's tests
npm run typecheck
npm run pack && npm run smoke # build the bundles, and run them as installed
Node 26.11.1 or later, because node:vfs is what bundles run on.
Releasing #
Everything is released together, at one version:
npm run version:set 0.2.0
git commit -am v0.2.0 && git tag -a v0.2.0 -m v0.2.0 && git push --follow-tags
The tag starts release.yml on the spindle. It tests,
builds and smoke-tests every bundle, and attaches them to the tag as release files. It then
attests each one (bundle attest --kind published) and publishes the listings: dns for the
app, and one for each plugin, named for its provider (desec, cloudflare) and listed --for
the app. Being listed for the app is what marks a plugin, so the dns- its package and file
carry is left out of its listing. It needs the secrets
TANGLED_HANDLE and TANGLED_APP_PASSWORD (tangled secret set …). Run by hand
(tangled run trigger -w release.yml), it is a dry run.
The release job gets the tangled CLI with bundle install, accepting it only with its
publisher's attestation (ci/policy.json).
Writing a provider plugin #
Anyone can write and publish a provider. A plugin is a package that dns imports by name, and
its default export is a Provider, as provider.ts describes:
import type { Provider } from '@pipobscure/dns/provider';
const provider: Provider = {
api: 1,
name: 'example', // what `--provider` and accounts call it
title: 'Example DNS',
settings: [{ name: 'token', description: 'an API token', secret: true, env: 'EXAMPLE_TOKEN' }],
open(settings, context) {
return {
async zones() { … }, // [{ name: 'example.com' }]
async records(zone, filter) { … }, // [{ name, type, ttl, records: ['presentation data'] }]
async apply(zone, changes) { … }, // { action: 'set', rrset } | { action: 'delete', name, type }
};
},
};
export default provider;
- Name it as you like. The package name, the listing name and the provider name can all
differ. dns imports a plugin once to learn what it offers and caches that by its build, and
dns loginrecords which package an account's provider came from. This project's own plugins are@pipobscure/dns-<provider>, inpackages/provider/<provider>, listed as<provider>. - Speak RRsets in presentation format. Names are fully qualified, without the trailing
dot. Record data is zone-file text with absolute names (
10 mail.example.com.). TXT is quoted strings. Convert to and from your API's shapes withcontext.rdata. - Use
context.httpfor requests. It handles JSON, gives error messages from the response body, waits out rate limits, and logs under--verbose. A plugin needs no dependencies, and imports nothing of dns at run time, only types. loginis optional: implement it if your provider can do better than asking for each setting, such as creating a scoped token from a username and password.- Develop it linked:
bundle install --for @pipobscure/dns ./my-pluginlinks the folder, and the installeddnsloads your plugin from it as it is, every run, so a change is there at once. Installing the released plugin replaces the link. - Test against
fakeContextinpackages/dns/test/support.ts.provider/desecis the reference. - Ship it as a plain bundle (no launcher), and publish it for the app:
bundle publish --for @pipobscure.com/dns <provider> <url>, named for the provider it adds.
Licence #
- TypeScript 97%
- JavaScript 2%
- Shell <1%