6d3d6d2562
Collapses Start/StartHost/StartHostAuth onto StartServer(ServerConfig) so auth and a TLS config can be set without growing the parameter list further. When TLS is set the server presents the certificate and requires TLS on the data plane; the wrappers preserve the existing no-auth/no-TLS behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.8 KiB
Go
100 lines
3.8 KiB
Go
// Package embeddednats starts an in-process NATS server with JetStream enabled.
|
|
//
|
|
// This lets the whole unibus stack run with `go run` without installing or
|
|
// managing a separate NATS deployment. In production, point clients at an
|
|
// external NATS via the --nats-url flag instead of using this.
|
|
package embeddednats
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"fmt"
|
|
"time"
|
|
|
|
server "github.com/nats-io/nats-server/v2/server"
|
|
)
|
|
|
|
// ServerConfig is the full set of knobs for the embedded NATS server. The zero
|
|
// value (empty StoreDir aside) yields a dev-friendly server: JetStream on, bound
|
|
// to all interfaces, no client auth, no TLS. Secured deployments set Auth and
|
|
// TLS; tests set Host to loopback and a free Port.
|
|
type ServerConfig struct {
|
|
StoreDir string // JetStream store directory
|
|
Host string // bind interface; "" = nats-server default ("0.0.0.0")
|
|
Port int // listen port
|
|
// Auth, when non-nil, is installed as CustomClientAuthentication so the data
|
|
// plane only accepts approved clients (nkey signature + bus allowlist).
|
|
Auth server.Authentication
|
|
// TLS, when non-nil, makes the server present a certificate and require TLS
|
|
// on the data plane. Clients must trust the issuing CA (see busauth).
|
|
TLS *tls.Config
|
|
}
|
|
|
|
// Start is a thin backward-compatible wrapper: embedded JetStream server on the
|
|
// default interface, no auth, no TLS.
|
|
func Start(storeDir string, port int) (*server.Server, error) {
|
|
return StartServer(ServerConfig{StoreDir: storeDir, Port: port})
|
|
}
|
|
|
|
// StartHost is Start with explicit control over the bind interface. host selects
|
|
// which network interface the data plane listens on: pass "127.0.0.1" to keep
|
|
// NATS loopback-only (the safe default for a single-host dev stack) or "0.0.0.0"
|
|
// to expose it to the LAN so remote peers (phones, other PCs) can connect. An
|
|
// empty host falls back to the nats-server default ("0.0.0.0", all interfaces).
|
|
func StartHost(storeDir, host string, port int) (*server.Server, error) {
|
|
return StartServer(ServerConfig{StoreDir: storeDir, Host: host, Port: port})
|
|
}
|
|
|
|
// StartHostAuth is StartHost with an optional custom client authenticator. When
|
|
// auth is non-nil only clients the authenticator approves may connect; when nil
|
|
// the server accepts any client (legacy, network-trusted behavior).
|
|
func StartHostAuth(storeDir, host string, port int, auth server.Authentication) (*server.Server, error) {
|
|
return StartServer(ServerConfig{StoreDir: storeDir, Host: host, Port: port, Auth: auth})
|
|
}
|
|
|
|
// StartServer launches an embedded nats-server with JetStream from cfg. It
|
|
// blocks until the server is ready to accept connections (up to 5s) and returns
|
|
// the running server; the caller must Shutdown it.
|
|
func StartServer(cfg ServerConfig) (*server.Server, error) {
|
|
opts := &server.Options{
|
|
JetStream: true,
|
|
StoreDir: cfg.StoreDir,
|
|
Host: cfg.Host,
|
|
Port: cfg.Port,
|
|
DontListen: false,
|
|
// Keep the embedded server quiet by default; the host app logs the URLs.
|
|
NoLog: true,
|
|
NoSigs: true,
|
|
}
|
|
if cfg.Auth != nil {
|
|
opts.CustomClientAuthentication = cfg.Auth
|
|
// A CustomClientAuthentication alone does not make the server advertise a
|
|
// nonce in its INFO line, and nats.go refuses to connect with an nkey to a
|
|
// server that does not ("nkeys not supported by the server"). Forcing the
|
|
// nonce makes nkey clients sign the challenge our authenticator verifies.
|
|
opts.AlwaysEnableNonce = true
|
|
}
|
|
if cfg.TLS != nil {
|
|
opts.TLSConfig = cfg.TLS
|
|
opts.TLS = true
|
|
}
|
|
|
|
ns, err := server.NewServer(opts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("embeddednats: new server: %w", err)
|
|
}
|
|
|
|
go ns.Start()
|
|
|
|
if !ns.ReadyForConnections(5 * time.Second) {
|
|
ns.Shutdown()
|
|
return nil, fmt.Errorf("embeddednats: server not ready for connections within 5s")
|
|
}
|
|
|
|
return ns, nil
|
|
}
|
|
|
|
// ClientURL returns a NATS connection URL for the running embedded server.
|
|
func ClientURL(ns *server.Server) string {
|
|
return ns.ClientURL()
|
|
}
|