/
config.go
983 lines (900 loc) · 52.3 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
// Copyright 2018 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"crypto/tls"
"flag"
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"github.com/heroiclabs/nakama/v3/flags"
"go.uber.org/zap"
"gopkg.in/yaml.v2"
)
// Config interface is the Nakama core configuration.
type Config interface {
GetName() string
GetDataDir() string
GetShutdownGraceSec() int
GetLogger() *LoggerConfig
GetMetrics() *MetricsConfig
GetSession() *SessionConfig
GetSocket() *SocketConfig
GetDatabase() *DatabaseConfig
GetSocial() *SocialConfig
GetRuntime() *RuntimeConfig
GetMatch() *MatchConfig
GetTracker() *TrackerConfig
GetConsole() *ConsoleConfig
GetLeaderboard() *LeaderboardConfig
GetMatchmaker() *MatchmakerConfig
GetIAP() *IAPConfig
Clone() (Config, error)
}
func ParseArgs(logger *zap.Logger, args []string) Config {
// Parse args to get path to a config file if passed in.
configFilePath := NewConfig(logger)
configFileFlagSet := flag.NewFlagSet("nakama", flag.ExitOnError)
configFileFlagMaker := flags.NewFlagMakerFlagSet(&flags.FlagMakingOptions{
UseLowerCase: true,
Flatten: false,
TagName: "yaml",
TagUsage: "usage",
}, configFileFlagSet)
if _, err := configFileFlagMaker.ParseArgs(configFilePath, args[1:]); err != nil {
logger.Fatal("Could not parse command line arguments", zap.Error(err))
}
// Parse config file if path is set.
mainConfig := NewConfig(logger)
runtimeEnvironment := mainConfig.GetRuntime().Environment
for _, cfg := range configFilePath.Config {
data, err := ioutil.ReadFile(cfg)
if err != nil {
logger.Fatal("Could not read config file", zap.String("path", cfg), zap.Error(err))
}
err = yaml.Unmarshal(data, mainConfig)
if err != nil {
logger.Fatal("Could not parse config file", zap.String("path", cfg), zap.Error(err))
}
// Convert and preserve the runtime environment key-value pairs.
runtimeEnvironment = convertRuntimeEnv(logger, runtimeEnvironment, mainConfig.GetRuntime().Env)
logger.Info("Successfully loaded config file", zap.String("path", cfg))
}
// Preserve the config file path arguments.
mainConfig.Config = configFilePath.Config
// Override config with those passed from command-line.
mainFlagSet := flag.NewFlagSet("nakama", flag.ExitOnError)
mainFlagMaker := flags.NewFlagMakerFlagSet(&flags.FlagMakingOptions{
UseLowerCase: true,
Flatten: false,
TagName: "yaml",
TagUsage: "usage",
}, mainFlagSet)
if _, err := mainFlagMaker.ParseArgs(mainConfig, args[1:]); err != nil {
logger.Fatal("Could not parse command line arguments", zap.Error(err))
}
mainConfig.GetRuntime().Environment = convertRuntimeEnv(logger, runtimeEnvironment, mainConfig.GetRuntime().Env)
mainConfig.GetRuntime().Env = make([]string, 0, len(mainConfig.GetRuntime().Environment))
for k, v := range mainConfig.GetRuntime().Environment {
mainConfig.GetRuntime().Env = append(mainConfig.GetRuntime().Env, fmt.Sprintf("%v=%v", k, v))
}
sort.Strings(mainConfig.GetRuntime().Env)
return mainConfig
}
func CheckConfig(logger *zap.Logger, config Config) map[string]string {
// Fail fast on invalid values.
if l := len(config.GetName()); l < 1 || l > 16 {
logger.Fatal("Name must be 1-16 characters", zap.String("param", "name"))
}
if config.GetShutdownGraceSec() < 0 {
logger.Fatal("Shutdown grace period must be >= 0", zap.Int("shutdown_grace_sec", config.GetShutdownGraceSec()))
}
if config.GetSocket().ServerKey == "" {
logger.Fatal("Server key must be set", zap.String("param", "socket.server_key"))
}
if config.GetSession().TokenExpirySec < 1 {
logger.Fatal("Token expiry seconds must be >= 1", zap.String("param", "session.token_expiry_sec"))
}
if config.GetSession().EncryptionKey == "" {
logger.Fatal("Encryption key must be set", zap.String("param", "session.encryption_key"))
}
if config.GetSession().RefreshEncryptionKey == "" {
logger.Fatal("Refresh token encryption key must be set", zap.String("param", "session.refresh_encryption_key"))
}
if config.GetSession().RefreshTokenExpirySec < 1 {
logger.Fatal("Refresh token expiry seconds must be >= 1", zap.String("param", "session.refresh_token_expiry_sec"))
}
if config.GetSession().EncryptionKey == config.GetSession().RefreshEncryptionKey {
logger.Fatal("Encryption key and refresh token encryption cannot match", zap.Strings("param", []string{"session.encryption_key", "session.refresh_encryption_key"}))
}
if config.GetSession().SingleMatch && !config.GetSession().SingleSocket {
logger.Fatal("Single match cannot be enabled without single socket", zap.Strings("param", []string{"session.single_match", "session.single_socket"}))
}
if config.GetRuntime().HTTPKey == "" {
logger.Fatal("Runtime HTTP key must be set", zap.String("param", "runtime.http_key"))
}
if config.GetConsole().MaxMessageSizeBytes < 1 {
logger.Fatal("Console max message size bytes must be >= 1", zap.Int64("console.max_message_size_bytes", config.GetConsole().MaxMessageSizeBytes))
}
if config.GetConsole().ReadTimeoutMs < 1 {
logger.Fatal("Console read timeout milliseconds must be >= 1", zap.Int("console.read_timeout_ms", config.GetConsole().ReadTimeoutMs))
}
if config.GetConsole().WriteTimeoutMs < 1 {
logger.Fatal("Console write timeout milliseconds must be >= 1", zap.Int("console.write_timeout_ms", config.GetConsole().WriteTimeoutMs))
}
if config.GetConsole().IdleTimeoutMs < 1 {
logger.Fatal("Console idle timeout milliseconds must be >= 1", zap.Int("console.idle_timeout_ms", config.GetConsole().IdleTimeoutMs))
}
if config.GetConsole().Username == "" {
logger.Fatal("Console username must be set", zap.String("param", "console.username"))
}
if config.GetConsole().Password == "" {
logger.Fatal("Console password must be set", zap.String("param", "console.password"))
}
if config.GetConsole().SigningKey == "" {
logger.Fatal("Console signing key must be set", zap.String("param", "console.signing_key"))
}
if p := config.GetSocket().Protocol; p != "tcp" && p != "tcp4" && p != "tcp6" {
logger.Fatal("Socket protocol must be one of: tcp, tcp4, tcp6", zap.String("socket.protocol", config.GetSocket().Protocol))
}
if config.GetSocket().MaxMessageSizeBytes < 1 {
logger.Fatal("Socket max message size bytes must be >= 1", zap.Int64("socket.max_message_size_bytes", config.GetSocket().MaxMessageSizeBytes))
}
if config.GetSocket().MaxRequestSizeBytes < 1 {
logger.Fatal("Socket max request size bytes must be >= 1", zap.Int64("socket.max_request_size_bytes", config.GetSocket().MaxRequestSizeBytes))
}
if config.GetSocket().ReadBufferSizeBytes < 1 {
logger.Fatal("Socket read buffer size bytes must be >= 1", zap.Int("socket.read_buffer_size_bytes", config.GetSocket().ReadBufferSizeBytes))
}
if config.GetSocket().WriteBufferSizeBytes < 1 {
logger.Fatal("Socket write buffer size bytes must be >= 1", zap.Int("socket.write_buffer_size_bytes", config.GetSocket().WriteBufferSizeBytes))
}
if config.GetSocket().ReadTimeoutMs < 1 {
logger.Fatal("Socket read timeout milliseconds must be >= 1", zap.Int("socket.read_timeout_ms", config.GetSocket().ReadTimeoutMs))
}
if config.GetSocket().WriteTimeoutMs < 1 {
logger.Fatal("Socket write timeout milliseconds must be >= 1", zap.Int("socket.write_timeout_ms", config.GetSocket().WriteTimeoutMs))
}
if config.GetSocket().IdleTimeoutMs < 1 {
logger.Fatal("Socket idle timeout milliseconds must be >= 1", zap.Int("socket.idle_timeout_ms", config.GetSocket().IdleTimeoutMs))
}
if config.GetSocket().PingPeriodMs >= config.GetSocket().PongWaitMs {
logger.Fatal("Ping period value must be less than pong wait value", zap.Int("socket.ping_period_ms", config.GetSocket().PingPeriodMs), zap.Int("socket.pong_wait_ms", config.GetSocket().PongWaitMs))
}
if len(config.GetDatabase().Addresses) < 1 {
logger.Fatal("At least one database address must be specified", zap.Strings("database.address", config.GetDatabase().Addresses))
}
for _, address := range config.GetDatabase().Addresses {
rawURL := fmt.Sprintf("postgresql://%s", address)
if _, err := url.Parse(rawURL); err != nil {
logger.Fatal("Bad database connection URL", zap.String("database.address", address), zap.Error(err))
}
}
if config.GetDatabase().DnsScanIntervalSec < 1 {
logger.Fatal("Database DNS scan interval seconds must be > 0", zap.Int("database.dns_scan_interval_sec", config.GetDatabase().DnsScanIntervalSec))
}
if config.GetRuntime().GetLuaMinCount() < 0 {
logger.Fatal("Minimum Lua runtime instance count must be >= 0", zap.Int("runtime.lua_min_count", config.GetRuntime().GetLuaMinCount()))
}
if config.GetRuntime().GetLuaMaxCount() < 1 {
logger.Fatal("Maximum Lua runtime instance count must be >= 1", zap.Int("runtime.lua_max_count", config.GetRuntime().GetLuaMinCount()))
}
if config.GetRuntime().GetLuaMinCount() > config.GetRuntime().GetLuaMaxCount() {
logger.Fatal("Minimum Lua runtime instance count must be less than or equal to maximum Lua runtime instance count", zap.Int("runtime.lua_min_count", config.GetRuntime().GetLuaMinCount()), zap.Int("runtime.lua_max_count", config.GetRuntime().GetLuaMaxCount()))
}
if config.GetRuntime().GetLuaCallStackSize() < 1 {
logger.Fatal("Lua runtime instance call stack size must be >= 1", zap.Int("runtime.lua_call_stack_size", config.GetRuntime().GetLuaCallStackSize()))
}
if config.GetRuntime().GetLuaRegistrySize() < 128 {
logger.Fatal("Lua runtime instance registry size must be >= 128", zap.Int("runtime.registry_size", config.GetRuntime().GetLuaRegistrySize()))
}
if config.GetRuntime().JsMinCount < 0 {
logger.Fatal("Minimum JavaScript runtime instance count must be >= 0", zap.Int("runtime.js_min_count", config.GetRuntime().JsMinCount))
}
if config.GetRuntime().JsMaxCount < 1 {
logger.Fatal("Maximum JavaScript runtime instance count must be >= 1", zap.Int("runtime.js_max_count", config.GetRuntime().JsMinCount))
}
if config.GetRuntime().JsMinCount > config.GetRuntime().JsMaxCount {
logger.Fatal("Minimum JavaScript runtime instance count must be less than or equal to maximum JavaScript runtime instance count", zap.Int("runtime.js_min_count", config.GetRuntime().JsMinCount), zap.Int("runtime.js_max_count", config.GetRuntime().JsMaxCount))
}
if config.GetRuntime().EventQueueSize < 1 {
logger.Fatal("Runtime event queue stack size must be >= 1", zap.Int("runtime.event_queue_size", config.GetRuntime().EventQueueSize))
}
if config.GetRuntime().EventQueueWorkers < 1 {
logger.Fatal("Runtime event queue workers must be >= 1", zap.Int("runtime.event_queue_workers", config.GetRuntime().EventQueueWorkers))
}
if config.GetMatch().InputQueueSize < 1 {
logger.Fatal("Match input queue size must be >= 1", zap.Int("match.input_queue_size", config.GetMatch().InputQueueSize))
}
if config.GetMatch().CallQueueSize < 1 {
logger.Fatal("Match call queue size must be >= 1", zap.Int("match.call_queue_size", config.GetMatch().CallQueueSize))
}
if config.GetMatch().SignalQueueSize < 1 {
logger.Fatal("Match signal queue size must be >= 1", zap.Int("match.signal_queue_size", config.GetMatch().SignalQueueSize))
}
if config.GetMatch().JoinAttemptQueueSize < 1 {
logger.Fatal("Match join attempt queue size must be >= 1", zap.Int("match.join_attempt_queue_size", config.GetMatch().JoinAttemptQueueSize))
}
if config.GetMatch().DeferredQueueSize < 1 {
logger.Fatal("Match deferred queue size must be >= 1", zap.Int("match.deferred_queue_size", config.GetMatch().DeferredQueueSize))
}
if config.GetMatch().JoinMarkerDeadlineMs < 1 {
logger.Fatal("Match join marker deadline must be >= 1", zap.Int("match.join_marker_deadline_ms", config.GetMatch().JoinMarkerDeadlineMs))
}
if config.GetMatch().MaxEmptySec < 0 {
logger.Fatal("Match max idle seconds must be >= 0", zap.Int("match.max_empty_sec", config.GetMatch().MaxEmptySec))
}
if config.GetMatch().LabelUpdateIntervalMs < 1 {
logger.Fatal("Match label update interval milliseconds must be > 0", zap.Int("match.label_update_interval_ms", config.GetMatch().LabelUpdateIntervalMs))
}
if config.GetTracker().EventQueueSize < 1 {
logger.Fatal("Tracker presence event queue size must be >= 1", zap.Int("tracker.event_queue_size", config.GetTracker().EventQueueSize))
}
if config.GetLeaderboard().CallbackQueueSize < 1 {
logger.Fatal("Leaderboard callback queue stack size must be >= 1", zap.Int("leaderboard.callback_queue_size", config.GetLeaderboard().CallbackQueueSize))
}
if config.GetLeaderboard().CallbackQueueWorkers < 1 {
logger.Fatal("Leaderboard callback queue workers must be >= 1", zap.Int("leaderboard.callback_queue_workers", config.GetLeaderboard().CallbackQueueWorkers))
}
if config.GetMatchmaker().MaxTickets < 1 {
logger.Fatal("Matchmaker maximum ticket count must be >= 1", zap.Int("matchmaker.max_tickets", config.GetMatchmaker().MaxTickets))
}
if config.GetMatchmaker().IntervalSec < 1 {
logger.Fatal("Matchmaker interval time seconds must be >= 1", zap.Int("matchmaker.interval_sec", config.GetMatchmaker().IntervalSec))
}
if config.GetMatchmaker().MaxIntervals < 1 {
logger.Fatal("Matchmaker max intervals must be >= 1", zap.Int("matchmaker.max_intervals", config.GetMatchmaker().MaxIntervals))
}
if config.GetMatchmaker().BatchPoolSize < 1 {
logger.Fatal("Matchmaker batch pool size must be >= 1", zap.Int("matchmaker.batch_pool_size", config.GetMatchmaker().BatchPoolSize))
}
if config.GetMatchmaker().RevThreshold < 0 {
logger.Fatal("Matchmaker reverse matching threshold must be >= 0", zap.Int("matchmaker.rev_threshold", config.GetMatchmaker().RevThreshold))
}
// If the runtime path is not overridden, set it to `datadir/modules`.
if config.GetRuntime().Path == "" {
config.GetRuntime().Path = filepath.Join(config.GetDataDir(), "modules")
}
// If JavaScript entrypoint is set, make sure it points to a valid file.
if config.GetRuntime().JsEntrypoint != "" {
p := filepath.Join(config.GetRuntime().Path, config.GetRuntime().JsEntrypoint)
info, err := os.Stat(p)
if err != nil {
logger.Fatal("JavaScript entrypoint must be a valid path", zap.Error(err))
}
if filepath.Ext(info.Name()) != ".js" {
logger.Fatal("JavaScript entrypoint must point to a .js file", zap.String("runtime.js_entrypoint", p))
}
}
configWarnings := make(map[string]string, 8)
// Log warnings for insecure default parameter values.
if config.GetConsole().Username == "admin" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "console.username"))
configWarnings["console.username"] = "Insecure default parameter value, change this for production!"
}
if config.GetConsole().Password == "password" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "console.password"))
configWarnings["console.password"] = "Insecure default parameter value, change this for production!"
}
if config.GetConsole().SigningKey == "defaultsigningkey" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "console.signing_key"))
configWarnings["console.signing_key"] = "Insecure default parameter value, change this for production!"
}
if config.GetSocket().ServerKey == "defaultkey" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "socket.server_key"))
configWarnings["socket.server_key"] = "Insecure default parameter value, change this for production!"
}
if config.GetSession().EncryptionKey == "defaultencryptionkey" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "session.encryption_key"))
configWarnings["session.encryption_key"] = "Insecure default parameter value, change this for production!"
}
if config.GetSession().RefreshEncryptionKey == "defaultrefreshencryptionkey" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "session.refresh_encryption_key"))
configWarnings["session.refresh_encryption_key"] = "Insecure default parameter value, change this for production!"
}
if config.GetRuntime().HTTPKey == "defaulthttpkey" {
logger.Warn("WARNING: insecure default parameter value, change this for production!", zap.String("param", "runtime.http_key"))
configWarnings["runtime.http_key"] = "Insecure default parameter value, change this for production!"
}
// Log warnings for deprecated config parameters.
if config.GetRuntime().MinCount != 0 {
logger.Warn("WARNING: deprecated configuration parameter", zap.String("deprecated", "runtime.min_count"), zap.String("param", "runtime.lua_min_count"))
configWarnings["runtime.min_count"] = "Deprecated configuration parameter"
}
if config.GetRuntime().MaxCount != 0 {
logger.Warn("WARNING: deprecated configuration parameter", zap.String("deprecated", "runtime.max_count"), zap.String("param", "runtime.lua_max_count"))
configWarnings["runtime.max_count"] = "Deprecated configuration parameter"
}
if config.GetRuntime().CallStackSize != 0 {
logger.Warn("WARNING: deprecated configuration parameter", zap.String("deprecated", "runtime.call_stack_size"), zap.String("param", "runtime.lua_call_stack_size"))
configWarnings["runtime.call_stack_size"] = "Deprecated configuration parameter"
}
if config.GetRuntime().RegistrySize != 0 {
logger.Warn("WARNING: deprecated configuration parameter", zap.String("deprecated", "runtime.registry_size"), zap.String("param", "runtime.lua_registry_size"))
configWarnings["runtime.registry_size"] = "Deprecated configuration parameter"
}
if config.GetRuntime().ReadOnlyGlobals != true {
logger.Warn("WARNING: deprecated configuration parameter", zap.String("deprecated", "runtime.read_only_globals"), zap.String("param", "runtime.lua_read_only_globals"))
configWarnings["runtime.read_only_globals"] = "Deprecated configuration parameter"
}
// Log warnings for SSL usage.
if config.GetSocket().SSLCertificate != "" && config.GetSocket().SSLPrivateKey == "" {
logger.Fatal("SSL configuration invalid, specify both socket.ssl_certificate and socket.ssl_private_key", zap.String("param", "socket.ssl_certificate"))
}
if config.GetSocket().SSLCertificate == "" && config.GetSocket().SSLPrivateKey != "" {
logger.Fatal("SSL configuration invalid, specify both socket.ssl_certificate and socket.ssl_private_key", zap.String("param", "socket.ssl_private_key"))
}
if config.GetSocket().SSLCertificate != "" && config.GetSocket().SSLPrivateKey != "" {
logger.Warn("WARNING: enabling direct SSL termination is not recommended, use an SSL-capable proxy or load balancer for production!")
certPEMBlock, err := ioutil.ReadFile(config.GetSocket().SSLCertificate)
if err != nil {
logger.Fatal("Error loading SSL certificate cert file", zap.Error(err))
}
keyPEMBlock, err := ioutil.ReadFile(config.GetSocket().SSLPrivateKey)
if err != nil {
logger.Fatal("Error loading SSL certificate key file", zap.Error(err))
}
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
if err != nil {
logger.Fatal("Error loading SSL certificate", zap.Error(err))
}
configWarnings["socket.ssl_certificate"] = "Enabling direct SSL termination is not recommended, use an SSL-capable proxy or load balancer for production!"
configWarnings["socket.ssl_private_key"] = "Enabling direct SSL termination is not recommended, use an SSL-capable proxy or load balancer for production!"
logger.Info("SSL mode enabled")
config.GetSocket().CertPEMBlock = certPEMBlock
config.GetSocket().KeyPEMBlock = keyPEMBlock
config.GetSocket().TLSCert = []tls.Certificate{cert}
}
return configWarnings
}
func convertRuntimeEnv(logger *zap.Logger, existingEnv map[string]string, mergeEnv []string) map[string]string {
envMap := make(map[string]string, len(existingEnv))
for k, v := range existingEnv {
envMap[k] = v
}
for _, e := range mergeEnv {
if !strings.Contains(e, "=") {
logger.Fatal("Invalid runtime environment value.", zap.String("value", e))
}
kv := strings.SplitN(e, "=", 2) // the value can contain the character "=" many times over.
if len(kv) == 1 {
envMap[kv[0]] = ""
} else if len(kv) == 2 {
envMap[kv[0]] = kv[1]
}
}
return envMap
}
type config struct {
Name string `yaml:"name" json:"name" usage:"Nakama server’s node name - must be unique."`
Config []string `yaml:"config" json:"config" usage:"The absolute file path to configuration YAML file."`
ShutdownGraceSec int `yaml:"shutdown_grace_sec" json:"shutdown_grace_sec" usage:"Maximum number of seconds to wait for the server to complete work before shutting down. Default is 0 seconds. If 0 the server will shut down immediately when it receives a termination signal."`
Datadir string `yaml:"data_dir" json:"data_dir" usage:"An absolute path to a writeable folder where Nakama will store its data."`
Logger *LoggerConfig `yaml:"logger" json:"logger" usage:"Logger levels and output."`
Metrics *MetricsConfig `yaml:"metrics" json:"metrics" usage:"Metrics settings."`
Session *SessionConfig `yaml:"session" json:"session" usage:"Session authentication settings."`
Socket *SocketConfig `yaml:"socket" json:"socket" usage:"Socket configuration."`
Database *DatabaseConfig `yaml:"database" json:"database" usage:"Database connection settings."`
Social *SocialConfig `yaml:"social" json:"social" usage:"Properties for social provider integrations."`
Runtime *RuntimeConfig `yaml:"runtime" json:"runtime" usage:"Script Runtime properties."`
Match *MatchConfig `yaml:"match" json:"match" usage:"Authoritative realtime match properties."`
Tracker *TrackerConfig `yaml:"tracker" json:"tracker" usage:"Presence tracker properties."`
Console *ConsoleConfig `yaml:"console" json:"console" usage:"Console settings."`
Leaderboard *LeaderboardConfig `yaml:"leaderboard" json:"leaderboard" usage:"Leaderboard settings."`
Matchmaker *MatchmakerConfig `yaml:"matchmaker" json:"matchmaker" usage:"Matchmaker settings."`
IAP *IAPConfig `yaml:"iap" json:"iap" usage:"In-App Purchase settings."`
}
// NewConfig constructs a Config struct which represents server settings, and populates it with default values.
func NewConfig(logger *zap.Logger) *config {
cwd, err := os.Getwd()
if err != nil {
logger.Fatal("Error getting current working directory.", zap.Error(err))
}
return &config{
Name: "nakama",
Datadir: filepath.Join(cwd, "data"),
ShutdownGraceSec: 0,
Logger: NewLoggerConfig(),
Metrics: NewMetricsConfig(),
Session: NewSessionConfig(),
Socket: NewSocketConfig(),
Database: NewDatabaseConfig(),
Social: NewSocialConfig(),
Runtime: NewRuntimeConfig(),
Match: NewMatchConfig(),
Tracker: NewTrackerConfig(),
Console: NewConsoleConfig(),
Leaderboard: NewLeaderboardConfig(),
Matchmaker: NewMatchmakerConfig(),
IAP: NewIAPConfig(),
}
}
func (c *config) Clone() (Config, error) {
configLogger := *(c.Logger)
configMetrics := *(c.Metrics)
configSession := *(c.Session)
configSocket := *(c.Socket)
configDatabase := *(c.Database)
configSocial := *(c.Social)
configRuntime := *(c.Runtime)
configMatch := *(c.Match)
configTracker := *(c.Tracker)
configConsole := *(c.Console)
configLeaderboard := *(c.Leaderboard)
configMatchmaker := *(c.Matchmaker)
configIAP := *(c.IAP)
nc := &config{
Name: c.Name,
Datadir: c.Datadir,
ShutdownGraceSec: c.ShutdownGraceSec,
Logger: &configLogger,
Metrics: &configMetrics,
Session: &configSession,
Socket: &configSocket,
Database: &configDatabase,
Social: &configSocial,
Runtime: &configRuntime,
Match: &configMatch,
Tracker: &configTracker,
Console: &configConsole,
Leaderboard: &configLeaderboard,
Matchmaker: &configMatchmaker,
IAP: &configIAP,
}
nc.Socket.CertPEMBlock = make([]byte, len(c.Socket.CertPEMBlock))
copy(nc.Socket.CertPEMBlock, c.Socket.CertPEMBlock)
nc.Socket.KeyPEMBlock = make([]byte, len(c.Socket.KeyPEMBlock))
copy(nc.Socket.KeyPEMBlock, c.Socket.KeyPEMBlock)
if len(c.Socket.TLSCert) != 0 {
cert, err := tls.X509KeyPair(nc.Socket.CertPEMBlock, nc.Socket.KeyPEMBlock)
if err != nil {
return nil, err
}
nc.Socket.TLSCert = []tls.Certificate{cert}
}
nc.Database.Addresses = make([]string, len(c.Database.Addresses))
copy(nc.Database.Addresses, c.Database.Addresses)
nc.Runtime.Env = make([]string, len(c.Runtime.Env))
copy(nc.Runtime.Env, c.Runtime.Env)
nc.Runtime.Environment = make(map[string]string, len(c.Runtime.Environment))
for k, v := range c.Runtime.Environment {
nc.Runtime.Environment[k] = v
}
nc.Leaderboard.BlacklistRankCache = make([]string, len(c.Leaderboard.BlacklistRankCache))
copy(nc.Leaderboard.BlacklistRankCache, c.Leaderboard.BlacklistRankCache)
return nc, nil
}
func (c *config) GetName() string {
return c.Name
}
func (c *config) GetDataDir() string {
return c.Datadir
}
func (c *config) GetShutdownGraceSec() int {
return c.ShutdownGraceSec
}
func (c *config) GetLogger() *LoggerConfig {
return c.Logger
}
func (c *config) GetMetrics() *MetricsConfig {
return c.Metrics
}
func (c *config) GetSession() *SessionConfig {
return c.Session
}
func (c *config) GetSocket() *SocketConfig {
return c.Socket
}
func (c *config) GetDatabase() *DatabaseConfig {
return c.Database
}
func (c *config) GetSocial() *SocialConfig {
return c.Social
}
func (c *config) GetRuntime() *RuntimeConfig {
return c.Runtime
}
func (c *config) GetMatch() *MatchConfig {
return c.Match
}
func (c *config) GetTracker() *TrackerConfig {
return c.Tracker
}
func (c *config) GetConsole() *ConsoleConfig {
return c.Console
}
func (c *config) GetLeaderboard() *LeaderboardConfig {
return c.Leaderboard
}
func (c *config) GetMatchmaker() *MatchmakerConfig {
return c.Matchmaker
}
func (c *config) GetIAP() *IAPConfig {
return c.IAP
}
// LoggerConfig is configuration relevant to logging levels and output.
type LoggerConfig struct {
Level string `yaml:"level" json:"level" usage:"Log level to set. Valid values are 'debug', 'info', 'warn', 'error'. Default 'info'."`
Stdout bool `yaml:"stdout" json:"stdout" usage:"Log to standard console output (as well as to a file if set). Default true."`
File string `yaml:"file" json:"file" usage:"Log output to a file (as well as stdout if set). Make sure that the directory and the file is writable."`
Rotation bool `yaml:"rotation" json:"rotation" usage:"Rotate log files. Default is false."`
// Reference: https://godoc.org/gopkg.in/natefinch/lumberjack.v2
MaxSize int `yaml:"max_size" json:"max_size" usage:"The maximum size in megabytes of the log file before it gets rotated. It defaults to 100 megabytes."`
MaxAge int `yaml:"max_age" json:"max_age" usage:"The maximum number of days to retain old log files based on the timestamp encoded in their filename. The default is not to remove old log files based on age."`
MaxBackups int `yaml:"max_backups" json:"max_backups" usage:"The maximum number of old log files to retain. The default is to retain all old log files (though MaxAge may still cause them to get deleted.)"`
LocalTime bool `yaml:"local_time" json:"local_time" usage:"This determines if the time used for formatting the timestamps in backup files is the computer's local time. The default is to use UTC time."`
Compress bool `yaml:"compress" json:"compress" usage:"This determines if the rotated log files should be compressed using gzip."`
Format string `yaml:"format" json:"format" usage:"Set logging output format. Can either be 'JSON' or 'Stackdriver'. Default is 'JSON'."`
}
// NewLoggerConfig creates a new LoggerConfig struct.
func NewLoggerConfig() *LoggerConfig {
return &LoggerConfig{
Level: "info",
Stdout: true,
File: "",
Rotation: false,
MaxSize: 100,
MaxAge: 0,
MaxBackups: 0,
LocalTime: false,
Compress: false,
Format: "json",
}
}
// MetricsConfig is configuration relevant to metrics capturing and output.
type MetricsConfig struct {
ReportingFreqSec int `yaml:"reporting_freq_sec" json:"reporting_freq_sec" usage:"Frequency of metrics exports. Default is 60 seconds."`
Namespace string `yaml:"namespace" json:"namespace" usage:"Namespace for Prometheus metrics. It will always prepend node name."`
PrometheusPort int `yaml:"prometheus_port" json:"prometheus_port" usage:"Port to expose Prometheus. If '0' Prometheus exports are disabled."`
Prefix string `yaml:"prefix" json:"prefix" usage:"Prefix for metric names. Default is 'nakama', empty string '' disables the prefix."`
CustomPrefix string `yaml:"custom_prefix" json:"custom_prefix" usage:"Prefix for custom runtime metric names. Default is 'custom', empty string '' disables the prefix."`
}
// NewMetricsConfig creates a new MatricsConfig struct.
func NewMetricsConfig() *MetricsConfig {
return &MetricsConfig{
ReportingFreqSec: 60,
Namespace: "",
PrometheusPort: 0,
Prefix: "nakama",
CustomPrefix: "custom",
}
}
// SessionConfig is configuration relevant to the session.
type SessionConfig struct {
EncryptionKey string `yaml:"encryption_key" json:"encryption_key" usage:"The encryption key used to produce the client token."`
TokenExpirySec int64 `yaml:"token_expiry_sec" json:"token_expiry_sec" usage:"Token expiry in seconds."`
RefreshEncryptionKey string `yaml:"refresh_encryption_key" json:"refresh_encryption_key" usage:"The encryption key used to produce the client refresh token."`
RefreshTokenExpirySec int64 `yaml:"refresh_token_expiry_sec" json:"refresh_token_expiry_sec" usage:"Refresh token expiry in seconds."`
SingleSocket bool `yaml:"single_socket" json:"single_socket" usage:"Only allow one socket per user. Older sessions are disconnected. Default false."`
SingleMatch bool `yaml:"single_match" json:"single_match" usage:"Only allow one match per user. Older matches receive a leave. Requires single socket to enable. Default false."`
}
// NewSessionConfig creates a new SessionConfig struct.
func NewSessionConfig() *SessionConfig {
return &SessionConfig{
EncryptionKey: "defaultencryptionkey",
TokenExpirySec: 60,
RefreshEncryptionKey: "defaultrefreshencryptionkey",
RefreshTokenExpirySec: 3600,
}
}
// SocketConfig is configuration relevant to the transport socket and protocol.
type SocketConfig struct {
ServerKey string `yaml:"server_key" json:"server_key" usage:"Server key to use to establish a connection to the server."`
Port int `yaml:"port" json:"port" usage:"The port for accepting connections from the client for the given interface(s), address(es), and protocol(s). Default 7350."`
Address string `yaml:"address" json:"address" usage:"The IP address of the interface to listen for client traffic on. Default listen on all available addresses/interfaces."`
Protocol string `yaml:"protocol" json:"protocol" usage:"The network protocol to listen for traffic on. Possible values are 'tcp' for both IPv4 and IPv6, 'tcp4' for IPv4 only, or 'tcp6' for IPv6 only. Default 'tcp'."`
MaxMessageSizeBytes int64 `yaml:"max_message_size_bytes" json:"max_message_size_bytes" usage:"Maximum amount of data in bytes allowed to be read from the client socket per message. Used for real-time connections."`
MaxRequestSizeBytes int64 `yaml:"max_request_size_bytes" json:"max_request_size_bytes" usage:"Maximum amount of data in bytes allowed to be read from clients per request. Used for gRPC and HTTP connections."`
ReadBufferSizeBytes int `yaml:"read_buffer_size_bytes" json:"read_buffer_size_bytes" usage:"Size in bytes of the pre-allocated socket read buffer. Default 4096."`
WriteBufferSizeBytes int `yaml:"write_buffer_size_bytes" json:"write_buffer_size_bytes" usage:"Size in bytes of the pre-allocated socket write buffer. Default 4096."`
ReadTimeoutMs int `yaml:"read_timeout_ms" json:"read_timeout_ms" usage:"Maximum duration in milliseconds for reading the entire request. Used for HTTP connections."`
WriteTimeoutMs int `yaml:"write_timeout_ms" json:"write_timeout_ms" usage:"Maximum duration in milliseconds before timing out writes of the response. Used for HTTP connections."`
IdleTimeoutMs int `yaml:"idle_timeout_ms" json:"idle_timeout_ms" usage:"Maximum amount of time in milliseconds to wait for the next request when keep-alives are enabled. Used for HTTP connections."`
WriteWaitMs int `yaml:"write_wait_ms" json:"write_wait_ms" usage:"Time in milliseconds to wait for an ack from the client when writing data. Used for real-time connections."`
PongWaitMs int `yaml:"pong_wait_ms" json:"pong_wait_ms" usage:"Time in milliseconds to wait between pong messages received from the client. Used for real-time connections."`
PingPeriodMs int `yaml:"ping_period_ms" json:"ping_period_ms" usage:"Time in milliseconds to wait between sending ping messages to the client. This value must be less than the pong_wait_ms. Used for real-time connections."`
PingBackoffThreshold int `yaml:"ping_backoff_threshold" json:"ping_backoff_threshold" usage:"Minimum number of messages received from the client during a single ping period that will delay the sending of a ping until the next ping period, to avoid sending unnecessary pings on regularly active connections. Default 20."`
OutgoingQueueSize int `yaml:"outgoing_queue_size" json:"outgoing_queue_size" usage:"The maximum number of messages waiting to be sent to the client. If this is exceeded the client is considered too slow and will disconnect. Used when processing real-time connections."`
SSLCertificate string `yaml:"ssl_certificate" json:"ssl_certificate" usage:"Path to certificate file if you want the server to use SSL directly. Must also supply ssl_private_key. NOT recommended for production use."`
SSLPrivateKey string `yaml:"ssl_private_key" json:"ssl_private_key" usage:"Path to private key file if you want the server to use SSL directly. Must also supply ssl_certificate. NOT recommended for production use."`
CertPEMBlock []byte `yaml:"-" json:"-"` // Created by fully reading the file contents of SSLCertificate, not set from input args directly.
KeyPEMBlock []byte `yaml:"-" json:"-"` // Created by fully reading the file contents of SSLPrivateKey, not set from input args directly.
TLSCert []tls.Certificate `yaml:"-" json:"-"` // Created by processing CertPEMBlock and KeyPEMBlock, not set from input args directly.
}
// NewTransportConfig creates a new TransportConfig struct.
func NewSocketConfig() *SocketConfig {
return &SocketConfig{
ServerKey: "defaultkey",
Port: 7350,
Address: "",
Protocol: "tcp",
MaxMessageSizeBytes: 4096,
MaxRequestSizeBytes: 262_144, // 256 KB.
ReadBufferSizeBytes: 4096,
WriteBufferSizeBytes: 4096,
ReadTimeoutMs: 10 * 1000,
WriteTimeoutMs: 10 * 1000,
IdleTimeoutMs: 60 * 1000,
WriteWaitMs: 5000,
PongWaitMs: 25000,
PingPeriodMs: 15000,
PingBackoffThreshold: 20,
OutgoingQueueSize: 64,
SSLCertificate: "",
SSLPrivateKey: "",
}
}
// DatabaseConfig is configuration relevant to the Database storage.
type DatabaseConfig struct {
Addresses []string `yaml:"address" json:"address" usage:"List of database servers (username:password@address:port/dbname). Default 'root@localhost:26257'."`
ConnMaxLifetimeMs int `yaml:"conn_max_lifetime_ms" json:"conn_max_lifetime_ms" usage:"Time in milliseconds to reuse a database connection before the connection is killed and a new one is created. Default 3600000 (1 hour)."`
MaxOpenConns int `yaml:"max_open_conns" json:"max_open_conns" usage:"Maximum number of allowed open connections to the database. Default 100."`
MaxIdleConns int `yaml:"max_idle_conns" json:"max_idle_conns" usage:"Maximum number of allowed open but unused connections to the database. Default 100."`
DnsScanIntervalSec int `yaml:"dns_scan_interval_sec" json:"dns_scan_interval_sec" usage:"Number of seconds between scans looking for DNS resolution changes for the database hostname. Default 60."`
}
// NewDatabaseConfig creates a new DatabaseConfig struct.
func NewDatabaseConfig() *DatabaseConfig {
return &DatabaseConfig{
Addresses: []string{"root@localhost:26257"},
ConnMaxLifetimeMs: 3600000,
MaxOpenConns: 100,
MaxIdleConns: 100,
DnsScanIntervalSec: 60,
}
}
// SocialConfig is configuration relevant to the social authentication providers.
type SocialConfig struct {
Steam *SocialConfigSteam `yaml:"steam" json:"steam" usage:"Steam configuration."`
FacebookInstantGame *SocialConfigFacebookInstantGame `yaml:"facebook_instant_game" json:"facebook_instant_game" usage:"Facebook Instant Game configuration."`
FacebookLimitedLogin *SocialConfigFacebookLimitedLogin `yaml:"facebook_limited_login" json:"facebook_limited_login" usage:"Facebook Limited Login configuration."`
Apple *SocialConfigApple `yaml:"apple" json:"apple" usage:"Apple Sign In configuration."`
}
// SocialConfigSteam is configuration relevant to Steam.
type SocialConfigSteam struct {
PublisherKey string `yaml:"publisher_key" json:"publisher_key" usage:"Steam Publisher Key value."`
AppID int `yaml:"app_id" json:"app_id" usage:"Steam App ID."`
}
// SocialConfigFacebookInstantGame is configuration relevant to Facebook Instant Games.
type SocialConfigFacebookInstantGame struct {
AppSecret string `yaml:"app_secret" json:"app_secret" usage:"Facebook Instant App secret."`
}
// SocialConfigFacebookLimitedLogin is configuration relevant to Facebook Limited Login.
type SocialConfigFacebookLimitedLogin struct {
AppId string `yaml:"app_id" json:"app_id" usage:"Facebook Limited Login App ID."`
}
// SocialConfigApple is configuration relevant to Apple Sign In.
type SocialConfigApple struct {
BundleId string `yaml:"bundle_id" json:"bundle_id" usage:"Apple Sign In bundle ID."`
}
// NewSocialConfig creates a new SocialConfig struct.
func NewSocialConfig() *SocialConfig {
return &SocialConfig{
Steam: &SocialConfigSteam{
PublisherKey: "",
AppID: 0,
},
FacebookInstantGame: &SocialConfigFacebookInstantGame{
AppSecret: "",
},
FacebookLimitedLogin: &SocialConfigFacebookLimitedLogin{
AppId: "",
},
Apple: &SocialConfigApple{
BundleId: "",
},
}
}
// RuntimeConfig is configuration relevant to the Runtime Lua VM.
type RuntimeConfig struct {
Environment map[string]string `yaml:"-" json:"-"`
Env []string `yaml:"env" json:"env" usage:"Values to pass into Runtime as environment variables."`
Path string `yaml:"path" json:"path" usage:"Path for the server to scan for Lua and Go library files."`
HTTPKey string `yaml:"http_key" json:"http_key" usage:"Runtime HTTP Invocation key."`
MinCount int `yaml:"min_count" json:"min_count" usage:"Minimum number of Lua runtime instances to allocate. Default 0."` // Kept for backwards compatibility
LuaMinCount int `yaml:"lua_min_count" json:"lua_min_count" usage:"Minimum number of Lua runtime instances to allocate. Default 16."`
MaxCount int `yaml:"max_count" json:"max_count" usage:"Maximum number of Lua runtime instances to allocate. Default 0."` // Kept for backwards compatibility
LuaMaxCount int `yaml:"lua_max_count" json:"lua_max_count" usage:"Maximum number of Lua runtime instances to allocate. Default 48."`
JsMinCount int `yaml:"js_min_count" json:"js_min_count" usage:"Maximum number of Javascript runtime instances to allocate. Default 48."`
JsMaxCount int `yaml:"js_max_count" json:"js_max_count" usage:"Maximum number of Javascript runtime instances to allocate. Default 48."`
CallStackSize int `yaml:"call_stack_size" json:"call_stack_size" usage:"Size of each runtime instance's call stack. Default 0."` // Kept for backwards compatibility
LuaCallStackSize int `yaml:"lua_call_stack_size" json:"lua_call_stack_size" usage:"Size of each runtime instance's call stack. Default 128."`
RegistrySize int `yaml:"registry_size" json:"registry_size" usage:"Size of each Lua runtime instance's registry. Default 0."` // Kept for backwards compatibility
LuaRegistrySize int `yaml:"lua_registry_size" json:"lua_registry_size" usage:"Size of each Lua runtime instance's registry. Default 512."`
EventQueueSize int `yaml:"event_queue_size" json:"event_queue_size" usage:"Size of the event queue buffer. Default 65536."`
EventQueueWorkers int `yaml:"event_queue_workers" json:"event_queue_workers" usage:"Number of workers to use for concurrent processing of events. Default 8."`
ReadOnlyGlobals bool `yaml:"read_only_globals" json:"read_only_globals" usage:"When enabled marks all Lua runtime global tables as read-only to reduce memory footprint. Default true."` // Kept for backwards compatibility
LuaReadOnlyGlobals bool `yaml:"lua_read_only_globals" json:"lua_read_only_globals" usage:"When enabled marks all Lua runtime global tables as read-only to reduce memory footprint. Default true."`
JsReadOnlyGlobals bool `yaml:"js_read_only_globals" json:"js_read_only_globals" usage:"When enabled marks all Javascript runtime globals as read-only to reduce memory footprint. Default true."`
LuaApiStacktrace bool `yaml:"lua_api_stacktrace" json:"lua_api_stacktrace" usage:"Include the Lua stacktrace in error responses returned to the client. Default false."`
JsEntrypoint string `yaml:"js_entrypoint" json:"js_entrypoint" usage:"Specifies the location of the bundled JavaScript runtime source code."`
}
// Function to allow backwards compatibility for MinCount config
func (r *RuntimeConfig) GetLuaMinCount() int {
if r.MinCount != 0 {
return r.MinCount
}
return r.LuaMinCount
}
// Function to allow backwards compatibility for MaxCount config
func (r *RuntimeConfig) GetLuaMaxCount() int {
if r.MaxCount != 0 {
return r.MaxCount
}
return r.LuaMaxCount
}
// Function to allow backwards compatibility for CallStackSize config
func (r *RuntimeConfig) GetLuaCallStackSize() int {
if r.CallStackSize != 0 {
return r.CallStackSize
}
return r.LuaCallStackSize
}
// Function to allow backwards compatibility for RegistrySize config
func (r *RuntimeConfig) GetLuaRegistrySize() int {
if r.RegistrySize != 0 {
return r.RegistrySize
}
return r.LuaRegistrySize
}
// Function to allow backwards compatibility for LuaReadOnlyGlobals config
func (r *RuntimeConfig) GetLuaReadOnlyGlobals() bool {
if r.ReadOnlyGlobals != true {
return r.ReadOnlyGlobals
}
return r.LuaReadOnlyGlobals
}
// NewRuntimeConfig creates a new RuntimeConfig struct.
func NewRuntimeConfig() *RuntimeConfig {
return &RuntimeConfig{
Environment: make(map[string]string, 0),
Env: make([]string, 0),
Path: "",
HTTPKey: "defaulthttpkey",
LuaMinCount: 16,
LuaMaxCount: 48,
LuaCallStackSize: 128,
LuaRegistrySize: 512,
JsMinCount: 16,
JsMaxCount: 32,
EventQueueSize: 65536,
EventQueueWorkers: 8,
ReadOnlyGlobals: true,
LuaReadOnlyGlobals: true,
JsReadOnlyGlobals: true,
LuaApiStacktrace: false,
}
}
// MatchConfig is configuration relevant to authoritative realtime multiplayer matches.
type MatchConfig struct {
InputQueueSize int `yaml:"input_queue_size" json:"input_queue_size" usage:"Size of the authoritative match buffer that stores client messages until they can be processed by the next tick. Default 128."`
CallQueueSize int `yaml:"call_queue_size" json:"call_queue_size" usage:"Size of the authoritative match buffer that sequences calls to match handler callbacks to ensure no overlaps. Default 128."`
SignalQueueSize int `yaml:"signal_queue_size" json:"signal_queue_size" usage:"Size of the authoritative match buffer that sequences signal operations to match handler callbacks to ensure no overlaps. Default 10."`
JoinAttemptQueueSize int `yaml:"join_attempt_queue_size" json:"join_attempt_queue_size" usage:"Size of the authoritative match buffer that limits the number of in-progress join attempts. Default 128."`
DeferredQueueSize int `yaml:"deferred_queue_size" json:"deferred_queue_size" usage:"Size of the authoritative match buffer that holds deferred message broadcasts until the end of each loop execution. Default 128."`
JoinMarkerDeadlineMs int `yaml:"join_marker_deadline_ms" json:"join_marker_deadline_ms" usage:"Deadline in milliseconds that client authoritative match joins will wait for match handlers to acknowledge joins. Default 15000."`
MaxEmptySec int `yaml:"max_empty_sec" json:"max_empty_sec" usage:"Maximum number of consecutive seconds that authoritative matches are allowed to be empty before they are stopped. 0 indicates no maximum. Default 0."`
LabelUpdateIntervalMs int `yaml:"label_update_interval_ms" json:"label_update_interval_ms" usage:"Time in milliseconds between match label update batch processes. Default 1000."`
}
// NewMatchConfig creates a new MatchConfig struct.
func NewMatchConfig() *MatchConfig {
return &MatchConfig{
InputQueueSize: 128,
CallQueueSize: 128,
SignalQueueSize: 10,
JoinAttemptQueueSize: 128,
DeferredQueueSize: 128,
JoinMarkerDeadlineMs: 15000,
MaxEmptySec: 0,
LabelUpdateIntervalMs: 1000,
}
}
// TrackerConfig is configuration relevant to the presence tracker.
type TrackerConfig struct {
EventQueueSize int `yaml:"event_queue_size" json:"event_queue_size" usage:"Size of the tracker presence event buffer. Increase if the server is expected to generate a large number of presence events in a short time. Default 1024."`
}
// NewTrackerConfig creates a new TrackerConfig struct.
func NewTrackerConfig() *TrackerConfig {
return &TrackerConfig{
EventQueueSize: 1024,
}
}
// ConsoleConfig is configuration relevant to the embedded console.
type ConsoleConfig struct {
Port int `yaml:"port" json:"port" usage:"The port for accepting connections for the embedded console, listening on all interfaces."`
Address string `yaml:"address" json:"address" usage:"The IP address of the interface to listen for console traffic on. Default listen on all available addresses/interfaces."`
MaxMessageSizeBytes int64 `yaml:"max_message_size_bytes" json:"max_message_size_bytes" usage:"Maximum amount of data in bytes allowed to be read from the client socket per message."`
ReadTimeoutMs int `yaml:"read_timeout_ms" json:"read_timeout_ms" usage:"Maximum duration in milliseconds for reading the entire request."`
WriteTimeoutMs int `yaml:"write_timeout_ms" json:"write_timeout_ms" usage:"Maximum duration in milliseconds before timing out writes of the response."`
IdleTimeoutMs int `yaml:"idle_timeout_ms" json:"idle_timeout_ms" usage:"Maximum amount of time in milliseconds to wait for the next request when keep-alives are enabled."`
Username string `yaml:"username" json:"username" usage:"Username for the embedded console. Default username is 'admin'."`
Password string `yaml:"password" json:"password" usage:"Password for the embedded console. Default password is 'password'."`
TokenExpirySec int64 `yaml:"token_expiry_sec" json:"token_expiry_sec" usage:"Token expiry in seconds. Default 86400."`
SigningKey string `yaml:"signing_key" json:"signing_key" usage:"Key used to sign console session tokens."`
}
// NewConsoleConfig creates a new ConsoleConfig struct.
func NewConsoleConfig() *ConsoleConfig {
return &ConsoleConfig{
Port: 7351,
MaxMessageSizeBytes: 4_194_304, // 4 MB.
ReadTimeoutMs: 10 * 1000,
WriteTimeoutMs: 60 * 1000,
IdleTimeoutMs: 300 * 1000,
Username: "admin",
Password: "password",
TokenExpirySec: 86400,
SigningKey: "defaultsigningkey",
}
}
// LeaderboardConfig is configuration relevant to the leaderboard system.
type LeaderboardConfig struct {
BlacklistRankCache []string `yaml:"blacklist_rank_cache" json:"blacklist_rank_cache" usage:"Disable rank cache for leaderboards with matching identifiers. To disable rank cache entirely, use '*', otherwise leave blank to enable rank cache."`
CallbackQueueSize int `yaml:"callback_queue_size" json:"callback_queue_size" usage:"Size of the leaderboard and tournament callback queue that sequences expiry/reset/end invocations. Default 65536."`
CallbackQueueWorkers int `yaml:"callback_queue_workers" json:"callback_queue_workers" usage:"Number of workers to use for concurrent processing of leaderboard and tournament callbacks. Default 8."`
}
// NewLeaderboardConfig creates a new LeaderboardConfig struct.
func NewLeaderboardConfig() *LeaderboardConfig {
return &LeaderboardConfig{
BlacklistRankCache: []string{},
CallbackQueueSize: 65536,
CallbackQueueWorkers: 8,
}
}
type MatchmakerConfig struct {
MaxTickets int `yaml:"max_tickets" json:"max_tickets" usage:"Maximum number of concurrent matchmaking tickets allowed per session or party. Default 3."`
IntervalSec int `yaml:"interval_sec" json:"interval_sec" usage:"How quickly the matchmaker attempts to form matches, in seconds. Default 15."`
MaxIntervals int `yaml:"max_intervals" json:"max_intervals" usage:"How many intervals the matchmaker attempts to find matches at the max player count, before allowing min count. Default 2."`
BatchPoolSize int `yaml:"batch_pool_size" json:"batch_pool_size" usage:"Number of concurrent indexing batches that will be allocated."`
RevPrecision bool `yaml:"rev_precision" json:"rev_precision" usage:"Reverse matching precision. Default true."`
RevThreshold int `yaml:"rev_threshold" json:"rev_threshold" usage:"Reverse matching threshold. Default 1."`
}
func NewMatchmakerConfig() *MatchmakerConfig {
return &MatchmakerConfig{
MaxTickets: 3,
IntervalSec: 15,
MaxIntervals: 2,
BatchPoolSize: 32,
RevPrecision: true,
RevThreshold: 1,
}
}
type IAPConfig struct {
Apple *IAPAppleConfig `yaml:"apple" json:"apple" usage:"Apple App Store purchase validation configuration."`
Google *IAPGoogleConfig `yaml:"google" json:"google" usage:"Google Play Store purchase validation configuration."`
Huawei *IAPHuaweiConfig `yaml:"huawei" json:"huawei" usage:"Huawei purchase validation configuration."`
}
func NewIAPConfig() *IAPConfig {
return &IAPConfig{
Apple: &IAPAppleConfig{},
Google: &IAPGoogleConfig{},
Huawei: &IAPHuaweiConfig{},
}
}
type IAPAppleConfig struct {
SharedPassword string `yaml:"shared_password" json:"shared_password" usage:"Your Apple Store App IAP shared password. Only necessary for validation of auto-renewable subscriptions."`
NotificationsEndpointId string `yaml:"notifications_endpoint_id" json:"notifications_endpoint_id" usage:"The callback endpoint identifier for Apple Store subscription notifications."`
}
type IAPGoogleConfig struct {
ClientEmail string `yaml:"client_email" json:"client_email" usage:"Google Service Account client email."`
PrivateKey string `yaml:"private_key" json:"private_key" usage:"Google Service Account private key."`
NotificationsEndpointId string `yaml:"notifications_endpoint_id" json:"notifications_endpoint_id" usage:"The callback endpoint identifier for Android subscription notifications."`
}
type IAPHuaweiConfig struct {
PublicKey string `yaml:"public_key" json:"public_key" usage:"Huawei IAP store Base64 encoded Public Key."`
ClientID string `yaml:"client_id" json:"client_id" usage:"Huawei OAuth client secret."`
ClientSecret string `yaml:"client_secret" json:"client_secret" usage:"Huawei OAuth app client secret."`
}