config.go raw
1 // Package config loads torly's settings from the environment.
2 //
3 // Every variable has a TORLY_ name and an ORLY_ fallback, so an existing orly
4 // or morly deployment can be pointed at torly by changing only the binary.
5 package config
6
7 import (
8 "fmt"
9 "os"
10 "path/filepath"
11 "strconv"
12 "strings"
13 )
14
15 // Version is the relay version, set by the build.
16 var Version = "0.1.0"
17
18 // Config is the complete runtime configuration.
19 type Config struct {
20 AppName string
21 Description string
22 Contact string
23 Icon string
24
25 ListenHost string
26 Port int
27 DataDir string
28 StaticDir string
29 RelayURL string
30 SecretKey string
31
32 // RelayPubKey is derived at startup from SecretKey or the persisted relay
33 // key file. It is not read from the environment directly.
34 RelayPubKey string
35
36 AuthRequired bool
37 MaxConnections int
38 ConnWorkers int
39 VerifyWorkers int
40
41 BlossomEnabled bool
42 BlossomMaxBlob int64
43
44 MaxEventBytes int
45 MaxSubsPerConn int
46 MaxFilters int
47 MaxLimit int
48 QueryLimit int
49 MaxFutureSecs int64
50
51 ExpiryInterval int64 // seconds between NIP-40 purges
52
53 LogLevel string
54 }
55
56 // FromEnv builds a Config, applying defaults for absent variables.
57 func FromEnv() (*Config, error) {
58 home, _ := os.UserHomeDir()
59 if home == "" {
60 home = "."
61 }
62 c := &Config{
63 AppName: env("APP_NAME", "torly"),
64 Description: env("DESCRIPTION", "torly - a TinyGo Nostr relay"),
65 Contact: env("CONTACT", ""),
66 Icon: env("ICON", ""),
67 ListenHost: env("LISTEN", "127.0.0.1"),
68 DataDir: env("DATA_DIR", filepath.Join(home, ".local", "share", "torly")),
69 StaticDir: env("STATIC_DIR", ""),
70 SecretKey: env("SECRET_KEY", ""),
71 AuthRequired: envBool("AUTH_REQUIRED", false),
72 MaxConnections: envInt("MAX_CONNECTIONS", 0),
73 ConnWorkers: envInt("CONN_WORKERS", 64),
74 VerifyWorkers: envInt("VERIFY_WORKERS", 2),
75 BlossomEnabled: envBool("BLOSSOM_ENABLED", true),
76 BlossomMaxBlob: int64(envInt("BLOSSOM_MAX_BLOB_MB", 50)) << 20,
77 MaxEventBytes: envInt("MAX_EVENT_BYTES", 128<<10),
78 MaxSubsPerConn: envInt("MAX_SUBS_PER_CONN", 64),
79 MaxFilters: envInt("MAX_FILTERS", 16),
80 MaxLimit: envInt("MAX_LIMIT", 5000),
81 QueryLimit: envInt("QUERY_LIMIT", 5000),
82 MaxFutureSecs: int64(envInt("MAX_FUTURE_SECS", 900)),
83 ExpiryInterval: int64(envInt("EXPIRY_INTERVAL", 60)),
84 LogLevel: strings.ToLower(env("LOG_LEVEL", "info")),
85 }
86 port, err := strconv.Atoi(env("PORT", "3334"))
87 if err != nil || port < 0 || port > 65535 {
88 return nil, fmt.Errorf("torly: invalid TORLY_PORT")
89 }
90 c.Port = port
91 if c.MaxEventBytes < 1024 {
92 return nil, fmt.Errorf("torly: TORLY_MAX_EVENT_BYTES must be at least 1024")
93 }
94 if c.BlossomMaxBlob < 1<<20 {
95 c.BlossomMaxBlob = 1 << 20
96 }
97 if c.MaxFilters < 1 {
98 c.MaxFilters = 1
99 }
100 if c.MaxSubsPerConn < 1 {
101 c.MaxSubsPerConn = 1
102 }
103 if c.ConnWorkers < 1 {
104 c.ConnWorkers = 1
105 }
106 if c.ConnWorkers > 1024 {
107 c.ConnWorkers = 1024
108 }
109 if c.VerifyWorkers < 1 {
110 c.VerifyWorkers = 1
111 }
112 if c.VerifyWorkers > 64 {
113 c.VerifyWorkers = 64
114 }
115 if c.MaxLimit < 1 {
116 c.MaxLimit = 1
117 }
118 if c.RelayURL = env("RELAY_URL", ""); c.RelayURL == "" {
119 host := c.ListenHost
120 if host == "" || host == "0.0.0.0" || host == "::" {
121 host = "localhost"
122 }
123 c.RelayURL = fmt.Sprintf("ws://%s:%d/", host, c.Port)
124 }
125 return c, nil
126 }
127
128 // Addr returns the listen address.
129 func (c *Config) Addr() string {
130 return fmt.Sprintf("%s:%d", c.ListenHost, c.Port)
131 }
132
133 // NIP11 is the relay information document served for NIP-11.
134 type NIP11 struct {
135 Name string `json:"name"`
136 Description string `json:"description"`
137 PubKey string `json:"pubkey,omitempty"`
138 Contact string `json:"contact,omitempty"`
139 SupportedNIPs []int `json:"supported_nips"`
140 Software string `json:"software"`
141 Version string `json:"version"`
142 Icon string `json:"icon,omitempty"`
143 Limitation LimitDoc `json:"limitation"`
144 }
145
146 // LimitDoc is the NIP-11 limitation object.
147 type LimitDoc struct {
148 MaxMessageLength int `json:"max_message_length"`
149 MaxSubscriptions int `json:"max_subscriptions"`
150 MaxFilters int `json:"max_filters"`
151 MaxLimit int `json:"max_limit"`
152 MaxSubIDLength int `json:"max_subid_length"`
153 MinPrefix int `json:"min_prefix"`
154 MaxEventTags int `json:"max_event_tags"`
155 MaxContentLength int `json:"max_content_length"`
156 AuthRequired bool `json:"auth_required"`
157 PaymentRequired bool `json:"payment_required"`
158 }
159
160 // SupportedNIPs lists the NIPs torly implements.
161 var SupportedNIPs = []int{1, 9, 11, 33, 40, 42, 45, 50, 70}
162
163 // EnvHelp documents the environment.
164 func EnvHelp() string {
165 return `torly - a TinyGo Nostr relay
166
167 Usage:
168 torly [serve] run the relay (default)
169 torly import [opts] import newline-delimited NIP-01 events
170 torly reindex-graph rebuild derived graph indexes from the store
171 torly blossom-backup --data-dir DIR --out FILE
172 torly blossom-restore --data-dir DIR --file FILE [--offline]
173 torly version print the version
174 torly help print this help
175
176 Configuration is by environment variable. TORLY_* is authoritative; the same
177 name with ORLY_* is accepted as a fallback.
178
179 TORLY_APP_NAME NIP-11 name (default torly)
180 TORLY_DESCRIPTION NIP-11 description
181 TORLY_CONTACT NIP-11 contact
182 TORLY_ICON NIP-11 icon URL
183 TORLY_LISTEN bind address (default 127.0.0.1)
184 TORLY_PORT bind port (default 3334)
185 TORLY_DATA_DIR database and blob directory
186 TORLY_STATIC_DIR optional static web root
187 TORLY_RELAY_URL public URL used in NIP-42 checks
188 TORLY_SECRET_KEY relay secret key (64 hex); default is DATA_DIR/relay.key
189 TORLY_AUTH_REQUIRED require NIP-42 AUTH for writes (default false)
190 TORLY_MAX_CONNECTIONS accepted connections cap, 0 = unlimited
191 TORLY_CONN_WORKERS fixed reader/writer worker pairs (default 64)
192 TORLY_VERIFY_WORKERS signature verification domains (default 2)
193 TORLY_BLOSSOM_ENABLED serve Blossom blobs (default true)
194 TORLY_BLOSSOM_MAX_BLOB_MB largest accepted blob in MiB (default 50)
195 TORLY_MAX_EVENT_BYTES largest event JSON in bytes (default 131072)
196 TORLY_MAX_SUBS_PER_CONN subscriptions per connection (default 64)
197 TORLY_MAX_FILTERS filters per REQ (default 16)
198 TORLY_MAX_LIMIT largest accepted filter limit (default 5000)
199 TORLY_QUERY_LIMIT result cap for unbounded filters (default 5000)
200 TORLY_MAX_FUTURE_SECS allowed created_at skew into the future (default 900)
201 TORLY_EXPIRY_INTERVAL seconds between NIP-40 purges (default 60)
202 TORLY_LOG_LEVEL error, warn, info, debug (default info)
203 `
204 }
205
206 func env(name, def string) string {
207 if v := os.Getenv("TORLY_" + name); v != "" {
208 return v
209 }
210 if v := os.Getenv("ORLY_" + name); v != "" {
211 return v
212 }
213 return def
214 }
215
216 func envInt(name string, def int) int {
217 v := env(name, "")
218 if v == "" {
219 return def
220 }
221 n, err := strconv.Atoi(v)
222 if err != nil {
223 return def
224 }
225 return n
226 }
227
228 func envBool(name string, def bool) bool {
229 v := strings.ToLower(env(name, ""))
230 switch v {
231 case "1", "true", "yes", "on":
232 return true
233 case "0", "false", "no", "off":
234 return false
235 }
236 return def
237 }
238