Repository navigation
Releases: nugget/vanitykeygen
Release list
v0.2.1
v0.2.1 is the release where vanitykeygen stops being a match logger and
becomes a fleet. The wire protocol is rebuilt around persistent state, the
server grows an embedded web dashboard with live updates, target management
becomes a real subsystem with its own type system, and the build, CI,
container, and contributor surfaces all get the polish a real project needs.
The PR review pass that landed alongside the rewrite turned a working
prototype into something safe to point at the public internet.
33 files changed, 3,865 insertions, 600 deletions.
A new client/server protocol
Underneath the surface, this is a from-scratch rewrite of how the two
binaries talk (#4). The single match-log file and ad-hoc HTTP endpoints are
replaced by a REST API backed by an embedded SQLite database (pkg/store,
pure-Go via modernc.org/sqlite so the binary stays CGO-free). Targets,
matches, and connected clients are first-class persisted entities. The
server gives every client a stable ID derived from its hostname so restarts
don't fragment fleet history, computes per-client key rates from heartbeat
deltas, and reaps idle clients in the background. The API itself is
documented in the README's API table, with ?limit= on every list endpoint
so nothing is unbounded.
Gin is gone. The server is back on net/http using Go 1.22+'s
method-prefixed ServeMux patterns, which removes the Gin dependency tree
entirely without any loss of expressiveness for the routes in question.
Embedded web dashboard
The server now ships a single-binary web dashboard at port 8080 (assets
embedded via web/embed.go). Four tabs cover the operator workflow:
Dashboard with live aggregate stats, Targets for CRUD over patterns,
Matches for browsing recorded hits and revealing key material on
demand, and Fleet for watching connected clients and their per-client
key rates. Updates stream over Server-Sent Events on /api/events, with
periodic keep-alives so reverse proxies don't time out idle streams.
The frontend is intentionally dependency-free vanilla JS — a single
web/js/app.js, no build step, no framework — and the server-side
rendering of HTML is limited to the static index.html shell.
Targets become a real subsystem
Patterns gained the structure they needed to support a multi-user setup.
Targets now have:
- Type:
word(a literal substring the server compiles into a regex
with configurable affix anchors) orregex(a raw user-supplied regex
validated at create time). - Match scope:
fingerprint,pubkey, orboth— controls which key
representation the regex is tested against, both on the client during
search and on the server during attribution. - Case modes (for word targets): any combination of
insensitive,
sensitive, andcapitalized. The server compiles these into the
smallest set of regex groups that covers the requested behavior.
Match attribution moved server-side: clients report what they found, and
the server re-tests against active targets to assign the match to the right
target ID, so multi-target deployments end up with a clean per-target
match history. Deleting a target cascades to its matches, atomically, in
a single transaction.
Build, container, and CI
The Makefile is replaced by a justfile. Recipes are grouped (build,
test, run, container), just build cross-compiles to the four
release platforms (linux/amd64, linux/arm64, darwin/amd64,
darwin/arm64) with version metadata injected via -ldflags, and macOS
builds are ad-hoc codesigned when the host can do it.
Container publishing got a real package recipe that builds and pushes a
multi-arch image to ghcr.io/nugget/vanitykeygen with proper OCI labels,
and a release recipe that attaches a manifest to the matching GitHub
release. Docker compose files for both server and client landed alongside
new VKG_LISTEN_PORT / VKG_LISTEN_ADDRESS env vars so containerized
deployments can be configured without flag-juggling.
CI runs the full local gate (fmt-check, mod-tidy-check, vet,
golangci-lint, test, build-all) on every push and PR via a small
GitHub Actions workflow. The mod-tidy-check recipe in particular keeps
go.mod honest — drift between what the code actually imports and what's
declared will fail CI.
Security hardening (PR review pass)
A second pass after the initial overhaul addressed every issue the PR
review surfaced:
- No client-supplied server-owned fields.
handlePostMatchand
handleCreateTargetnow clearID,Timestamp,CreatedAt, and
TargetIDon input. Match attribution always runs server-side. Target
enums (type,match_scope,case_modes) are validated against
allowed sets. - PUT means replace.
handleUpdateTargetno longer field-merges with
the existing row, so an empty label is now a real clear and a missing
activeno longer silently flips it to false. The web UI was already
sending complete payloads; the merge logic was a footgun without a use
case. - SSE no longer leaks key material. The
matchbroadcast now sends a
vkg.MatchSummary(id, target/client/hostname, timestamp, match
metadata, public fingerprint) instead of the fullMatch. Private key
material is only retrievable viaGET /api/matches/{id}. - No CORS wildcard. The SSE handler dropped
Access-Control-Allow-Origin: *. Same-origin only. - Atomic cascade deletes with a 404 path when the target ID doesn't
exist, instead of silently returning 204 on missing IDs. - Web XSS closed. The dashboard replaced every inline
onclick=
handler with delegatedaddEventListenerlisteners anddata-*
attributes, so untrusted IDs can no longer execute as script even if
one ever made it into the DB. - Client honesty. All four client HTTP calls (
fetchTargets,
register,sendHeartbeat,reportMatch) now checkresp.StatusCode
and surface non-2xx responses as wrapped errors. Per-seekerkeyCount
accounting is fixed so the matching key is included and the post-match
reset doesn't silently bump the count back to one.
Project surfaces
The repo gained a real contributor guide. AGENTS.md documents build,
code conventions, architecture, security, and PR workflow at the project
level; CLAUDE.md is a thin Claude-specific operator layer that points to
it. The README itself was rewritten with an architecture diagram, the full
API reference, server/client flag tables, and container/release recipes.
Full Changelog
Streamlined API
v0.2.0 is where vanitykeygen starts to look like a real two-binary system
instead of a server with a satellite. The wire-level types live in their own
package, the HTTP layer picks up a real router, and the build pipeline gets
the polish it needs to actually ship containers.
9 files changed, 266 insertions, 93 deletions.
A shared schema package
The new pkg/vkg package is the single source of truth for the types that
flow over the wire — Key, Match, Target. Previously the server defined
them in its own package and the client carried near-duplicate copies; any
field shift had to be edited in lockstep on both sides, and JSON tags drifted
easily. With pkg/vkg, the client now imports the same struct the server
marshals, so the schema is enforced by the type system at build time.
This is a small change in line count (~30 lines moved) but a meaningful one
for the project's shape: it's the first time the codebase admits that the
client and server are peers sharing a contract, not a server with an embedded
client.
Gin for the HTTP server
The server's HTTP handlers move from raw net/http.HandlerFunc to
Gin (#2). A new pkg/server/api.go introduces
setupRouter, and the existing match-post / target-fetch handlers are
rewritten as gin.HandlerFunc. pkg/server/main.go loses about 80 lines of
manual JSON decode/encode and status-code plumbing in the process. Routes
themselves don't change shape, so existing clients keep working without any
API adjustments.
Build & deploy cleanup
The Makefile gets a thorough sweep so containers can actually ship. Separated
buildserver / buildclient targets each emit their own image under
nugget/vkg-server and nugget/vkg-client, OCI labels now carry version
metadata derived from git describe --always --long --tags --dirty, and a
new vkgstatic target produces a CGO-free binary suitable for the client
image. Local-development affordances runserver and runclient make the
inner-loop match the production layout, and editor scratch files are
properly .gitignored.