zeekayandHanzo Dev 55fa04e348 Build on Go 1.27
It builds and ships with cgo off, in the Makefile and in the Dockerfile, and
that is how this was verified: build, vet and tests clean on 1.27.1.

With cgo ON it panics at init — two dependencies both register a driver named
"sqlite" — which is a defect in the dependency graph rather than anything the
toolchain moved, and it is invisible to every build this repo actually makes.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 01:42:59 -07:00
2026-09-17 00:33:51 -07:00
2026-09-17 00:33:51 -07:00
2026-09-17 01:42:59 -07:00
2026-09-17 01:42:59 -07:00
2025-10-10 18:53:08 -03:00
2026-09-03 17:46:03 -07:00
2026-09-17 01:42:59 -07:00
2026-09-17 01:42:59 -07:00
2026-06-28 20:10:07 -07:00

base-ha

base-ha

Highly available Hanzo Base cluster.

Quasar consensus is leaderless — all nodes are equal validators. SQLite's single-writer constraint is satisfied by deterministic writer-pinning: the lowest-sorted alive NodeID holds the write lock. All others are replicas that 307 mutating requests to the writer and apply change-sets via async replication.

  • Writes (POST/PUT/PATCH/DELETE) reverse-proxy to the pinned writer.
  • Reads (GET) honor txseq cookies for read-your-writes consistency.
  • Replication is async; conflict resolution is last-writer-wins by default.
  • One binary. Single BASE_* env namespace.

Quick start

go install github.com/hanzoai/base-ha@latest

# node 1 (bootstrap)
BASE_NODE_ID=node1 \
BASE_LOCAL_TARGET=http://127.0.0.1:8090 \
BASE_PEERS=http://127.0.0.1:8091,http://127.0.0.1:8092 \
BASE_CONSENSUS=quasar \
BASE_SUPERUSER_EMAIL=[email protected] \
BASE_SUPERUSER_PASS=change-me \
  base-ha serve --http 127.0.0.1:8090

# node 2
BASE_NODE_ID=node2 \
BASE_LOCAL_TARGET=http://127.0.0.1:8091 \
BASE_PEERS=http://127.0.0.1:8090,http://127.0.0.1:8092 \
BASE_CONSENSUS=quasar \
  base-ha serve --http 127.0.0.1:8091

Or use Docker:

docker compose up

Configuration

Cluster identity

Var Description Default
BASE_NODE_ID Unique node identifier. $HOSTNAME
BASE_LOCAL_TARGET This node's reachable URL. —
BASE_PEERS CSV of peer base URLs. —

Writer election

Var Values Description
BASE_CONSENSUS quasar (default), pubsub quasar is the native heartbeat writer-pin. pubsub falls back to go-ha's graft dynamic election.
BASE_STATIC_WRITER URL All nodes forward writes to this URL. Overrides election.

Replication transport

Var Description
BASE_PUBSUB_URL External broker URL (nats://pubsub:4222). Works with nats-server or hanzoai/pubsub.
BASE_PUBSUB_PORT Embedded broker port (enables embedded mode).
BASE_PUBSUB_STORE_DIR Embedded broker storage dir.
BASE_PUBSUB_CONFIG Broker config file (overrides other pubsub settings).
BASE_REPLICATION_STREAM Stream name. Default: base.
BASE_REPLICAS JetStream replica count. Default: 1.
BASE_ASYNC_PUBLISHER Enable async publish with outbox. Recommended with external broker.
BASE_ASYNC_PUBLISHER_DIR Outbox directory.
BASE_ROW_IDENTIFY pk (default), rowid, or full.

Bootstrap

Var Description
BASE_SUPERUSER_EMAIL Superuser email at startup.
BASE_SUPERUSER_PASS Superuser password at startup.

Architecture

user
 │
 ▼
[replica]  POST/PUT/PATCH/DELETE → 307 → [writer]
[replica]  GET + txseq cookie           [writer or replica with fresh state]
                                             │
                                             │ SQLite change-set replication
                                             ▼
                                         [replicas]

Writer election (BASE_CONSENSUS=quasar, default):

  • Each node heartbeats to peers every 500ms via /_ha/heartbeat.
  • Alive peers ranked by NodeID (lexicographic). Lowest = writer.
  • Lease timeout = 3 × heartbeat (1.5s).
  • No RAFT log, no snapshots. All nodes are equal validators.

Fallback (BASE_CONSENSUS=pubsub):

  • Delegates to go-ha's graft-over-JetStream election.
  • Use when you already run a NATS/pubsub cluster.

Event hooks on replicas

Replicas fire these events on replicated changes:

  • OnModelAfterCreateSuccess, OnModelAfterCreateError
  • OnModelAfterUpdateSuccess, OnModelAfterUpdateError
  • OnModelAfterDeleteSuccess, OnModelAfterDeleteError

Before* hooks fire only on the writer.

Using Hanzo pubsub as the broker

hanzoai/pubsub is wire-compatible with NATS:

docker run --rm -p 4222:4222 ghcr.io/hanzoai/pubsub:latest -js -sd /data

BASE_PUBSUB_URL=nats://localhost:4222 base-ha serve --http :8090

License

MIT.

S
Description
Hanzo Base HA plugin
Readme MIT
1.7 MiB
Languages
Go 96.8%
Dockerfile 2.4%
Makefile 0.8%