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