Go's GC doesn't respect cgroup memory limits on its own, risking OOMKill
under load; set GOMEMLIMIT to ~90% of each deployment's resources.limits.memory.
Also switch imagePullPolicy to Always so :latest tags aren't served stale
from node-local cache after a new push.
Building on arm64 Mac without GOOS/GOARCH produced amd64 node "exec format
error". The homelab CA was only trusted in the build stage, so the final
distroless runtime couldn't verify Authentik's TLS cert during OIDC discovery.
Cross-file `needs:` across separate nested helmfiles didn't resolve in
Helmfile v1 (releases defined in sibling files weren't visible to each
other's dependency graph) -- confirmed live against the homelab cluster:
kafka-cluster failed with "depend(s) on an undefined release" even though
strimzi-operator had just been installed successfully by a sibling file.
Collapsed releases.d/*.gotmpl into a single helmfile.yaml.gotmpl so the
whole release graph is resolved together.
Also add a second Ingress (management-service-grpc, backend-protocol: GRPC)
scoped to the QueueService gRPC path prefix on the same host/port as the
REST ingress. kmsvc-cli dials --server directly via gRPC (default
kmsvc.homelab.internal:443 per its README), so raw gRPC needs an external
path too, not just REST -- the original "gRPC stays internal" default
didn't account for the CLI's own connection model.
Add Dockerfile.queue-operator (existed for cmd/server only before).
Local charts: kafka-cluster (Strimzi Kafka+KafkaNodePool CRs, 3-replica KRaft
topology, 5Gi memory cap, Longhorn storage, pod anti-affinity), queue-crd
(Queue CRD + queue-operator Deployment/RBAC), management-service
(Deployment/Service/ConfigMap/HPA/Ingress, REST exposed externally via
cert-manager-issued TLS, raw gRPC kept cluster-internal per design.md §7a).
helmfile.yaml.gotmpl + releases.d/*.gotmpl wire strimzi-operator ->
kafka-cluster -> redis -> {queue-crd, management-service} via `needs:`.
Directory is releases.d, not helmfile.d as originally sketched in design.md
section 7b: Helmfile v1 treats a literal "helmfile.d" directory as a special
auto-discovery mode that conflicts with an explicit top-level helmfile.yaml.
Files use .gotmpl (required by Helmfile v1 for {{ }}-templated files) and
each declares its own environments: block, since nested helmfiles don't
inherit the parent's resolved values in this version.
Namespace is sqs throughout. environments/homelab.yaml carries no secrets.
Validated locally via helm lint/helm template (all 3 charts) and
`helmfile -e homelab build` (dependency ordering + value substitution) — no
cluster contact made. Live apply is a separate, explicitly-confirmed step.
Assembles tasks 1/6/7/8 into a runnable cmd/server binary: QueueServiceServer
handlers translating kafkamgmt.v1 proto to internal/core/queue's plain Go
types, a lazy per-queue Kafka consumer registry for ReceiveMessage, and a
Redis-scan-based queue discovery loop that starts a reaper goroutine per
queue (queue lifecycle isn't exposed over gRPC, so this is the server's only
signal). Promotes kmsvc-proto to a direct go.mod dependency.
Handler-level integration tests run against kfake+miniredis (same documented
tradeoff as tasks 5-7's envtest/testcontainers substitution), exercising
send->receive->delete through the real QueueServiceServer implementation.
Remove local proto/, buf.yaml, buf.gen.yaml, and generated
internal/api/v1/*.pb.go. The message-plane contract now lives in the
sibling repo kmsvc-proto (forgejo.riotpiao.homelab.com/rock/kmsvc-proto),
fetched via go get — no local buf/protoc plugin install needed.
Nothing in this repo imported internal/api/v1 yet, so this is a clean
swap with no call-site changes.
JWKS caching via lestrrat-go/jwx, signature/iss/aud/exp validation, and
a single gRPC interceptor that grpc-gateway's forwarded headers make
work identically for REST callers.
Per-queue ticker that scans vis_index for expired in-flight messages and
drives each through the atomic reap.lua check-and-act, routing maxed-out
messages to their queue's configured DLQ via the existing SendMessage
path. Safe to run from multiple replicas against the same queue.
Implements SendMessage (size cap, FIFO dedup, shard routing), ReceiveMessage
(long-poll loop across active+closing shards, FIFO per-group exclusivity
gating, redelivery hand-out), DeleteMessage (ack + low-watermark offset
advancement), and ChangeMessageVisibility against the Redis state layer
and a real Kafka producer/consumer.
Reconciles Queue CRs into Kafka topics + Redis shard-map/queue-meta state:
creates shard-0 on first reconcile, splits a shard's hash range into two
children once its split threshold is crossed, drains and closes a parent
shard once its consumer group has fully caught up and its retention
window has elapsed, and tears down every shard's topic + Redis state on
deletion. Includes the manager entrypoint (cmd/queue-operator) and RBAC.
Implements the kmsvc: key schema (inflight, pending/watermark keyed by
shard+partition, vis_index, dedup, fifo_lock, queue meta, shard map) plus
the atomic reap/ack Lua scripts used for safe multi-replica redelivery
and DLQ routing.
QueueSpec/QueueStatus model the shard-based (Kinesis-style hash-range
splitting) queue lifecycle object, with the controller-gen-generated
deepcopy and CRD YAML.
Adds queue_service.proto (SendMessage, SendMessageBatch, ReceiveMessage,
DeleteMessage, DeleteMessageBatch, ChangeMessageVisibility(Batch)) with
grpc-gateway REST annotations, plus the generated Go server/client and
gateway stubs.