main.go raw
1 // orly-nits is a gRPC shim that manages a bitcoind (nits) process and exposes
2 // health/status information for the orly-launcher supervisor.
3 package main
4
5 import (
6 "context"
7 "net"
8 "os"
9 "os/signal"
10 "path/filepath"
11 "syscall"
12
13 "go-simpler.org/env"
14 "google.golang.org/grpc"
15 "next.orly.dev/pkg/lol"
16 "next.orly.dev/pkg/lol/chk"
17 "next.orly.dev/pkg/lol/log"
18
19 orlynitsv1 "next.orly.dev/pkg/proto/orlynits/v1"
20 )
21
22 // Config holds the orly-nits shim configuration.
23 type Config struct {
24 // Listen is the gRPC server listen address for the shim
25 Listen string `env:"ORLY_NITS_LISTEN" default:"127.0.0.1:50070" usage:"gRPC shim listen address"`
26
27 // Binary is the path to the bitcoind/nits binary
28 Binary string `env:"ORLY_NITS_BINARY" default:"nits" usage:"path to bitcoind/nits binary"`
29
30 // DataDir is the bitcoind data directory
31 DataDir string `env:"ORLY_NITS_DATA_DIR" usage:"bitcoind data directory"`
32
33 // RPCPort is the bitcoind JSON-RPC port
34 RPCPort int `env:"ORLY_NITS_RPC_PORT" default:"8332" usage:"bitcoind JSON-RPC port"`
35
36 // RPCUser is the bitcoind RPC username (if not using cookie auth)
37 RPCUser string `env:"ORLY_NITS_RPC_USER" usage:"bitcoind RPC username"`
38
39 // RPCPass is the bitcoind RPC password (if not using cookie auth)
40 RPCPass string `env:"ORLY_NITS_RPC_PASS" usage:"bitcoind RPC password"`
41
42 // Network is the bitcoin network
43 Network string `env:"ORLY_NITS_NETWORK" default:"mainnet" usage:"bitcoin network (mainnet, testnet, signet, regtest)"`
44
45 // PruneSize is the prune target in MB (0 = no pruning)
46 PruneSize int `env:"ORLY_NITS_PRUNE_MB" default:"2048" usage:"prune target in MB (0 = no pruning)"`
47
48 // LogLevel is the logging level
49 LogLevel string `env:"ORLY_NITS_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"`
50 }
51
52 func main() {
53 cfg := loadConfig()
54
55 lol.SetLogLevel(cfg.LogLevel)
56 log.I.F("orly-nits starting")
57
58 ctx, cancel := context.WithCancel(context.Background())
59 defer cancel()
60
61 // Handle shutdown signals
62 sigCh := make(chan os.Signal, 1)
63 signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
64 go func() {
65 sig := <-sigCh
66 log.I.F("received signal %v, shutting down", sig)
67 cancel()
68 }()
69
70 // Start bitcoind process
71 node := NewBitcoind(cfg)
72 if err := node.Start(ctx); err != nil {
73 log.E.F("failed to start bitcoind: %v", err)
74 os.Exit(1)
75 }
76 defer node.Stop()
77
78 // Start gRPC server
79 lis, err := net.Listen("tcp", cfg.Listen)
80 if chk.E(err) {
81 log.E.F("failed to listen on %s: %v", cfg.Listen, err)
82 os.Exit(1)
83 }
84
85 grpcServer := grpc.NewServer()
86 svc := NewNitsService(node)
87 orlynitsv1.RegisterNitsServiceServer(grpcServer, svc)
88
89 log.I.F("orly-nits gRPC server listening on %s", cfg.Listen)
90
91 // Serve until context is cancelled
92 go func() {
93 <-ctx.Done()
94 log.I.F("stopping gRPC server")
95 grpcServer.GracefulStop()
96 }()
97
98 if err := grpcServer.Serve(lis); err != nil {
99 log.E.F("gRPC server error: %v", err)
100 }
101
102 log.I.F("orly-nits stopped")
103 }
104
105 func loadConfig() *Config {
106 cfg := &Config{}
107 if err := env.Load(cfg, nil); chk.E(err) {
108 log.E.F("failed to load config: %v", err)
109 os.Exit(1)
110 }
111
112 if cfg.DataDir == "" {
113 home, err := os.UserHomeDir()
114 if chk.E(err) {
115 log.E.F("failed to get home directory: %v", err)
116 os.Exit(1)
117 }
118 cfg.DataDir = filepath.Join(home, ".local", "share", "orly", "nits")
119 }
120
121 if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) {
122 log.E.F("failed to create data directory %s: %v", cfg.DataDir, err)
123 os.Exit(1)
124 }
125
126 return cfg
127 }
128