You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
4.7 KiB
170 lines
4.7 KiB
// orly-acl is a standalone gRPC ACL server for the ORLY relay. |
|
// It wraps the ACL implementations and exposes them via gRPC. |
|
package main |
|
|
|
import ( |
|
"context" |
|
"net" |
|
"os" |
|
"os/signal" |
|
"syscall" |
|
"time" |
|
|
|
"google.golang.org/grpc" |
|
"google.golang.org/grpc/reflection" |
|
"lol.mleku.dev" |
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/log" |
|
|
|
"next.orly.dev/app/config" |
|
"next.orly.dev/pkg/acl" |
|
"next.orly.dev/pkg/database" |
|
databasegrpc "next.orly.dev/pkg/database/grpc" |
|
orlyaclv1 "next.orly.dev/pkg/proto/orlyacl/v1" |
|
) |
|
|
|
func main() { |
|
cfg := loadConfig() |
|
|
|
// Set log level |
|
lol.SetLogLevel(cfg.LogLevel) |
|
log.I.F("orly-acl starting with log level: %s, mode: %s", cfg.LogLevel, cfg.ACLMode) |
|
|
|
ctx, cancel := context.WithCancel(context.Background()) |
|
defer cancel() |
|
|
|
// Initialize database (direct Badger or gRPC client) |
|
var db database.Database |
|
var err error |
|
|
|
if cfg.DBType == "grpc" { |
|
// Use gRPC database client |
|
log.I.F("connecting to gRPC database server at %s", cfg.GRPCDBServer) |
|
dbClient, err := databasegrpc.New(ctx, &databasegrpc.ClientConfig{ |
|
ServerAddress: cfg.GRPCDBServer, |
|
ConnectTimeout: 30 * time.Second, |
|
}) |
|
if chk.E(err) { |
|
log.E.F("failed to connect to gRPC database: %v", err) |
|
os.Exit(1) |
|
} |
|
db = dbClient |
|
} else { |
|
// Use direct Badger database |
|
dbCfg := &database.DatabaseConfig{ |
|
DataDir: cfg.DataDir, |
|
LogLevel: cfg.LogLevel, |
|
BlockCacheMB: cfg.BlockCacheMB, |
|
IndexCacheMB: cfg.IndexCacheMB, |
|
QueryCacheSizeMB: cfg.QueryCacheSizeMB, |
|
QueryCacheMaxAge: cfg.QueryCacheMaxAge, |
|
QueryCacheDisabled: cfg.QueryCacheDisabled, |
|
SerialCachePubkeys: cfg.SerialCachePubkeys, |
|
SerialCacheEventIds: cfg.SerialCacheEventIds, |
|
ZSTDLevel: cfg.ZSTDLevel, |
|
} |
|
|
|
log.I.F("initializing Badger database at %s", cfg.DataDir) |
|
db, err = database.NewWithConfig(ctx, cancel, dbCfg) |
|
if chk.E(err) { |
|
log.E.F("failed to initialize database: %v", err) |
|
os.Exit(1) |
|
} |
|
} |
|
|
|
// Wait for database to be ready |
|
log.I.F("waiting for database to be ready...") |
|
<-db.Ready() |
|
log.I.F("database ready") |
|
|
|
// Create gRPC server EARLY so launcher can detect it's alive |
|
grpcServer := grpc.NewServer( |
|
grpc.MaxRecvMsgSize(16<<20), // 16MB |
|
grpc.MaxSendMsgSize(16<<20), // 16MB |
|
) |
|
|
|
// Register ACL service (will be configured below) |
|
service := NewACLService(cfg, db) |
|
orlyaclv1.RegisterACLServiceServer(grpcServer, service) |
|
|
|
// Register reflection for debugging with grpcurl |
|
reflection.Register(grpcServer) |
|
|
|
// Start listening immediately so launcher knows we're alive |
|
lis, err := net.Listen("tcp", cfg.Listen) |
|
if chk.E(err) { |
|
log.E.F("failed to listen on %s: %v", cfg.Listen, err) |
|
os.Exit(1) |
|
} |
|
log.I.F("gRPC ACL server listening on %s", cfg.Listen) |
|
|
|
// Start serving in background while we configure |
|
go func() { |
|
if err := grpcServer.Serve(lis); err != nil { |
|
log.E.F("gRPC server error: %v", err) |
|
} |
|
}() |
|
|
|
// Create app config for ACL configuration |
|
appCfg := &config.C{ |
|
Owners: cfg.GetOwners(), |
|
Admins: cfg.GetAdmins(), |
|
BootstrapRelays: cfg.GetBootstrapRelays(), |
|
RelayAddresses: cfg.GetRelayAddresses(), |
|
FollowListFrequency: cfg.FollowListFrequency, |
|
FollowsThrottleEnabled: cfg.FollowsThrottleEnabled, |
|
FollowsThrottlePerEvent: cfg.FollowsThrottlePerEvent, |
|
FollowsThrottleMaxDelay: cfg.FollowsThrottleMaxDelay, |
|
} |
|
|
|
// Set ACL mode and configure the registry (may take time to load follow lists) |
|
acl.Registry.SetMode(cfg.ACLMode) |
|
if err := acl.Registry.Configure(appCfg, db, ctx); chk.E(err) { |
|
log.E.F("failed to configure ACL: %v", err) |
|
os.Exit(1) |
|
} |
|
|
|
// Mark service as ready now that configuration is complete |
|
service.SetReady(true) |
|
|
|
// Start the syncer goroutine for background operations |
|
acl.Registry.Syncer() |
|
log.I.F("ACL syncer started for mode: %s", cfg.ACLMode) |
|
|
|
// Handle graceful shutdown - block until signal received |
|
sigs := make(chan os.Signal, 1) |
|
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) |
|
sig := <-sigs |
|
log.I.F("received signal %v, shutting down...", sig) |
|
|
|
// Cancel context to stop all operations |
|
cancel() |
|
|
|
// Gracefully stop gRPC server with timeout |
|
stopped := make(chan struct{}) |
|
go func() { |
|
grpcServer.GracefulStop() |
|
close(stopped) |
|
}() |
|
|
|
select { |
|
case <-stopped: |
|
log.I.F("gRPC server stopped gracefully") |
|
case <-time.After(5 * time.Second): |
|
log.W.F("gRPC graceful stop timed out, forcing stop") |
|
grpcServer.Stop() |
|
} |
|
|
|
// Sync and close database (only for direct Badger) |
|
if cfg.DBType != "grpc" { |
|
log.I.F("syncing database...") |
|
if err := db.Sync(); chk.E(err) { |
|
log.W.F("failed to sync database: %v", err) |
|
} |
|
log.I.F("closing database...") |
|
if err := db.Close(); chk.E(err) { |
|
log.W.F("failed to close database: %v", err) |
|
} |
|
} |
|
log.I.F("shutdown complete") |
|
}
|
|
|