config.go 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. package config
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "net/url"
  7. "strings"
  8. )
  9. var (
  10. // Simple error for duplicate listener definitions
  11. errDuplicateListener = errors.New("duplicate listener definition")
  12. // Simple error for missing BOS listeners
  13. errNoBOSListeners = errors.New("at least one BOS listener is required")
  14. )
  15. // Custom error types for URI-related errors
  16. type uriFormatError struct {
  17. URI string
  18. Err error
  19. }
  20. func (e uriFormatError) Error() string {
  21. return fmt.Sprintf("invalid listener URI %q: %v. Valid format: SCHEME://HOST:PORT (e.g., LOCAL://0.0.0.0:5190)", e.URI, e.Err)
  22. }
  23. type Build struct {
  24. Version string `json:"version"`
  25. Commit string `json:"commit"`
  26. Date string `json:"date"`
  27. }
  28. type Listener struct {
  29. BOSListenAddress string
  30. BOSAdvertisedHostPlain string
  31. BOSAdvertisedHostSSL string
  32. KerberosListenAddress string
  33. HasSSL bool
  34. }
  35. //go:generate go run ../cmd/config_generator unix settings.env ssl
  36. type Config struct {
  37. BOSListeners []string `envconfig:"OSCAR_LISTENERS" required:"true" basic:"LOCAL://0.0.0.0:5190" ssl:"LOCAL://0.0.0.0:5190" description:"Network listeners for core OSCAR services. For multi-homed servers, allows users to connect from multiple networks. For example, you can allow both LAN and Internet clients to connect to the same server using different connection settings.\n\nFormat:\n\t- Comma-separated list of [NAME]://[HOSTNAME]:[PORT]\n\t- Listener names and ports must be unique\n\t- Listener names are user-defined\n\t- Each listener needs a listener in OSCAR_ADVERTISED_LISTENERS_PLAIN\n\nExamples:\n\t// Listen on all interfaces\n\tLAN://0.0.0.0:5190\n\t// Separate Internet and LAN config\n\tWAN://142.250.176.206:5190,LAN://192.168.1.10:5191"`
  38. BOSAdvertisedHostsPlain []string `envconfig:"OSCAR_ADVERTISED_LISTENERS_PLAIN" required:"true" basic:"LOCAL://127.0.0.1:5190" ssl:"LOCAL://127.0.0.1:5190" description:"Hostnames published by the server that clients connect to for accessing various OSCAR services. These hostnames are NOT the bind addresses. For multi-homed use servers, allows clients to connect using separate hostnames per network.\n\nFormat:\n\t- Comma-separated list of [NAME]://[HOSTNAME]:[PORT]\n\t- Each listener config must correspond to a config in OSCAR_LISTENERS\n\t- Clients MUST be able to connect to these hostnames\n\nExamples:\n\t// Local LAN config, server behind NAT\n\tLAN://192.168.1.10:5190\n\t// Separate Internet and LAN config\n\tWAN://aim.example.com:5190,LAN://192.168.1.10:5191"`
  39. BOSAdvertisedHostsSSL []string `envconfig:"OSCAR_ADVERTISED_LISTENERS_SSL" required:"false" basic:"" ssl:"LOCAL://ras.dev:5193" description:"Same as OSCAR_ADVERTISED_LISTENERS_PLAIN, except the hostname is for the server that terminates SSL."`
  40. KerberosListeners []string `envconfig:"KERBEROS_LISTENERS" required:"false" basic:"" ssl:"LOCAL://0.0.0.0:1088" description:"Network listeners for Kerberos authentication. See OSCAR_LISTENERS doc for more details.\n\nExamples:\n\t// Listen on all interfaces\n\tLAN://0.0.0.0:1088\n\t// Separate Internet and LAN config\n\tWAN://142.250.176.206:1088,LAN://192.168.1.10:1087"`
  41. TOCListeners []string `envconfig:"TOC_LISTENERS" required:"true" basic:"0.0.0.0:9898" ssl:"0.0.0.0:9898" description:"Network listeners for TOC protocol service.\n\nFormat: Comma-separated list of hostname:port pairs.\n\nExamples:\n\t// All interfaces\n\t0.0.0.0:9898\n\t// Multiple listeners\n\t0.0.0.0:9898,192.168.1.10:9899"`
  42. APIListener string `envconfig:"API_LISTENER" required:"true" basic:"127.0.0.1:8080" ssl:"127.0.0.1:8080" description:"Network listener for management API binds to. Only 1 listener can be specified. (Default 127.0.0.1 restricts to same machine only)."`
  43. DBPath string `envconfig:"DB_PATH" required:"true" basic:"oscar.sqlite" ssl:"oscar.sqlite" description:"The path to the SQLite database file. The file and DB schema are auto-created if they doesn't exist."`
  44. DisableAuth bool `envconfig:"DISABLE_AUTH" required:"true" basic:"true" ssl:"true" description:"Disable password check and auto-create new users at login time. Useful for quickly creating new accounts during development without having to register new users via the management API."`
  45. DisableMultiLoginNotif bool `envconfig:"DISABLE_MULTI_LOGIN_NOTIF" required:"false" basic:"true" ssl:"true" description:"Disable notification sent when another client signs in with the same screen name."`
  46. LogLevel string `envconfig:"LOG_LEVEL" required:"true" basic:"info" ssl:"info" description:"Set logging granularity. Possible values: 'trace', 'debug', 'info', 'warn', 'error'."`
  47. }
  48. func (c *Config) ParseListenersCfg() ([]Listener, error) {
  49. // Helper function to parse and validate a single URI
  50. parseURI := func(uriStr string) (*url.URL, error) {
  51. uriStr = strings.TrimSpace(uriStr)
  52. if uriStr == "" {
  53. return nil, nil
  54. }
  55. u, err := url.Parse(uriStr)
  56. if err != nil {
  57. return nil, uriFormatError{URI: uriStr, Err: err}
  58. }
  59. switch {
  60. case u.Scheme == "":
  61. return nil, uriFormatError{URI: uriStr, Err: errors.New("missing scheme")}
  62. case u.Hostname() == "":
  63. return nil, uriFormatError{URI: uriStr, Err: errors.New("missing host")}
  64. case u.Port() == "":
  65. return nil, uriFormatError{URI: uriStr, Err: errors.New("missing port")}
  66. }
  67. return u, nil
  68. }
  69. m := make(map[string]*Listener)
  70. // Parse BOS listeners
  71. for _, uriStr := range c.BOSListeners {
  72. u, err := parseURI(uriStr)
  73. if err != nil {
  74. return nil, err
  75. }
  76. if u == nil {
  77. continue
  78. }
  79. if _, ok := m[u.Scheme]; !ok {
  80. m[u.Scheme] = &Listener{}
  81. }
  82. if m[u.Scheme].BOSListenAddress != "" {
  83. return nil, errDuplicateListener
  84. }
  85. m[u.Scheme].BOSListenAddress = net.JoinHostPort(u.Hostname(), u.Port())
  86. }
  87. // Parse plaintext BOS advertised listeners
  88. for _, uriStr := range c.BOSAdvertisedHostsPlain {
  89. u, err := parseURI(uriStr)
  90. if err != nil {
  91. return nil, err
  92. }
  93. if u == nil {
  94. continue
  95. }
  96. if _, ok := m[u.Scheme]; !ok {
  97. m[u.Scheme] = &Listener{}
  98. }
  99. if m[u.Scheme].BOSAdvertisedHostPlain != "" {
  100. return nil, errDuplicateListener
  101. }
  102. m[u.Scheme].BOSAdvertisedHostPlain = net.JoinHostPort(u.Hostname(), u.Port())
  103. }
  104. // Parse SSL BOS advertised listeners
  105. for _, uriStr := range c.BOSAdvertisedHostsSSL {
  106. u, err := parseURI(uriStr)
  107. if err != nil {
  108. return nil, err
  109. }
  110. if u == nil {
  111. continue
  112. }
  113. if _, ok := m[u.Scheme]; !ok {
  114. m[u.Scheme] = &Listener{}
  115. }
  116. if m[u.Scheme].BOSAdvertisedHostSSL != "" {
  117. return nil, errDuplicateListener
  118. }
  119. m[u.Scheme].HasSSL = true
  120. m[u.Scheme].BOSAdvertisedHostSSL = net.JoinHostPort(u.Hostname(), u.Port())
  121. }
  122. // Parse Kerberos listeners
  123. for _, uriStr := range c.KerberosListeners {
  124. u, err := parseURI(uriStr)
  125. if err != nil {
  126. return nil, err
  127. }
  128. if u == nil {
  129. continue
  130. }
  131. if _, ok := m[u.Scheme]; !ok {
  132. m[u.Scheme] = &Listener{}
  133. }
  134. if m[u.Scheme].KerberosListenAddress != "" {
  135. return nil, errDuplicateListener
  136. }
  137. m[u.Scheme].KerberosListenAddress = net.JoinHostPort(u.Hostname(), u.Port())
  138. }
  139. ret := make([]Listener, 0, len(m))
  140. for k, v := range m {
  141. switch {
  142. case v.BOSAdvertisedHostPlain == "":
  143. return nil, fmt.Errorf("missing BOS advertise address for listener `%s://`", k)
  144. case v.BOSListenAddress == "":
  145. return nil, fmt.Errorf("missing BOS listen address for listener `%s://`", k)
  146. }
  147. ret = append(ret, *v)
  148. }
  149. if len(ret) == 0 {
  150. return nil, errNoBOSListeners
  151. }
  152. return ret, nil
  153. }
  154. func (c *Config) Validate() error {
  155. // Validate TOCListeners (format: hostname:port pairs)
  156. for _, listener := range c.TOCListeners {
  157. listener = strings.TrimSpace(listener)
  158. if listener == "" {
  159. continue
  160. }
  161. host, port, err := net.SplitHostPort(listener)
  162. if err != nil {
  163. return fmt.Errorf("invalid TOC listener %q: %v. Valid format: HOST:PORT (e.g., 0.0.0.0:9898)", listener, err)
  164. }
  165. if host == "" {
  166. return fmt.Errorf("invalid TOC listener %q: missing host. Valid format: HOST:PORT (e.g., 0.0.0.0:9898)", listener)
  167. }
  168. if port == "" {
  169. return fmt.Errorf("invalid TOC listener %q: missing port. Valid format: HOST:PORT (e.g., 0.0.0.0:9898)", listener)
  170. }
  171. }
  172. // Validate APIListener (format: hostname:port pair, no scheme)
  173. apiListener := strings.TrimSpace(c.APIListener)
  174. if apiListener == "" {
  175. return fmt.Errorf("APIListener is required and cannot be empty")
  176. }
  177. host, port, err := net.SplitHostPort(apiListener)
  178. if err != nil {
  179. return fmt.Errorf("invalid API listener %q: %v. Valid format: HOST:PORT (e.g., 127.0.0.1:8080)", c.APIListener, err)
  180. }
  181. if host == "" {
  182. return fmt.Errorf("invalid API listener %q: missing host. Valid format: HOST:PORT (e.g., 127.0.0.1:8080)", c.APIListener)
  183. }
  184. if port == "" {
  185. return fmt.Errorf("invalid API listener %q: missing port. Valid format: HOST:PORT (e.g., 127.0.0.1:8080)", c.APIListener)
  186. }
  187. return nil
  188. }