Releases: ckumar392/zonegit
v0.8.0
Pull replication. A secondary zonegitd can now mirror a primary
continuously over HTTP. The protocol is a Merkle walk: secondary asks
"what objects do you have reachable from these branch tips that I
don't have given I already have these?", primary computes the delta,
secondary fetches each object by content hash. Idiomatic for a
content-addressed store, network-efficient by construction, and
provable correct because every object body validates against its own
hash on insert.
Added
pkg/replicate— three-endpoint HTTP protocol under/v0/:
GET /v0/refsreturns the primary's zones and branch tips;
POST /v0/objects/walkreturns the missing-object set given
{roots, known};GET /v0/objects/<hash>streams the object body
withX-Object-Kindheader. Client and Server are independent —
embed either in a custom daemon, or use both via the zonegitd
flags below (pkg/replicate).zonegitd --replicate-listen :PORT— primaries expose the
replication endpoints on this port (separate from the DNS listener
so traffic stays clean).zonegitd --primary URL— runs the daemon in secondary mode.
Opens the local repo writable, runs a background poller every
--pull-interval(default 5s), lands incoming objects via the
Merkle delta. Resolver reads through the same writable handle (a
newresolve.StaticSnapshotter) so newly-pulled commits answer
the very next DNS query.- Secondary-mode startup tolerates an empty repo — the v0.4
"must have at least one zone" check is skipped when--primaryis
set, because the secondary's zones come from replication.
Changed
- The 24-step demo grew to 25 steps. New step 24 spins up a fresh
empty secondary daemon, watches it catch up from the primary,
mutates a record on the primary only, and shows the secondary
delta-pulling the change within 2 seconds — same record, two
daemons, no shared filesystem
(scripts/demo.sh). - Reconciler now detects the snapshotter mode: in secondary mode it
reuses the writable handle (Badger forbids parallel read-only opens
while a write lock is held); in primary mode it keeps opening a
fresh read-only handle per tick.
Known limitations (deferred to v0.9)
- One-way replication only. Multi-master is a v1.0+ design
problem deliberately out of scope here. The "primary owns all
writes" model fits the typical DNS deployment shape. - No authentication on the replication endpoints. A v0.9 add-on
will require a shared HMAC token in theAuthorizationheader
before serving/v0/*. Today the assumption is that the
replication network is private. - Object fetch is one-at-a-time over HTTP. Efficient enough for
the demo (~100 objects/sec on localhost); a batched-streaming
variant of the same wire protocol (HTTP/2 multiplexing or gRPC
bidi) is the v0.9+ throughput optimization.
Prebuilt binaries for Linux and macOS (amd64 + arm64) are attached below. The 25-step make demo now ends with a primary→secondary replication step that catches a fresh empty daemon up from the primary, then delta-pulls a live mutation within 2 seconds.
v0.7.0
Integration + operational completeness. v0.7 closes the last credible
SME objections from the v0.6 punch list: a real CoreDNS plugin (drop
into your existing CoreDNS deployment), a DS-record helper (publish
trust anchors to the parent zone), and auto-resigning on every commit
that touches a signed RRset.
Added
- CoreDNS plugin (
plugin/coredns/zonegit/) — a real CoreDNS
plugin that wrapspkg/resolve.Resolver. Lives in its own Go module
so the (very large) CoreDNS dependency tree stays out of the main
zonegit module. Corefile syntax mirrors thezonegitdflag set:See plugin/coredns/zonegit/README.md.foo.com. { zonegit /path/to/repo { branch main canary canary:20 at HEAD~5 } } cmd/coredns-with-zonegit/— a custom CoreDNS binary with the
plugin compiled in. Build viamake coredns. Producesbin/coredns
(~99 MB) that's wire-compatible with stock CoreDNS for every
directive and speaks the newzonegitdirective.zonegit ds [zone]— prints the parent-zone DS record for a
zone's KSK (digest type 2, SHA-256). Operators paste the output into
their parent zone to complete the DNSSEC chain of trust.zonegit set --auto-sign— when keys exist for the active zone,
re-signs the touched RRset in the same commit that mutates it.
The helper preserves RRSIGs covering untouched RRsets at the same
owner (since all RRSIGs at an owner share a single Rrtype=RRSIG
blob, naïve re-signing would have wiped them; auto-sign loads the
existing RRSIG set, drops only the ones being replaced, appends
fresh, and re-stages).
Changed
- The demo grew from 21 steps to 24. New steps 21–23 walk through
zonegit ds,set --auto-sign, and a side-by-side dig comparison
of zonegitd and the new CoreDNS-with-zonegit binary serving the same
repo on different ports. Makefilegains acorednstarget. First run pulls in the CoreDNS
module graph (~5 min over a cold module cache); subsequent builds
use the Go module cache and finish in seconds.
Known limitations (deferred to v0.8)
- Pull replication — the Merkle DAG makes "give me every reachable
object fromrefs/heads/<zone>/<branch>I don't already have"
almost trivial to implement; v0.8 will ship a gRPC streaming
protocol so a secondaryzonegitdcan mirror a primary continuously. - NIOS integration bridge — the Infoblox-specific connector that
watches NIOS zone changes and lands them as zonegit commits. The
single highest-leverage v0.8/v0.9 feature for an Infoblox SME
audience. - CoreDNS plugin metrics — currently emits CoreDNS's own
coredns_*metrics; zonegit'szonegit_dns_queries_totalis only
populated when serving viazonegitd. A Corefile directive that
enables the resolver'sMetricsHookagainst an exporter would close
the gap.
Prebuilt binaries for Linux and macOS (amd64 + arm64) are attached below. The 24-step make demo ends with a side-by-side comparison of zonegitd and a custom CoreDNS-with-zonegit binary serving the same repo.
v0.6.0
Real DNSSEC + operational completeness. v0.6 turns the v0.5 DNSSEC
scaffold into actual cryptographic signing (Ed25519, RFC 8080), adds
SIGHUP-driven config reload so the daemon never needs to restart for a
policy change, and fixes the IXFR walk to handle merge ancestors.
Added
- pkg/dnssec — Ed25519 (algorithm 15, RFC 8080) keypair management
and RRSIG generation.Generate()mints a fresh KSK + ZSK,
WriteToDir/LoadFromDirpersist them as base64 files, and
SignRRsetwraps miekg/dns'sRRSIG.Signto produce signatures that
validate end-to-end. KSK signs DNSKEY; ZSK signs everything else
(pkg/dnssec/dnssec.go). zonegit zone-keygen— generate and persist a zone's KSK + ZSK
under<repo>/keys/. One command per zone.zonegit sign-zone(real signing) — when keys are present, emits
real RRSIGs over every RRset. NSEC chain regenerated, DNSKEYs at the
apex point at the loaded public keys.--dry-runremains for tests
and demos that don't want to roll keys.- RRSIG-batching — multiple RRsets at the same owner each get an
RRSIG; all of them are stored together as a single RRSIG RRset (one
blob per owner) so the existing(owner, rrtype)storage model
carries DNSSEC without schema changes. - DO-bit-aware resolver — when the requester sets the DNSSEC-OK
bit in the OPT pseudo-record, the resolver looks up the RRSIG RRset
at the answer's owner and appends any RRSIG whoseTypeCovered
matches the query type.dig +dnssecnow returns answer + RRSIG
inline (pkg/resolve/resolve.go). - SIGHUP config reload —
zonegitdlistens for SIGHUP, re-parses
--config, and atomically swaps it. The reconciler tracks the rule
each zone was registered with and re-registers only those whose
config actually changed. No restart, no dropped queries
(cmd/zonegitd/main.go). - IXFR full-DAG walk —
findCommitBySOASerialnow BFS-walks all
parents (not just first-parent), so an IXFR with a serial that
landed via a merge ancestor is still resolved correctly. Bounded at
10k commits to defend against pathological graphs
(pkg/resolve/ixfr.go).
Changed
- Demo step 20 now generates a real keypair, runs
sign-zone, and
showsdig +dnssecreturning an A + RRSIG (96-char Ed25519
signature, KSK key tag visible in the RRSIG header). cmd/zonegit/sign_zone.gois now backed bypkg/dnssecfor the
signing path; the placeholder mode (--dry-run) remains for tests.
Known limitations (deferred to v0.7)
- CoreDNS plugin — the
pkg/resolve.Resolveris plugin-shaped and
ready to wrap; the remaining work is the plugin scaffold, setup
function, and Corefile parser registration (~150 LoC + a Makefile
target that builds acoredns-with-zonegitbinary). Slated as the
v0.7 headline so the "plug into your existing CoreDNS deployment"
story has running code behind it. - Trust anchor publishing — DS records for the apex KSK need to be
uploaded to the parent zone for true validation. Azonegit ds
helper that prints the DS record for the KSK is trivial to add and
will ship with v0.7. - Automatic re-signing on commit — today RRSIGs only refresh when
sign-zoneis run. Resolvers won't accept signatures past their
expiration window. v0.7 adds an opt-in--auto-signflag to
zonegit set/committhat re-signs touched RRsets in the same
commit.
Prebuilt binaries for Linux and macOS (amd64 + arm64) are attached below. make demo reproduces the 21-step walkthrough, now ending with a real Ed25519-signed RRSIG over api.foo.com. A.
v0.5.0
Operational polish + DNSSEC scaffold. v0.5 makes the daemon credible
to a DDI SME who lives in primary-secondary deployments: IXFR replaces
the "re-AXFR on every change" approximation, per-zone YAML config
unlocks "production on tenant A, canary 20% on tenant B" without
restarts, and the DNSSEC scaffold proves the object pipeline is
DNSSEC-shaped before v0.6 adds actual crypto.
Added
- IXFR (incremental zone transfer) —
pkg/resolve/ixfr.gowalks
first-parent commits to find the historical commit whose apex SOA
matches the client's serial, then emits a single-delta IXFR response
(RFC 1995 §4: latest SOA → old SOA → removals → latest SOA →
additions → latest SOA). Falls back to AXFR when the historical
commit isn't reachable (e.g. afterreset --hard) or when serials
match. Driven entirely bypkg/history.Diff— the same routine that
powerszonegit diff. - Per-zone YAML config —
zonegitd --config <file>lets each
zone get its own branch / canary / time-travel pin. Top-level
defaults apply to zones not in the map; per-zone overrides win on a
field-by-field basis. The daemon's reconciler reads this every tick,
so adding a zone to the YAML and SIGHUP-equivalent flows are
straightforward extensions (cmd/zonegitd/config.go). - DNSSEC scaffold —
zonegit sign-zone --dry-runenumerates every
RRset, computes an NSEC chain over the canonical sort of owner
names, stages KSK + ZSK at the apex, and stages an RRSIG per RRset
with placeholder signature bytes. All five DNSSEC RR types
(DNSKEY, RRSIG, NSEC, plus the bits encoded in NSEC's type bitmap)
flow through the existing content-addressed object pipeline as
ordinary RRsets — proving the architecture supports DNSSEC before
v0.6 adds real crypto (cmd/zonegit/sign_zone.go). KindSymrefandRepo.SwitchZonecontinue to underpin the demo
— no new layout changes in v0.5; the v0.4 work paid off here.
Changed
- The 19-step demo grew to 21 steps. New step 19 demonstrates IXFR
with an older serial, showing the live commit-DAG-driven delta;
step 20 runssign-zone --dry-runand digs the resulting DNSKEY
and NSEC records. cmd/zonegitd/main.gonow resolves per-zone settings through
daemonConfig.ruleFor(zone)during reconciliation. The CLI
--branch/--canary/--at/--canary-saltflags are
preserved as the top-level config defaults.- Adds
gopkg.in/yaml.v3as a direct dependency (it was already
present transitively); no other new deps.
Known limitations (intentionally deferred to v0.6)
- DNSSEC signatures are placeholder bytes. Resolvers will not validate
them. Real Ed25519 / RSA / ECDSA signing requires KMS or
file-backed-key wiring on the write path — that's the v0.6
milestone. - The daemon doesn't reload the YAML config on SIGHUP yet. Restart
is required to pick up config edits; zone additions / removals are
picked up automatically by the existing reconciler. - IXFR finds the historical commit by walking first-parent only. A
zone with merge commits and a request against a serial that lived
on a merged-in branch (not first-parent) will fall back to AXFR.
Production deployments rarely encounter this pattern.
Prebuilt binaries for Linux and macOS (amd64 + arm64) are attached below, built by goreleaser from this tag. make demo reproduces the full 21-step walkthrough end-to-end, including the IXFR delta-transfer and the DNSSEC scaffold.
v0.4.0
Multi-zone milestone. One repo can now hold many zones; one zonegitd
process serves all of them on a single port. This unlocks the
multi-tenant / MSP narrative (one daemon, many customer zones, per-zone
RBAC via ref isolation) without changing the protocol on the wire.
Added
- Multi-zone repo layout — every branch and tag is scoped to its
zone (refs/heads/<zone>/<branch>,refs/tags/<zone>/<tag>); zone
membership is enumerable viarefs/zonegit/zones/<zone>markers.
Object storage is shared across zones; identical RRsets dedupe
byte-for-byte regardless of which zone they appear in
(pkg/refs/refs.go). zonegit zone add | list | switch— manage zones in a repo.
Existinginitregisters the first zone;zone addjoins additional
zones without moving HEAD (cmd/zonegit/zone.go).- Multi-zone daemon —
zonegitdenumerates registered zones at
startup and registers oneResolverper zone with miekg/dns. Time
travel, canary, and AXFR all apply per zone.--zonebecomes optional
and selects a single zone if given (cmd/zonegitd/main.go). - Runtime zone discovery — a 1s reconciler in the daemon notices
zones added or removed at runtime (zonegit zone add bar.com.is
picked up without a daemon restart). The snapshotter's
SetWatchedRefslets the reconciler extend the watch set on the fly
(cmd/zonegitd/main.go,
pkg/resolve/snapshot.go). - Object-backed HEAD symref (
KindSymref) — HEAD now points at a
content-addressed object containing the target ref string, removing
the 31-byte limit that the v0.3 length-prefix scheme imposed on
refs/heads/<zone>/<branch>paths. Long zone names work
(pkg/object/object.go,
pkg/refs/refs.go). - Automatic v0.3 → v0.4 migration on Open — legacy single-zone
repos are detected and converted in place: branches and tags are
rewritten to the new zone-scoped paths, HEAD is re-encoded as a
symref, the zone marker is created, and the legacy
refs/zonegit/zoneref is removed. Idempotent and crash-safe to
resume (pkg/refs/refs.goMigrateLegacyV03,
pkg/repo/repo.goOpen).
Changed
Repo.Headnow returns(zone, branch, commit, err). Callers must
update to consume the zone segment.refs.DB.CreateBranch / UpdateBranch / DeleteBranch / GetBranch / ListBranches / CreateTag / GetTag / DeleteTag / ListTagsandSetHEAD
all take an additional zone parameter; bare-nameResolvenow
resolves against the active zone from HEAD.- The 18-step demo grew to 19 steps with a new "MULTI-ZONE" step that
registers a second zone (bar.com.) at runtime and proves a single
daemon serves bothfoo.com.andbar.com.from one port
without restart (scripts/demo.sh). cmd/zonegit/main.go--zoneflag, when given, switches HEAD to that
zone's current branch for the duration of the command (using the new
Repo.SwitchZone).- Snapshotter no longer owns its watched-ref list permanently; the
daemon updates it as zones come and go viaSetWatchedRefs.
Removed
Repo.Zone()andRepo.SetZone()— superseded byActiveZone(),
AddZone(),Zones(),SwitchZone().- The
MaxHeadTargetLenconstant — there is no length limit anymore.
Known limitations (intentionally deferred to v0.5)
- The daemon's reconciler opens one fresh read-only Badger handle per
second to discover zone changes. Cheap, but a sentinel-file watcher
or Badger Subscribe would be cleaner at scale. - Per-zone
--branch/--canaryconfiguration is uniform; v0.5 will
add a per-zone config file so different zones can have different
rollout policies. - AXFR is still full-only (no IXFR), inherited from v0.3.
Prebuilt binaries for Linux and macOS (amd64 + arm64) are attached below, built by goreleaser from this tag. make demo reproduces the full 19-step walkthrough end-to-end, including the runtime multi-zone discovery without a daemon restart.
v0.3.0
Demo-readiness milestone. Every claim the README makes now corresponds to
running code (and a step in make demo). The five additions below are what
moved this from "interesting weekend project" to "credible authority
direction":
Added
- SOA serial auto-increment on commit —
pkg/repo.Commitnow stages a
bumped apex SOA whenever any non-SOA RRset is mutated and the user has
not explicitly staged an SOA. Without this, the README's "no SOA dance"
pitch left secondaries with no way to know anything changed
(pkg/repo/repo.go, pkg/zone/soa.go). pkg/resolve— the DNS query path, extracted fromcmd/zonegitd
into its own package per the architecture diagram. Provides
Resolver.Handle,Resolver.HandleWithRemote, AXFR streaming, and the
Snapshotter/Router/MetricsHookseams (pkg/resolve).- Cached snapshotter (
pkg/resolve.PollingSnapshotter) — replaces the
v0/v1 per-query Badger reopen with a single cached handle invalidated
only when a watched branch's tip hash actually changes. Per-query cost
drops from one Badger Open to one atomic pointer load. - Time-travel daemon —
zonegitd --at <refish>pins serving to a
historical commit. Anydigagainst that daemon answers as the zone
existed at that commit. The README's "what did this resolve to N
commits ago?" claim is now answerable by real DNS, not just a CLI
dump (cmd/zonegitd/main.go). - Canary routing —
zonegitd --canary canary:20plus a tiny
subnet-bucket selector inpkg/routesend X% of traffic (by stable
hash of the client/24) to a canary branch. Rollback is one ref
move (pkg/route). This is the v2 SELECTORS.md headline
use case (UC5) implemented in its smallest defensible form;
the full grammar remains a v3 milestone. - AXFR — full-zone transfer over TCP, RFC 5936 compliant
(leading + trailing SOA, RRsets in canonical tree-walk order). Makes
the "drop-in BIND replacement" claim real for primary-secondary
deployments (pkg/resolve/axfr.go). - Prometheus metrics endpoint —
--metrics-listen :9353exposes
zonegit_dns_queries_total{qtype,rcode}and an active-branch info
gauge. Hand-rolled (noclient_golangdep)
(pkg/resolve/metrics.go). - Ed25519 commit signing —
zonegit keygen,zonegit sign-commit,
zonegit verify [--chain]. The signature header was already reserved
on the commit object; this adds the actual sign/verify primitive in
pkg/sign(pkg/sign). - PR-style change verbs —
zonegit propose <name> --from main,
zonegit review <name> --into main,zonegit approve <name> --into main.
Thin convenience over branch/diff/merge, but the vocabulary matches
how change-management SMEs actually talk
(cmd/zonegit/propose.go). - Persisted zone metadata —
zonegit init <zone>writes the zone
name torefs/zonegit/zone. Subsequent CLI and daemon invocations
auto-load it, so--zoneis now optional after the first
init(pkg/refs/refs.go). object.WalkAllLeaves— depth-first leaf enumeration over a tree.
Powers AXFR; will also be the seam for zone-export and signed-zone
workflows (pkg/object/treeops.go).
Changed
- The 15-step demo grew to 18 steps. Coverage now includes SOA
before/after observation, time-traveldig, canary bucket split,
AXFR, propose/approve, and a curl against/metrics
(scripts/demo.sh). cmd/zonegitd/main.goshrank from ~260 LoC of inline DNS handling
down to ~150 LoC of flag parsing + wiring; the heavy lifting moved
intopkg/resolve.
Known limitations
- AXFR is full-only — no IXFR (delta) yet. Secondaries with a primary
pointing atzonegitdre-AXFR on every NOTIFY. v4 will add IXFR
over the existing commit-diff plumbing inpkg/history. - The selector engine in
pkg/routeis one rule shape
(hash(client.subnet, salt) % 100 < pct). The full SELECTORS.md
grammar (geo, ASN, time windows, list literals) remains a v3 item. - Snapshotter invalidates via a 200ms polling reopener — fine for the
demo and a small repo, but production deployments should switch to
fsnotify (Badger's on-disk manifest) or writer-pushed signals. - Commit signing is file-keyed; no KMS yet, no server-side
"refuse unsigned" policy yet. Both are slated for the next
milestone.
Prebuilt binaries for Linux and macOS (amd64 + arm64) are attached below, built by goreleaser from this tag. make demo reproduces the full 18-step walkthrough end-to-end.
v0.2.0
Changelog
- c472e03: v1: branches at serve time, 3-way merge, revert, reset (@ckumar392)
v0.1.0
Changelog
- c0387d1: add contributing guide, changelog, and code of conduct (Chandan Kumar ckumar392@users.noreply.github.com)
- 3b30403: add issue and PR templates (Chandan Kumar ckumar392@users.noreply.github.com)
- 195c4c9: authoritative DNS server and an end-to-end demo (Chandan Kumar ckumar392@users.noreply.github.com)
- 8cebfa5: gofmt fixups in history and refs (forgot to run it before pushing, oops) (Chandan Kumar ckumar392@users.noreply.github.com)
- 32aeba5: history: log, diff, blame, and walk-at-time (Chandan Kumar ckumar392@users.noreply.github.com)
- 5e552a7: make the demo script self-documenting so people can actually follow it (Chandan Kumar ckumar392@users.noreply.github.com)
- d32e988: object model: blob/tree/commit/tag with canonical encoding and content hashing (Chandan Kumar ckumar392@users.noreply.github.com)
- fd9933c: public Go API in pkg/repo, plus a first cut of the cobra CLI (Chandan Kumar ckumar392@users.noreply.github.com)
- 2f5450e: refs: branches, HEAD, tags, reflog, and a small ref-ish resolver (Chandan Kumar ckumar392@users.noreply.github.com)
- 6b9873a: rename dnsdb -> zonegit everywhere; bump module path to github.com/ckumar392/zonegit (Chandan Kumar ckumar392@users.noreply.github.com)
- 366efa5: rewrite README with a proper demo walkthrough (Chandan Kumar ckumar392@users.noreply.github.com)
- ee9c7ad: scaffold the repo: layout, makefile, ci, design doc skeletons (Chandan Kumar ckumar392@users.noreply.github.com)
- 660e747: storage: define the seam, add badger backend and a conformance suite all backends must pass (Chandan Kumar ckumar392@users.noreply.github.com)
- cc40ab4: tidy up repo: drop draft posts and scratch notes, polish README (Chandan Kumar ckumar392@users.noreply.github.com)
- 94fb2ef: wire up goreleaser, add badges (Chandan Kumar ckumar392@users.noreply.github.com)
- 959ec43: zone: canonical RRset encoding plus zonefile import/export (Chandan Kumar ckumar392@users.noreply.github.com)