GREEN · RED · BLUE PACKAGE SKILL · VULTR · AUTOMQ
AutoMQ Package Skill
A reproducible workflow for a three-node AutoMQ cluster on Vultr — the Kafka 3.9 wire protocol over brokers whose storage tier is object storage rather than replicated disks. Shipped in Clojure, TypeScript and Python, rendering the same bytes from one colors.yml.
Quick start
npx skills add getcolors/automq
cp .agents/skills/package-automq-green/green ./green
chmod +x green
./green build
./green create --dry-run
Build renders reproducible output and dry-run walks the workflow without provider side effects. Real creation and deletion require explicit authorization.
Substitute package-automq-red/red or package-automq-blue/blue for the same verbs on Bun or uv. The three colours render byte-identical artifacts, and scripts/parity.sh is what proves it on every change.
Architecture
Compute
Three Vultr instances in one VPC, and a firewall that opens 22 and 9092 — nothing else. The controller quorum and inter-broker replication never touch the public interface.
Storage tier
Two R2 buckets, adopted and never created. AutoMQ writes hash-prefixed keys at the bucket root and supports no path prefix, so each bucket belongs to one cluster outright — proven empty before it is claimed.
Identity
Four principals, no anonymous access anywhere. The controller listener uses PLAIN from a static JAAS entry; inter-broker and public listeners use SCRAM bootstrapped into the metadata log at genesis.
Acceptance
Six gates on the hosts and seven from the workstation, including a targeted failover: the partition is chosen because node 2 leads it, so the test cannot pass without exercising the broker it killed.
Connecting
The bootstrap name carries one A record per node; each broker advertises its own name, and one Let’s Encrypt certificate covers all of them.
kcat -b <automq-host>:9092 \
-X security.protocol=SASL_SSL \
-X sasl.mechanism=SCRAM-SHA-512 \
-X sasl.username=automq \
-X sasl.password=<from automq-credential> -L
The password is generated on the server and printed by automq-credential, a root-only command deliberately separate from automq-status — the command an operator runs a hundred times must not put a credential into scrollback.
Authentication is not authorization
Port 9092 faces the internet, so SCRAM alone is not the boundary. The cluster runs StandardAuthorizer with allow.everyone.if.no.acl.found=false, and the public client principal holds prefixed Describe/Read/Write on topics and Describe/Read on groups — no Create, no Alter, no ClusterAction. Acceptance asserts the denials, not only the grants.
One certificate issuer
Node 0 alone runs ACME and is the only host that receives a Cloudflare token, publishing the result to the ops bucket for the others to pull. Three nodes issuing independently would race on the shared _acme-challenge record for the bootstrap name — each deleting the others’ proof — and would put a zone-editing credential on every publicly reachable broker. Restarts are ordered by a conditional-create lease in object storage, because a node checking its own quorum health cannot order actors it cannot see.
Exposure
| Port | Exposure | Purpose |
|---|---|---|
| 22 | vultr-ssh-sources | Key-only SSH: convergence and recovery |
| 9092 | vultr-kafka-sources | The public Kafka endpoint: SASL_SSL, SCRAM-SHA-512, ACL-gated |
| 9093 | VPC | KRaft controller quorum |
| 9094 | VPC | Inter-broker traffic |
delete never empties the buckets. They hold the cluster’s data, and a teardown that silently erased them would make an accidental delete unrecoverable. Purging is a separate, deliberate action.Source
github.com/getcolors/automq · reference deployment automq-vultr · getcolors.ai