summaryrefslogtreecommitdiffstats
path: root/cmd/relay/main.go
diff options
context:
space:
mode:
authorbndw <ben@bdw.to>2026-02-14 12:14:19 -0800
committerbndw <ben@bdw.to>2026-02-14 12:14:19 -0800
commitea4f508f5ee91b370c6912cde26b1a432380d037 (patch)
tree79081398bc0da1db76c28de6de04ed88a5e53bc3 /cmd/relay/main.go
parent4fc493e6d8cc20137f920f8647e39fc5051bb245 (diff)
feat: integrate config system into relay main.go
Add support for loading configuration from YAML file via -config flag. Wire up auth, rate limiting, and metrics interceptors based on config. Changes: - Add -config flag to relay command - Use config types directly in auth package (AuthOperationConfig) - Add conversion methods: RateLimitConfig.ToRateLimiter(), MetricsConfig.ToMetrics() - Add Metrics.Serve() method for prometheus HTTP endpoint - Update main.go to initialize interceptors from config - Fix type naming: OperationAuthConfig -> AuthOperationConfig for consistency Config now supports complete relay setup including auth read/write allowlists, rate limiting, and prometheus metrics.
Diffstat (limited to 'cmd/relay/main.go')
-rw-r--r--cmd/relay/main.go103
1 files changed, 75 insertions, 28 deletions
diff --git a/cmd/relay/main.go b/cmd/relay/main.go
index 3a1eeef..209d758 100644
--- a/cmd/relay/main.go
+++ b/cmd/relay/main.go
@@ -1,6 +1,7 @@
1package main 1package main
2 2
3import ( 3import (
4 "context"
4 "flag" 5 "flag"
5 "log" 6 "log"
6 "net" 7 "net"
@@ -9,8 +10,6 @@ import (
9 "os/signal" 10 "os/signal"
10 "syscall" 11 "syscall"
11 12
12 "context"
13
14 "connectrpc.com/connect" 13 "connectrpc.com/connect"
15 "golang.org/x/net/http2" 14 "golang.org/x/net/http2"
16 "golang.org/x/net/http2/h2c" 15 "golang.org/x/net/http2/h2c"
@@ -18,23 +17,27 @@ import (
18 17
19 pb "northwest.io/muxstr/api/nostr/v1" 18 pb "northwest.io/muxstr/api/nostr/v1"
20 "northwest.io/muxstr/api/nostr/v1/nostrv1connect" 19 "northwest.io/muxstr/api/nostr/v1/nostrv1connect"
20 "northwest.io/muxstr/internal/auth"
21 "northwest.io/muxstr/internal/config"
21 connecthandler "northwest.io/muxstr/internal/handler/connect" 22 connecthandler "northwest.io/muxstr/internal/handler/connect"
22 grpchandler "northwest.io/muxstr/internal/handler/grpc" 23 grpchandler "northwest.io/muxstr/internal/handler/grpc"
23 wshandler "northwest.io/muxstr/internal/handler/websocket" 24 wshandler "northwest.io/muxstr/internal/handler/websocket"
25 "northwest.io/muxstr/internal/metrics"
26 "northwest.io/muxstr/internal/ratelimit"
24 "northwest.io/muxstr/internal/storage" 27 "northwest.io/muxstr/internal/storage"
25 "northwest.io/muxstr/internal/subscription" 28 "northwest.io/muxstr/internal/subscription"
26) 29)
27 30
28func main() { 31func main() {
29 var ( 32 configFile := flag.String("config", "", "Path to config file (optional)")
30 grpcAddr = flag.String("grpc-addr", ":50051", "gRPC server address")
31 wsAddr = flag.String("ws-addr", ":8080", "WebSocket server address")
32 dbPath = flag.String("db", "relay.db", "SQLite database path")
33 publicURL = flag.String("public-url", "", "Public URL for relay (e.g., nostr-grpc.x.bdw.to)")
34 )
35 flag.Parse() 33 flag.Parse()
36 34
37 store, err := storage.New(*dbPath) 35 cfg, err := config.Load(*configFile)
36 if err != nil {
37 log.Fatalf("failed to load config: %v", err)
38 }
39
40 store, err := storage.New(cfg.Database.Path)
38 if err != nil { 41 if err != nil {
39 log.Fatalf("failed to create storage: %v", err) 42 log.Fatalf("failed to create storage: %v", err)
40 } 43 }
@@ -51,42 +54,86 @@ func main() {
51 path, handler := nostrv1connect.NewNostrRelayHandler(connectHandler, connect.WithInterceptors()) 54 path, handler := nostrv1connect.NewNostrRelayHandler(connectHandler, connect.WithInterceptors())
52 mux.Handle(path, handler) 55 mux.Handle(path, handler)
53 56
57 var serverOpts []grpc.ServerOption
58
59 if cfg.Auth.Read.Enabled || cfg.Auth.Write.Enabled {
60 authOpts := &auth.InterceptorOptions{
61 Read: cfg.Auth.Read,
62 Write: cfg.Auth.Write,
63 TimestampWindow: cfg.Auth.TimestampWindow,
64 SkipMethods: cfg.Auth.SkipMethods,
65 }
66 serverOpts = append(serverOpts,
67 grpc.UnaryInterceptor(auth.NostrUnaryInterceptor(authOpts)),
68 grpc.StreamInterceptor(auth.NostrStreamInterceptor(authOpts)),
69 )
70 }
71
72 if cfg.RateLimit.Enabled {
73 limiter := ratelimit.New(cfg.RateLimit.ToRateLimiter())
74 serverOpts = append(serverOpts,
75 grpc.ChainUnaryInterceptor(ratelimit.UnaryInterceptor(limiter)),
76 grpc.ChainStreamInterceptor(ratelimit.StreamInterceptor(limiter)),
77 )
78 }
79
80 var m *metrics.Metrics
81 if cfg.Metrics.Enabled {
82 m = metrics.New(cfg.Metrics.ToMetrics())
83 serverOpts = append(serverOpts,
84 grpc.ChainUnaryInterceptor(metrics.UnaryServerInterceptor(m)),
85 grpc.ChainStreamInterceptor(metrics.StreamServerInterceptor(m)),
86 )
87
88 go func() {
89 log.Printf("Metrics server listening on %s%s", cfg.Metrics.Addr, cfg.Metrics.Path)
90 if err := m.Serve(cfg.Metrics.Addr, cfg.Metrics.Path); err != nil {
91 log.Printf("Metrics server failed: %v", err)
92 }
93 }()
94 }
95
54 wsHandler := wshandler.NewHandler(store, subManager) 96 wsHandler := wshandler.NewHandler(store, subManager)
55 97
56 // Set public URLs for index page
57 var grpcDisplay, httpDisplay, wsDisplay string 98 var grpcDisplay, httpDisplay, wsDisplay string
58 if *publicURL != "" { 99 if cfg.Server.PublicURL != "" {
59 // Use public URLs when behind reverse proxy (port 443) 100 grpcDisplay = cfg.Server.PublicURL + ":443"
60 grpcDisplay = *publicURL + ":443" 101 httpDisplay = "https://" + cfg.Server.PublicURL
61 httpDisplay = "https://" + *publicURL 102 wsDisplay = "wss://" + cfg.Server.PublicURL
62 wsDisplay = "wss://" + *publicURL
63 } else { 103 } else {
64 // Use local addresses for development 104 grpcDisplay = cfg.Server.GrpcAddr
65 grpcDisplay = *grpcAddr 105 httpDisplay = "http://" + cfg.Server.HttpAddr
66 httpDisplay = "http://" + *wsAddr 106 wsDisplay = "ws://" + cfg.Server.HttpAddr
67 wsDisplay = "ws://" + *wsAddr
68 } 107 }
69 wsHandler.SetIndexData(grpcDisplay, httpDisplay, wsDisplay) 108 wsHandler.SetIndexData(grpcDisplay, httpDisplay, wsDisplay)
70 mux.Handle("/", wsHandler) 109 mux.Handle("/", wsHandler)
71 110
72 grpcLis, err := net.Listen("tcp", *grpcAddr) 111 grpcLis, err := net.Listen("tcp", cfg.Server.GrpcAddr)
73 if err != nil { 112 if err != nil {
74 log.Fatalf("failed to listen on gRPC port: %v", err) 113 log.Fatalf("failed to listen on gRPC port: %v", err)
75 } 114 }
76 115
77 grpcServer := grpc.NewServer() 116 grpcServer := grpc.NewServer(serverOpts...)
78 pb.RegisterNostrRelayServer(grpcServer, grpcHandler) 117 pb.RegisterNostrRelayServer(grpcServer, grpcHandler)
79 118
80 httpServer := &http.Server{ 119 httpServer := &http.Server{
81 Addr: *wsAddr, 120 Addr: cfg.Server.HttpAddr,
82 Handler: h2c.NewHandler(mux, &http2.Server{}), 121 Handler: h2c.NewHandler(mux, &http2.Server{}),
122 ReadTimeout: cfg.Server.ReadTimeout,
123 WriteTimeout: cfg.Server.WriteTimeout,
83 } 124 }
84 125
85 log.Printf("gRPC server listening on %s", *grpcAddr) 126 log.Printf("gRPC server listening on %s", cfg.Server.GrpcAddr)
86 log.Printf("HTTP server listening on %s", *wsAddr) 127 log.Printf("HTTP server listening on %s", cfg.Server.HttpAddr)
87 log.Printf(" - Connect (gRPC-Web) at %s/nostr.v1.NostrRelay/*", *wsAddr) 128 log.Printf(" - Connect (gRPC-Web) at %s/nostr.v1.NostrRelay/*", cfg.Server.HttpAddr)
88 log.Printf(" - WebSocket (Nostr) at %s/", *wsAddr) 129 log.Printf(" - WebSocket (Nostr) at %s/", cfg.Server.HttpAddr)
89 log.Printf("Database: %s", *dbPath) 130 log.Printf("Database: %s", cfg.Database.Path)
131 if cfg.Auth.Read.Enabled || cfg.Auth.Write.Enabled {
132 log.Printf("Auth: enabled (read=%v write=%v)", cfg.Auth.Read.Enabled, cfg.Auth.Write.Enabled)
133 }
134 if cfg.RateLimit.Enabled {
135 log.Printf("Rate limiting: enabled")
136 }
90 137
91 sigChan := make(chan os.Signal, 1) 138 sigChan := make(chan os.Signal, 1)
92 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) 139 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)