1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
package main
import (
"flag"
"log"
"net"
"net/http"
"os"
"os/signal"
"syscall"
"context"
"connectrpc.com/connect"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/grpc"
pb "northwest.io/nostr-grpc/api/nostr/v1"
"northwest.io/nostr-grpc/api/nostr/v1/nostrv1connect"
connecthandler "northwest.io/nostr-grpc/internal/handler/connect"
grpchandler "northwest.io/nostr-grpc/internal/handler/grpc"
wshandler "northwest.io/nostr-grpc/internal/handler/websocket"
"northwest.io/nostr-grpc/internal/storage"
"northwest.io/nostr-grpc/internal/subscription"
)
func main() {
var (
grpcAddr = flag.String("grpc-addr", ":50051", "gRPC server address")
wsAddr = flag.String("ws-addr", ":8080", "WebSocket server address")
dbPath = flag.String("db", "relay.db", "SQLite database path")
publicURL = flag.String("public-url", "", "Public URL for relay (e.g., nostr-grpc.x.bdw.to)")
)
flag.Parse()
store, err := storage.New(*dbPath)
if err != nil {
log.Fatalf("failed to create storage: %v", err)
}
defer store.Close()
subManager := subscription.NewManager()
grpcHandler := grpchandler.NewServer(store)
grpcHandler.SetSubscriptionManager(subManager)
connectHandler := connecthandler.NewHandler(grpcHandler)
mux := http.NewServeMux()
path, handler := nostrv1connect.NewNostrRelayHandler(connectHandler, connect.WithInterceptors())
mux.Handle(path, handler)
wsHandler := wshandler.NewHandler(store, subManager)
// Set public URLs for index page
var grpcDisplay, httpDisplay, wsDisplay string
if *publicURL != "" {
// Use public URLs when behind reverse proxy (port 443)
grpcDisplay = *publicURL + ":443"
httpDisplay = "https://" + *publicURL
wsDisplay = "wss://" + *publicURL
} else {
// Use local addresses for development
grpcDisplay = *grpcAddr
httpDisplay = "http://" + *wsAddr
wsDisplay = "ws://" + *wsAddr
}
wsHandler.SetIndexData(grpcDisplay, httpDisplay, wsDisplay)
mux.Handle("/", wsHandler)
grpcLis, err := net.Listen("tcp", *grpcAddr)
if err != nil {
log.Fatalf("failed to listen on gRPC port: %v", err)
}
grpcServer := grpc.NewServer()
pb.RegisterNostrRelayServer(grpcServer, grpcHandler)
httpServer := &http.Server{
Addr: *wsAddr,
Handler: h2c.NewHandler(mux, &http2.Server{}),
}
log.Printf("gRPC server listening on %s", *grpcAddr)
log.Printf("HTTP server listening on %s", *wsAddr)
log.Printf(" - Connect (gRPC-Web) at %s/nostr.v1.NostrRelay/*", *wsAddr)
log.Printf(" - WebSocket (Nostr) at %s/", *wsAddr)
log.Printf("Database: %s", *dbPath)
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
log.Println("Shutting down...")
grpcServer.GracefulStop()
httpServer.Shutdown(context.Background())
}()
go func() {
if err := grpcServer.Serve(grpcLis); err != nil {
log.Fatalf("gRPC server failed: %v", err)
}
}()
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("WebSocket server failed: %v", err)
}
}
|