Skip to content

Releases: ckumar392/zonegit

v0.8.0

28 May 06:57

Choose a tag to compare

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/refs returns the primary's zones and branch tips;
    POST /v0/objects/walk returns the missing-object set given
    {roots, known}; GET /v0/objects/<hash> streams the object body
    with X-Object-Kind header. 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
    new resolve.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 --primary is
    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 the Authorization header
    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

23 May 12:19

Choose a tag to compare

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 wraps pkg/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 the zonegitd flag set:
    foo.com. {
        zonegit /path/to/repo {
            branch main
            canary canary:20
            at HEAD~5
        }
    }
    
    See plugin/coredns/zonegit/README.md.
  • cmd/coredns-with-zonegit/ — a custom CoreDNS binary with the
    plugin compiled in. Build via make coredns. Produces bin/coredns
    (~99 MB) that's wire-compatible with stock CoreDNS for every
    directive and speaks the new zonegit directive.
  • 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.
  • Makefile gains a coredns target. 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 from refs/heads/<zone>/<branch> I don't already have"
    almost trivial to implement; v0.8 will ship a gRPC streaming
    protocol so a secondary zonegitd can 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's zonegit_dns_queries_total is only
    populated when serving via zonegitd. A Corefile directive that
    enables the resolver's MetricsHook against 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

23 May 11:34

Choose a tag to compare

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 / LoadFromDir persist them as base64 files, and
    SignRRset wraps miekg/dns's RRSIG.Sign to 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-run remains 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 whose TypeCovered
    matches the query type. dig +dnssec now returns answer + RRSIG
    inline (pkg/resolve/resolve.go).
  • SIGHUP config reloadzonegitd listens 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 walkfindCommitBySOASerial now 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
    shows dig +dnssec returning an A + RRSIG (96-char Ed25519
    signature, KSK key tag visible in the RRSIG header).
  • cmd/zonegit/sign_zone.go is now backed by pkg/dnssec for the
    signing path; the placeholder mode (--dry-run) remains for tests.

Known limitations (deferred to v0.7)

  • CoreDNS plugin — the pkg/resolve.Resolver is 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 a coredns-with-zonegit binary). 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. A zonegit 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-zone is run. Resolvers won't accept signatures past their
    expiration window. v0.7 adds an opt-in --auto-sign flag to
    zonegit set / commit that 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

23 May 11:18

Choose a tag to compare

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.go walks
    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. after reset --hard) or when serials
    match. Driven entirely by pkg/history.Diff — the same routine that
    powers zonegit diff.
  • Per-zone YAML configzonegitd --config &lt;file&gt; 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 scaffoldzonegit sign-zone --dry-run enumerates 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).
  • KindSymref and Repo.SwitchZone continue 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 runs sign-zone --dry-run and digs the resulting DNSKEY
    and NSEC records.
  • cmd/zonegitd/main.go now resolves per-zone settings through
    daemonConfig.ruleFor(zone) during reconciliation. The CLI
    --branch / --canary / --at / --canary-salt flags are
    preserved as the top-level config defaults.
  • Adds gopkg.in/yaml.v3 as 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

23 May 10:52

Choose a tag to compare

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 via refs/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.
    Existing init registers the first zone; zone add joins additional
    zones without moving HEAD (cmd/zonegit/zone.go).
  • Multi-zone daemonzonegitd enumerates registered zones at
    startup and registers one Resolver per zone with miekg/dns. Time
    travel, canary, and AXFR all apply per zone. --zone becomes 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
    SetWatchedRefs lets 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/zone ref is removed. Idempotent and crash-safe to
    resume (pkg/refs/refs.go MigrateLegacyV03,
    pkg/repo/repo.go Open).

Changed

  • Repo.Head now returns (zone, branch, commit, err). Callers must
    update to consume the zone segment.
  • refs.DB.CreateBranch / UpdateBranch / DeleteBranch / GetBranch / ListBranches / CreateTag / GetTag / DeleteTag / ListTags and SetHEAD
    all take an additional zone parameter; bare-name Resolve now
    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 both foo.com. and bar.com. from one port
    without restart (scripts/demo.sh).
  • cmd/zonegit/main.go --zone flag, 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 via SetWatchedRefs.

Removed

  • Repo.Zone() and Repo.SetZone() — superseded by ActiveZone(),
    AddZone(), Zones(), SwitchZone().
  • The MaxHeadTargetLen constant — 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 / --canary configuration 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

23 May 08:34

Choose a tag to compare

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 commitpkg/repo.Commit now 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 from cmd/zonegitd
    into its own package per the architecture diagram. Provides
    Resolver.Handle, Resolver.HandleWithRemote, AXFR streaming, and the
    Snapshotter/Router/MetricsHook seams (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 daemonzonegitd --at <refish> pins serving to a
    historical commit. Any dig against 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 routingzonegitd --canary canary:20 plus a tiny
    subnet-bucket selector in pkg/route send 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 :9353 exposes
    zonegit_dns_queries_total{qtype,rcode} and an active-branch info
    gauge. Hand-rolled (no client_golang dep)
    (pkg/resolve/metrics.go).
  • Ed25519 commit signingzonegit 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 verbszonegit 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 metadatazonegit init <zone> writes the zone
    name to refs/zonegit/zone. Subsequent CLI and daemon invocations
    auto-load it, so --zone is 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-travel dig, canary bucket split,
    AXFR, propose/approve, and a curl against /metrics
    (scripts/demo.sh).
  • cmd/zonegitd/main.go shrank from ~260 LoC of inline DNS handling
    down to ~150 LoC of flag parsing + wiring; the heavy lifting moved
    into pkg/resolve.

Known limitations

  • AXFR is full-only — no IXFR (delta) yet. Secondaries with a primary
    pointing at zonegitd re-AXFR on every NOTIFY. v4 will add IXFR
    over the existing commit-diff plumbing in pkg/history.
  • The selector engine in pkg/route is 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

27 Apr 17:45

Choose a tag to compare

Changelog

v0.1.0

26 Apr 19:24

Choose a tag to compare

Changelog