main.go raw

   1  // orly-acl is a standalone gRPC ACL server for the ORLY relay.
   2  // It wraps the ACL implementations and exposes them via gRPC.
   3  package main
   4  
   5  import (
   6  	"context"
   7  	"net"
   8  	"os"
   9  	"os/signal"
  10  	"syscall"
  11  	"time"
  12  
  13  	"google.golang.org/grpc"
  14  	"google.golang.org/grpc/reflection"
  15  	"next.orly.dev/pkg/lol"
  16  	"next.orly.dev/pkg/lol/chk"
  17  	"next.orly.dev/pkg/lol/log"
  18  
  19  	"next.orly.dev/app/config"
  20  	"next.orly.dev/pkg/acl"
  21  	"next.orly.dev/pkg/database"
  22  	databasegrpc "next.orly.dev/pkg/database/grpc"
  23  	orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1"
  24  )
  25  
  26  func main() {
  27  	cfg := loadConfig()
  28  
  29  	// Set log level
  30  	lol.SetLogLevel(cfg.LogLevel)
  31  	log.I.F("orly-acl starting with log level: %s, mode: %s", cfg.LogLevel, cfg.ACLMode)
  32  
  33  	ctx, cancel := context.WithCancel(context.Background())
  34  	defer cancel()
  35  
  36  	// Initialize database (direct Badger or gRPC client)
  37  	var db database.Database
  38  	var err error
  39  
  40  	if cfg.DBType == "grpc" {
  41  		// Use gRPC database client
  42  		log.I.F("connecting to gRPC database server at %s", cfg.GRPCDBServer)
  43  		dbClient, err := databasegrpc.New(ctx, &databasegrpc.ClientConfig{
  44  			ServerAddress:  cfg.GRPCDBServer,
  45  			ConnectTimeout: 30 * time.Second,
  46  		})
  47  		if chk.E(err) {
  48  			log.E.F("failed to connect to gRPC database: %v", err)
  49  			os.Exit(1)
  50  		}
  51  		db = dbClient
  52  	} else {
  53  		// Use direct Badger database
  54  		dbCfg := &database.DatabaseConfig{
  55  			DataDir:             cfg.DataDir,
  56  			LogLevel:            cfg.LogLevel,
  57  			BlockCacheMB:        cfg.BlockCacheMB,
  58  			IndexCacheMB:        cfg.IndexCacheMB,
  59  			QueryCacheSizeMB:    cfg.QueryCacheSizeMB,
  60  			QueryCacheMaxAge:    cfg.QueryCacheMaxAge,
  61  			QueryCacheDisabled:  cfg.QueryCacheDisabled,
  62  			SerialCachePubkeys:  cfg.SerialCachePubkeys,
  63  			SerialCacheEventIds: cfg.SerialCacheEventIds,
  64  			ZSTDLevel:           cfg.ZSTDLevel,
  65  		}
  66  
  67  		log.I.F("initializing Badger database at %s", cfg.DataDir)
  68  		db, err = database.NewWithConfig(ctx, cancel, dbCfg)
  69  		if chk.E(err) {
  70  			log.E.F("failed to initialize database: %v", err)
  71  			os.Exit(1)
  72  		}
  73  	}
  74  
  75  	// Wait for database to be ready
  76  	log.I.F("waiting for database to be ready...")
  77  	<-db.Ready()
  78  	log.I.F("database ready")
  79  
  80  	// Create gRPC server EARLY so launcher can detect it's alive
  81  	grpcServer := grpc.NewServer(
  82  		grpc.MaxRecvMsgSize(16<<20), // 16MB
  83  		grpc.MaxSendMsgSize(16<<20), // 16MB
  84  	)
  85  
  86  	// Register ACL service (will be configured below)
  87  	service := NewACLService(cfg, db)
  88  	orlyaclv1.RegisterACLServiceServer(grpcServer, service)
  89  
  90  	// Register reflection for debugging with grpcurl
  91  	reflection.Register(grpcServer)
  92  
  93  	// Start listening immediately so launcher knows we're alive
  94  	lis, err := net.Listen("tcp", cfg.Listen)
  95  	if chk.E(err) {
  96  		log.E.F("failed to listen on %s: %v", cfg.Listen, err)
  97  		os.Exit(1)
  98  	}
  99  	log.I.F("gRPC ACL server listening on %s", cfg.Listen)
 100  
 101  	// Start serving in background while we configure
 102  	go func() {
 103  		if err := grpcServer.Serve(lis); err != nil {
 104  			log.E.F("gRPC server error: %v", err)
 105  		}
 106  	}()
 107  
 108  	// Create app config for ACL configuration
 109  	appCfg := &config.C{
 110  		Owners:                  cfg.GetOwners(),
 111  		Admins:                  cfg.GetAdmins(),
 112  		BootstrapRelays:         cfg.GetBootstrapRelays(),
 113  		RelayAddresses:          cfg.GetRelayAddresses(),
 114  		FollowListFrequency:     cfg.FollowListFrequency,
 115  		FollowsThrottleEnabled:  cfg.FollowsThrottleEnabled,
 116  		FollowsThrottlePerEvent: cfg.FollowsThrottlePerEvent,
 117  		FollowsThrottleMaxDelay: cfg.FollowsThrottleMaxDelay,
 118  	}
 119  
 120  	// Set ACL mode first
 121  	acl.Registry.SetMode(cfg.ACLMode)
 122  
 123  	// Mark service as ready IMMEDIATELY so relay can start
 124  	// Configure runs in background and loads follow lists asynchronously
 125  	service.SetReady(true)
 126  	log.I.F("ACL service ready (follow lists loading in background)")
 127  
 128  	// Run Configure in background (may take time to load follow lists)
 129  	go func() {
 130  		if err := acl.Registry.Configure(appCfg, db, ctx); chk.E(err) {
 131  			log.E.F("failed to configure ACL: %v", err)
 132  			// Don't exit - service can still function with limited ACL
 133  		}
 134  		log.I.F("ACL configuration complete")
 135  		// Start the syncer goroutine for background operations
 136  		acl.Registry.Syncer()
 137  		log.I.F("ACL syncer started for mode: %s", cfg.ACLMode)
 138  	}()
 139  
 140  	// Handle graceful shutdown - block until signal received
 141  	sigs := make(chan os.Signal, 1)
 142  	signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
 143  	sig := <-sigs
 144  	log.I.F("received signal %v, shutting down...", sig)
 145  
 146  	// Cancel context to stop all operations
 147  	cancel()
 148  
 149  	// Gracefully stop gRPC server with timeout
 150  	stopped := make(chan struct{})
 151  	go func() {
 152  		grpcServer.GracefulStop()
 153  		close(stopped)
 154  	}()
 155  
 156  	select {
 157  	case <-stopped:
 158  		log.I.F("gRPC server stopped gracefully")
 159  	case <-time.After(5 * time.Second):
 160  		log.W.F("gRPC graceful stop timed out, forcing stop")
 161  		grpcServer.Stop()
 162  	}
 163  
 164  	// Sync and close database (only for direct Badger)
 165  	if cfg.DBType != "grpc" {
 166  		log.I.F("syncing database...")
 167  		if err := db.Sync(); chk.E(err) {
 168  			log.W.F("failed to sync database: %v", err)
 169  		}
 170  		log.I.F("closing database...")
 171  		if err := db.Close(); chk.E(err) {
 172  			log.W.F("failed to close database: %v", err)
 173  		}
 174  	}
 175  	log.I.F("shutdown complete")
 176  }
 177