Skip to content

Add porkbun: DNS at Porkbun from the command line - #25

Merged
ralyodio merged 1 commit into
masterfrom
worktree-porkbun-cli
Aug 28, 2026
Merged

Add porkbun: DNS at Porkbun from the command line#25
ralyodio merged 1 commit into
masterfrom
worktree-porkbun-cli

Conversation

@ralyodio

Copy link
Copy Markdown
Contributor

Adds a porkbun command: DNS at Porkbun without the dashboard.

porkbun ls example.com
porkbun set example.com www CNAME app.up.railway.app
porkbun unpark example.com --dry-run

Why unpark exists

A domain bought and left alone answers with an ALIAS at the apex and a wildcard
CNAME, both pointing at a *.porkbun.com host. Those records are not
independent
— they are how a URL forwarding rule is implemented. Adding your own
ALIAS beside them does nothing: the forward keeps winning, the new host never sees a
request, and it reads as a broken deploy rather than a DNS problem. Deleting the
forward takes its records with it.

This cost an afternoon on d3vices.com today. It is one command now.

What counts as parking is deliberately narrow — only ALIAS/CNAME pointing at
porkbun.com. The MX records at fwd1.porkbun.com are Porkbun's email
forwarding
and the NS records are the zone's delegation; sweeping either up would
break mail or take the domain off the internet.

Two API shapes worth knowing

  • Every call is a POST with credentials in the body, and status is a field,
    not the HTTP code.
    A bad key, an unknown domain and a malformed record all return
    200 OK with {"status":"ERROR"} — so checking response.ok reports success for
    all three. unwrap treats the body's own status as the verdict.
  • The API's name is the label relative to the zone, and the apex is the empty
    string, which nobody types. @, empty, the bare label and the full name are all
    accepted and normalised — the alternative is creating www.example.com.example.com.

set is an upsert

It edits in place, keeping the record id. Delete-then-create has a window where the
name does not resolve at all. It reports unchanged and sends no write when the
value already matches, and refuses rather than guessing when several records share a
name and type (two TXT values is legitimate; silently replacing one is not).

Also

keyVariable now treats - and _ as the same separator. The single-word keys
never needed it; porkbun_secret_api_key and porkbun-secret-api-key are the same
key and both have to reach the same entry.

Verification

381 tests pass, typecheck clean. Exercised against the live API: create, no-op
re-set, in-place update, read-back via both host forms, delete by host+type, plus the
missing-credential and below-minimum-TTL error paths. The throwaway test record was
removed and the zone left at its original 13 records.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Hh54BWan1jXMzKqqwLMSr

Porkbun hosts most of the zones here and its API is the only way to change a
record without the dashboard. That is fine for one record and not fine for
"point the apex and www at a new host", which is four edits that have to land
together.

`unpark` is the reason this exists. A domain bought and left alone answers with
an ALIAS at the apex and a wildcard CNAME, both pointing at a *.porkbun.com
host, and those records are not independent: they are how a URL forwarding rule
is implemented. Adding your own ALIAS beside them does nothing at all -- the
forward keeps winning, the new host never sees a request, and the failure reads
as a broken deploy rather than a DNS problem. Deleting the forward takes its
records with it. That cost an afternoon on d3vices.com today; it is one command
now, with --dry-run to see the plan first.

What counts as parking is deliberately narrow -- only ALIAS and CNAME records
pointing at porkbun.com. The MX records at fwd1.porkbun.com are Porkbun's email
forwarding and the NS records are the zone's delegation, so sweeping either up
would break mail or take the domain off the internet.

Two API shapes drive the rest:

- Every call is a POST with the credentials in the JSON body, and `status` is a
  field rather than the HTTP code. A refused key, an unknown domain and a
  malformed record all come back 200 OK with {"status":"ERROR"}, so checking
  response.ok reports success for every one of them. `unwrap` treats the body's
  own status as the verdict and surfaces `message` verbatim.
- The API's `name` is the label relative to the zone and the apex is the empty
  string, which nobody types. `@`, an empty value, the bare label and the full
  name are all accepted and normalised, because the alternative is creating
  www.example.com.example.com.

`set` is an upsert that edits in place, keeping the record id: delete-then-create
has a window where the name does not resolve. It reports `unchanged` and sends
no write when the value already matches, and refuses rather than guessing when
several records share a name and type.

keyVariable now treats `-` and `_` as the same separator. The single-word keys
never needed it; porkbun_secret_api_key and porkbun-secret-api-key are the same
key and both have to reach the same entry.

Verified against the live API: create, no-op re-set, in-place update, read-back
by both host forms, delete by host+type, and the missing-credential and
below-minimum-TTL error paths. The throwaway test record was removed and the
zone left at its original 13 records.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Hh54BWan1jXMzKqqwLMSr
Comment thread src/credentials.ts
perplexity: 'PERPLEXITY_API_KEY',
elevenlabs: 'ELEVENLABS_API_KEY',
porkbun: 'PORKBUN_API_KEY',
porkbun_secret: 'PORKBUN_SECRET_API_KEY',
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

12 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 1 | LOW: 6

Severity Rule Location
HIGH sh-remote-script-execution root-ubuntu.sh:122
HIGH sh-remote-script-execution root-ubuntu.sh:2044
HIGH sh-remote-script-execution root-ubuntu.sh:2048
HIGH sh-remote-script-execution root-ubuntu.sh:2101
HIGH sh-remote-script-execution root-ubuntu.sh:3130
MEDIUM redos-nested-quantifier src/domain-free.ts:56
LOW secret-generic-credential src/credentials.ts:36
LOW insecure-temp-file test/blog.test.ts:73
LOW insecure-temp-file test/blog.test.ts:74
LOW insecure-temp-file test/credentials.test.ts:43
LOW insecure-temp-file test/credentials.test.ts:44
LOW secret-generic-api-key test/credentials.test.ts:208

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit d7cd959 into master Aug 28, 2026
5 checks passed
@ralyodio
ralyodio deleted the worktree-porkbun-cli branch August 28, 2026 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants