main.go raw
1 // orly-acl-managed is a standalone gRPC ACL server using the Managed mode.
2 // It provides fine-grained control via NIP-86 management API.
3 package main
4
5 import (
6 "context"
7 "os"
8 "path/filepath"
9 "strings"
10 "time"
11
12 "go-simpler.org/env"
13 "next.orly.dev/pkg/lol"
14 "next.orly.dev/pkg/lol/chk"
15 "next.orly.dev/pkg/lol/log"
16
17 "next.orly.dev/pkg/acl/server"
18 "next.orly.dev/pkg/database"
19 )
20
21 // Config holds the ACL server configuration.
22 type Config struct {
23 // Listen is the gRPC server listen address
24 Listen string `env:"ORLY_ACL_LISTEN" default:"127.0.0.1:50052" usage:"gRPC server listen address"`
25
26 // LogLevel is the logging level
27 LogLevel string `env:"ORLY_ACL_LOG_LEVEL" default:"info" usage:"log level (trace, debug, info, warn, error)"`
28
29 // Database configuration - Managed mode requires direct Badger access
30 DataDir string `env:"ORLY_DATA_DIR" usage:"database data directory"`
31
32 // Badger configuration
33 BlockCacheMB int `env:"ORLY_DB_BLOCK_CACHE_MB" default:"256" usage:"block cache size in MB"`
34 IndexCacheMB int `env:"ORLY_DB_INDEX_CACHE_MB" default:"128" usage:"index cache size in MB"`
35 ZSTDLevel int `env:"ORLY_DB_ZSTD_LEVEL" default:"3" usage:"ZSTD compression level"`
36 QueryCacheSizeMB int `env:"ORLY_DB_QUERY_CACHE_SIZE_MB" default:"64" usage:"query cache size in MB"`
37 QueryCacheMaxAge time.Duration `env:"ORLY_DB_QUERY_CACHE_MAX_AGE" default:"5m" usage:"query cache max age"`
38 QueryCacheDisabled bool `env:"ORLY_DB_QUERY_CACHE_DISABLED" default:"false" usage:"disable query cache"`
39 SerialCachePubkeys int `env:"ORLY_SERIAL_CACHE_PUBKEYS" default:"100000" usage:"serial cache pubkeys capacity"`
40 SerialCacheEventIds int `env:"ORLY_SERIAL_CACHE_EVENT_IDS" default:"500000" usage:"serial cache event IDs capacity"`
41
42 // ACL configuration
43 Owners string `env:"ORLY_OWNERS" usage:"comma-separated list of owner npubs"`
44 Admins string `env:"ORLY_ADMINS" usage:"comma-separated list of admin npubs"`
45 RelayAddresses string `env:"ORLY_RELAY_ADDRESSES" usage:"comma-separated list of relay addresses (self)"`
46 }
47
48 func main() {
49 cfg := loadConfig()
50
51 // Set log level
52 lol.SetLogLevel(cfg.LogLevel)
53 log.I.F("orly-acl-managed starting with log level: %s", cfg.LogLevel)
54
55 ctx, cancel := context.WithCancel(context.Background())
56 defer cancel()
57
58 // Managed mode requires direct Badger database access (not gRPC)
59 dbCfg := &database.DatabaseConfig{
60 DataDir: cfg.DataDir,
61 LogLevel: cfg.LogLevel,
62 BlockCacheMB: cfg.BlockCacheMB,
63 IndexCacheMB: cfg.IndexCacheMB,
64 QueryCacheSizeMB: cfg.QueryCacheSizeMB,
65 QueryCacheMaxAge: cfg.QueryCacheMaxAge,
66 QueryCacheDisabled: cfg.QueryCacheDisabled,
67 SerialCachePubkeys: cfg.SerialCachePubkeys,
68 SerialCacheEventIds: cfg.SerialCacheEventIds,
69 ZSTDLevel: cfg.ZSTDLevel,
70 }
71
72 log.I.F("initializing Badger database at %s", cfg.DataDir)
73 db, err := database.NewWithConfig(ctx, cancel, dbCfg)
74 if chk.E(err) {
75 log.E.F("failed to initialize database: %v", err)
76 os.Exit(1)
77 }
78
79 // Wait for database to be ready
80 log.I.F("waiting for database to be ready...")
81 <-db.Ready()
82 log.I.F("database ready")
83
84 // Create server config
85 serverCfg := &server.Config{
86 Listen: cfg.Listen,
87 ACLMode: "managed", // Hardcoded for this binary
88 LogLevel: cfg.LogLevel,
89 Owners: splitList(cfg.Owners),
90 Admins: splitList(cfg.Admins),
91 RelayAddresses: splitList(cfg.RelayAddresses),
92 }
93
94 // Create and configure server
95 srv := server.New(db, serverCfg, true)
96 if err := srv.ConfigureACL(ctx); chk.E(err) {
97 log.E.F("failed to configure ACL: %v", err)
98 os.Exit(1)
99 }
100
101 // Start server
102 if err := srv.ListenAndServe(ctx, cancel); err != nil {
103 log.E.F("gRPC server error: %v", err)
104 }
105 }
106
107 func loadConfig() *Config {
108 cfg := &Config{}
109 if err := env.Load(cfg, nil); chk.E(err) {
110 log.E.F("failed to load config: %v", err)
111 os.Exit(1)
112 }
113
114 // Set default data directory if not specified
115 if cfg.DataDir == "" {
116 home, err := os.UserHomeDir()
117 if chk.E(err) {
118 log.E.F("failed to get home directory: %v", err)
119 os.Exit(1)
120 }
121 cfg.DataDir = filepath.Join(home, ".local", "share", "ORLY")
122 }
123
124 // Ensure data directory exists
125 if err := os.MkdirAll(cfg.DataDir, 0700); chk.E(err) {
126 log.E.F("failed to create data directory %s: %v", cfg.DataDir, err)
127 os.Exit(1)
128 }
129
130 return cfg
131 }
132
133 func splitList(s string) []string {
134 if s == "" {
135 return nil
136 }
137 return strings.Split(s, ",")
138 }
139