feat(membershipd): open JetStream for the embedded node + wire it into the server
The control plane previously opened a privileged JetStream client only when
clustered or running --store kv (needJS). It now also opens one for a standalone
single-node embedded deployment (openJS = needJS || embedded), because the
embedded NATS always ships JetStream and the server needs it to own persisted
rooms' durable streams (ensure on create + serve GET /rooms/{id}/history). An
external NATS without a cluster/KV feature is unchanged (no JetStream; history
degrades to empty).
The internal service identity is generated under the same broadened condition so
the in-process JetStream connection authenticates under enforce. After NewServer
the js context is wired via SetJetStream with the control-plane KV replication
factor, so a persisted room's history is as available as its metadata.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+21
-3
@@ -150,6 +150,16 @@ func main() {
|
||||
decentralized := *storeBackend == "kv"
|
||||
needJS := clustered || decentralized
|
||||
enforce := authMode == membership.AuthEnforce
|
||||
embedded := *natsURL == ""
|
||||
// The control plane also needs a privileged JetStream client to OWN the durable
|
||||
// per-room streams of persisted rooms (ensure the stream on room creation so the
|
||||
// subject is captured from the first message — even from a JetStream-less browser
|
||||
// client — and read it back for GET /rooms/{id}/history). The embedded NATS
|
||||
// always ships JetStream, so open the client whenever we run embedded, even for a
|
||||
// standalone SQLite node. For an EXTERNAL NATS we only reach for JetStream when a
|
||||
// cluster/KV feature explicitly requires it (unchanged), so an operator-managed
|
||||
// external deployment without those features behaves exactly as before.
|
||||
openJS := needJS || embedded
|
||||
|
||||
// Internal service identity (issue 0006a): when the embedded data plane enforces
|
||||
// auth, membershipd must still connect to its OWN server to manage JetStream.
|
||||
@@ -159,7 +169,7 @@ func main() {
|
||||
// the server is embedded), so a standalone or non-enforce node is unchanged.
|
||||
var internalID cs.Identity
|
||||
var internalPubHex string
|
||||
if needJS && enforce && *natsURL == "" {
|
||||
if openJS && enforce && embedded {
|
||||
if *internalIDFile != "" {
|
||||
// Persisted identity: load it, generating + writing it (0600) on first
|
||||
// start. A stable internal key is what `user add --store kv` presents to
|
||||
@@ -316,9 +326,9 @@ func main() {
|
||||
// only client that can connect in this window (the holder still denies everyone
|
||||
// else; the internal identity bypasses the store).
|
||||
var js jetstream.JetStream
|
||||
if needJS {
|
||||
if openJS {
|
||||
var internalNC *nats.Conn
|
||||
if *natsURL == "" {
|
||||
if embedded {
|
||||
internalNC, js, err = connectInternalJS(ns, internalID, enforce)
|
||||
} else {
|
||||
internalNC, js, err = connectExternalJS(natsClientURL, *caFile)
|
||||
@@ -340,6 +350,14 @@ func main() {
|
||||
}
|
||||
|
||||
srv := membership.NewServer(store, blobs, authMode)
|
||||
// Wire the privileged JetStream context so the control plane owns persisted
|
||||
// rooms' durable streams (ensure on create + serve GET /rooms/{id}/history). The
|
||||
// stream replication factor matches the control-plane KV replication so a room's
|
||||
// history is as available as its metadata. js is nil only for an external NATS
|
||||
// without a cluster/KV feature, where history degrades to empty (see openJS).
|
||||
if js != nil {
|
||||
srv.SetJetStream(js, *kvReplicas)
|
||||
}
|
||||
// On a public (non-loopback) bind, disable cleartext rooms: the embedded NATS
|
||||
// has no per-subject ACL, so cleartext content would be readable by any
|
||||
// registered peer. Forcing E2E keeps message content confidential regardless
|
||||
|
||||
Reference in New Issue
Block a user