S06-S08: Python spec, Go in-process leaf, tests and Forgejo CI
Some checks are pending
ci / python (push) Waiting to run
ci / go (push) Waiting to run

This commit is contained in:
George Lambert 2026-09-15 22:17:19 -04:00
commit d67509e470
16 changed files with 726 additions and 0 deletions

26
go/cmd/sm-leaf/main.go Normal file
View file

@ -0,0 +1,26 @@
// sm-leaf: in-process NATS core plus optional leaf to the Verae hub.
package main
import (
"flag"
"log"
"os"
"os/signal"
"syscall"
"github.com/marchon/secure-messaging/internal/leaf"
)
func main() {
hub := flag.String("hub", os.Getenv("SM_LEAF_HUB"), "leaf hub URL, e.g. nats-leaf://10.10.10.21:7422")
flag.Parse()
n, err := leaf.Start(*hub)
if err != nil {
log.Fatal(err)
}
log.Printf("sm-leaf in-process nats %s hub=%q", n.Addr(), *hub)
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
_ = n.Shutdown()
}

19
go/go.mod Normal file
View file

@ -0,0 +1,19 @@
module github.com/marchon/secure-messaging
go 1.22
require (
github.com/nats-io/nats-server/v2 v2.10.24
github.com/nats-io/nats.go v1.38.0
)
require (
github.com/klauspost/compress v1.17.11 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
github.com/nats-io/jwt/v2 v2.7.3 // indirect
github.com/nats-io/nkeys v0.4.9 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
golang.org/x/crypto v0.31.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/time v0.8.0 // indirect
)

21
go/go.sum Normal file
View file

@ -0,0 +1,21 @@
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
github.com/nats-io/jwt/v2 v2.7.3 h1:6bNPK+FXgBeAqdj4cYQ0F8ViHRbi7woQLq4W29nUAzE=
github.com/nats-io/jwt/v2 v2.7.3/go.mod h1:GvkcbHhKquj3pkioy5put1wvPxs78UlZ7D/pY+BgZk4=
github.com/nats-io/nats-server/v2 v2.10.24 h1:KcqqQAD0ZZcG4yLxtvSFJY7CYKVYlnlWoAiVZ6i/IY4=
github.com/nats-io/nats-server/v2 v2.10.24/go.mod h1:olvKt8E5ZlnjyqBGbAXtxvSQKsPodISK5Eo/euIta4s=
github.com/nats-io/nats.go v1.38.0 h1:A7P+g7Wjp4/NWqDOOP/K6hfhr54DvdDQUznt5JFg9XA=
github.com/nats-io/nats.go v1.38.0/go.mod h1:IGUM++TwokGnXPs82/wCuiHS02/aKrdYUQkU8If6yjw=
github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0=
github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE=
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=

66
go/internal/leaf/leaf.go Normal file
View file

@ -0,0 +1,66 @@
// Package leaf starts an in-process nats-server and optional hub leaf.
package leaf
import (
"errors"
"net/url"
"time"
natsserver "github.com/nats-io/nats-server/v2/server"
nats "github.com/nats-io/nats.go"
)
// Node is an in-process broker plus client.
type Node struct {
ns *natsserver.Server
nc *nats.Conn
}
// Start binds 127.0.0.1:0 and optionally leaf-connects to hub.
func Start(hub string) (*Node, error) {
opts := &natsserver.Options{Host: "127.0.0.1", Port: -1}
if hub != "" {
u, err := url.Parse(hub)
if err != nil {
return nil, err
}
opts.LeafNode.Remotes = []*natsserver.RemoteLeafOpts{{URLs: []*url.URL{u}}}
}
ns, err := natsserver.NewServer(opts)
if err != nil {
return nil, err
}
go ns.Start()
if !ns.ReadyForConnections(5 * time.Second) {
return nil, errors.New("nats not ready")
}
nc, err := nats.Connect(ns.ClientURL())
if err != nil {
ns.Shutdown()
return nil, err
}
n := &Node{ns: ns, nc: nc}
if _, err := n.nc.Subscribe("verae.sm.send", func(msg *nats.Msg) {
if msg.Reply != "" {
_ = n.nc.Publish(msg.Reply, []byte(`{"accepted":true}`))
}
}); err != nil {
n.Shutdown()
return nil, err
}
return n, nil
}
// Addr is the in-process client URL.
func (n *Node) Addr() string { return n.ns.ClientURL() }
// Shutdown stops client and server.
func (n *Node) Shutdown() error {
if n.nc != nil {
n.nc.Close()
}
if n.ns != nil {
n.ns.Shutdown()
}
return nil
}

View file

@ -0,0 +1,14 @@
package leaf
import "testing"
func TestStartInProcess(t *testing.T) {
n, err := Start("")
if err != nil {
t.Fatal(err)
}
defer n.Shutdown()
if n.Addr() == "" {
t.Fatal("empty addr")
}
}