feat: initial scaffold of unibus message bus (membership service + client lib + demo peers)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
// Command membershipd is the unibus control-plane service: room metadata,
|
||||
// member directory, sealed key distribution, and the media blob store. The data
|
||||
// plane is NATS — if --nats-url is empty it starts an embedded nats-server with
|
||||
// JetStream so the whole stack runs with `go run` and nothing to install.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
server "github.com/nats-io/nats-server/v2/server"
|
||||
|
||||
"github.com/enmanuel/unibus/pkg/blobstore"
|
||||
"github.com/enmanuel/unibus/pkg/embeddednats"
|
||||
"github.com/enmanuel/unibus/pkg/membership"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
natsURL = flag.String("nats-url", "", "external NATS url; empty starts an embedded server")
|
||||
httpPort = flag.String("http-port", "8420", "HTTP port for the control-plane API")
|
||||
dbPath = flag.String("db", "./local_files/unibus.db", "SQLite database path")
|
||||
storeDir = flag.String("store-dir", "./local_files/blobs", "blob store directory")
|
||||
natsPort = flag.Int("nats-port", 4222, "embedded NATS listen port (when --nats-url empty)")
|
||||
natsStore = flag.String("nats-store", "./local_files/jetstream", "embedded JetStream store dir")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
log.SetFlags(log.LstdFlags | log.Lmsgprefix)
|
||||
log.SetPrefix("[membershipd] ")
|
||||
|
||||
// Data plane: embedded or external NATS.
|
||||
var ns *server.Server
|
||||
natsClientURL := *natsURL
|
||||
if natsClientURL == "" {
|
||||
var err error
|
||||
ns, err = embeddednats.Start(*natsStore, *natsPort)
|
||||
if err != nil {
|
||||
log.Fatalf("start embedded nats: %v", err)
|
||||
}
|
||||
natsClientURL = embeddednats.ClientURL(ns)
|
||||
log.Printf("embedded NATS (JetStream) ready: %s", natsClientURL)
|
||||
} else {
|
||||
log.Printf("using external NATS: %s", natsClientURL)
|
||||
}
|
||||
|
||||
// Control plane: SQLite store + blob store + HTTP API.
|
||||
store, err := membership.Open(*dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("open membership store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
log.Printf("membership store: %s", *dbPath)
|
||||
|
||||
blobs, err := blobstore.New(*storeDir)
|
||||
if err != nil {
|
||||
log.Fatalf("open blob store: %v", err)
|
||||
}
|
||||
log.Printf("blob store: %s", *storeDir)
|
||||
|
||||
srv := membership.NewServer(store, blobs)
|
||||
addr := "127.0.0.1:" + *httpPort
|
||||
httpSrv := &http.Server{Addr: addr, Handler: srv}
|
||||
|
||||
go func() {
|
||||
log.Printf("HTTP control-plane API: http://%s", addr)
|
||||
log.Printf(" health: http://%s/healthz", addr)
|
||||
if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("http server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Graceful shutdown on SIGINT/SIGTERM.
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-stop
|
||||
log.Printf("shutting down...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
_ = httpSrv.Shutdown(ctx)
|
||||
if ns != nil {
|
||||
ns.Shutdown()
|
||||
ns.WaitForShutdown()
|
||||
}
|
||||
log.Printf("bye")
|
||||
}
|
||||
Reference in New Issue
Block a user