setting.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. // Copyright 2014 Unknwon
  2. // Copyright 2014 Torkel Ödegaard
  3. package setting
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "fmt"
  8. "net/url"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "regexp"
  13. "runtime"
  14. "strings"
  15. "github.com/macaron-contrib/session"
  16. "gopkg.in/ini.v1"
  17. "github.com/grafana/grafana/pkg/log"
  18. "github.com/grafana/grafana/pkg/util"
  19. )
  20. type Scheme string
  21. const (
  22. HTTP Scheme = "http"
  23. HTTPS Scheme = "https"
  24. )
  25. const (
  26. DEV string = "development"
  27. PROD string = "production"
  28. TEST string = "test"
  29. )
  30. var (
  31. // App settings.
  32. Env string = DEV
  33. AppUrl string
  34. AppSubUrl string
  35. // build
  36. BuildVersion string
  37. BuildCommit string
  38. BuildStamp int64
  39. // Paths
  40. LogsPath string
  41. HomePath string
  42. DataPath string
  43. // Log settings.
  44. LogModes []string
  45. LogConfigs []util.DynMap
  46. // Http server options
  47. Protocol Scheme
  48. Domain string
  49. HttpAddr, HttpPort string
  50. SshPort int
  51. CertFile, KeyFile string
  52. RouterLogging bool
  53. StaticRootPath string
  54. EnableGzip bool
  55. EnforceDomain bool
  56. // Security settings.
  57. SecretKey string
  58. LogInRememberDays int
  59. CookieUserName string
  60. CookieRememberName string
  61. DisableGravatar bool
  62. EmailCodeValidMinutes int
  63. // User settings
  64. AllowUserSignUp bool
  65. AllowUserOrgCreate bool
  66. AutoAssignOrg bool
  67. AutoAssignOrgRole string
  68. // Http auth
  69. AdminUser string
  70. AdminPassword string
  71. AnonymousEnabled bool
  72. AnonymousOrgName string
  73. AnonymousOrgRole string
  74. // Auth proxy settings
  75. AuthProxyEnabled bool
  76. AuthProxyHeaderName string
  77. AuthProxyHeaderProperty string
  78. AuthProxyAutoSignUp bool
  79. // Basic Auth
  80. BasicAuthEnabled bool
  81. // Session settings.
  82. SessionOptions session.Options
  83. // Global setting objects.
  84. Cfg *ini.File
  85. ConfRootPath string
  86. IsWindows bool
  87. // PhantomJs Rendering
  88. ImagesDir string
  89. PhantomDir string
  90. // for logging purposes
  91. configFiles []string
  92. appliedCommandLineProperties []string
  93. appliedEnvOverrides []string
  94. ReportingEnabled bool
  95. GoogleAnalyticsId string
  96. // LDAP
  97. LdapEnabled bool
  98. LdapServers []*LdapServerConf
  99. // SMTP email settings
  100. Smtp SmtpSettings
  101. )
  102. type CommandLineArgs struct {
  103. Config string
  104. HomePath string
  105. Args []string
  106. }
  107. func init() {
  108. IsWindows = runtime.GOOS == "windows"
  109. log.NewLogger(0, "console", `{"level": 0}`)
  110. }
  111. func parseAppUrlAndSubUrl(section *ini.Section) (string, string) {
  112. appUrl := section.Key("root_url").MustString("http://localhost:3000/")
  113. if appUrl[len(appUrl)-1] != '/' {
  114. appUrl += "/"
  115. }
  116. // Check if has app suburl.
  117. url, err := url.Parse(appUrl)
  118. if err != nil {
  119. log.Fatal(4, "Invalid root_url(%s): %s", appUrl, err)
  120. }
  121. appSubUrl := strings.TrimSuffix(url.Path, "/")
  122. return appUrl, appSubUrl
  123. }
  124. func ToAbsUrl(relativeUrl string) string {
  125. return AppUrl + relativeUrl
  126. }
  127. func applyEnvVariableOverrides() {
  128. appliedEnvOverrides = make([]string, 0)
  129. for _, section := range Cfg.Sections() {
  130. for _, key := range section.Keys() {
  131. sectionName := strings.ToUpper(strings.Replace(section.Name(), ".", "_", -1))
  132. keyName := strings.ToUpper(strings.Replace(key.Name(), ".", "_", -1))
  133. envKey := fmt.Sprintf("GF_%s_%s", sectionName, keyName)
  134. envValue := os.Getenv(envKey)
  135. if len(envValue) > 0 {
  136. key.SetValue(envValue)
  137. appliedEnvOverrides = append(appliedEnvOverrides, fmt.Sprintf("%s=%s", envKey, envValue))
  138. }
  139. }
  140. }
  141. }
  142. func applyCommandLineDefaultProperties(props map[string]string) {
  143. appliedCommandLineProperties = make([]string, 0)
  144. for _, section := range Cfg.Sections() {
  145. for _, key := range section.Keys() {
  146. keyString := fmt.Sprintf("default.%s.%s", section.Name(), key.Name())
  147. value, exists := props[keyString]
  148. if exists {
  149. key.SetValue(value)
  150. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  151. }
  152. }
  153. }
  154. }
  155. func applyCommandLineProperties(props map[string]string) {
  156. for _, section := range Cfg.Sections() {
  157. for _, key := range section.Keys() {
  158. keyString := fmt.Sprintf("%s.%s", section.Name(), key.Name())
  159. value, exists := props[keyString]
  160. if exists {
  161. key.SetValue(value)
  162. appliedCommandLineProperties = append(appliedCommandLineProperties, fmt.Sprintf("%s=%s", keyString, value))
  163. }
  164. }
  165. }
  166. }
  167. func getCommandLineProperties(args []string) map[string]string {
  168. props := make(map[string]string)
  169. for _, arg := range args {
  170. if !strings.HasPrefix(arg, "cfg:") {
  171. continue
  172. }
  173. trimmed := strings.TrimPrefix(arg, "cfg:")
  174. parts := strings.Split(trimmed, "=")
  175. if len(parts) != 2 {
  176. log.Fatal(3, "Invalid command line argument", arg)
  177. return nil
  178. }
  179. props[parts[0]] = parts[1]
  180. }
  181. return props
  182. }
  183. func makeAbsolute(path string, root string) string {
  184. if filepath.IsAbs(path) {
  185. return path
  186. }
  187. return filepath.Join(root, path)
  188. }
  189. func evalEnvVarExpression(value string) string {
  190. regex := regexp.MustCompile(`\${(\w+)}`)
  191. return regex.ReplaceAllStringFunc(value, func(envVar string) string {
  192. envVar = strings.TrimPrefix(envVar, "${")
  193. envVar = strings.TrimSuffix(envVar, "}")
  194. envValue := os.Getenv(envVar)
  195. return envValue
  196. })
  197. }
  198. func evalConfigValues() {
  199. for _, section := range Cfg.Sections() {
  200. for _, key := range section.Keys() {
  201. key.SetValue(evalEnvVarExpression(key.Value()))
  202. }
  203. }
  204. }
  205. func loadSpecifedConfigFile(configFile string) {
  206. if configFile == "" {
  207. configFile = filepath.Join(HomePath, "conf/custom.ini")
  208. // return without error if custom file does not exist
  209. if !pathExists(configFile) {
  210. return
  211. }
  212. }
  213. userConfig, err := ini.Load(configFile)
  214. userConfig.BlockMode = false
  215. if err != nil {
  216. log.Fatal(3, "Failed to parse %v, %v", configFile, err)
  217. }
  218. for _, section := range userConfig.Sections() {
  219. for _, key := range section.Keys() {
  220. if key.Value() == "" {
  221. continue
  222. }
  223. defaultSec, err := Cfg.GetSection(section.Name())
  224. if err != nil {
  225. log.Error(3, "Unknown config section %s defined in %s", section.Name(), configFile)
  226. continue
  227. }
  228. defaultKey, err := defaultSec.GetKey(key.Name())
  229. if err != nil {
  230. log.Error(3, "Unknown config key %s defined in section %s, in file", key.Name(), section.Name(), configFile)
  231. continue
  232. }
  233. defaultKey.SetValue(key.Value())
  234. }
  235. }
  236. configFiles = append(configFiles, configFile)
  237. }
  238. func loadConfiguration(args *CommandLineArgs) {
  239. var err error
  240. // load config defaults
  241. defaultConfigFile := path.Join(HomePath, "conf/defaults.ini")
  242. configFiles = append(configFiles, defaultConfigFile)
  243. Cfg, err = ini.Load(defaultConfigFile)
  244. Cfg.BlockMode = false
  245. if err != nil {
  246. log.Fatal(3, "Failed to parse defaults.ini, %v", err)
  247. }
  248. // command line props
  249. commandLineProps := getCommandLineProperties(args.Args)
  250. // load default overrides
  251. applyCommandLineDefaultProperties(commandLineProps)
  252. // init logging before specific config so we can log errors from here on
  253. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  254. initLogging(args)
  255. // load specified config file
  256. loadSpecifedConfigFile(args.Config)
  257. // apply environment overrides
  258. applyEnvVariableOverrides()
  259. // apply command line overrides
  260. applyCommandLineProperties(commandLineProps)
  261. // evaluate config values containing environment variables
  262. evalConfigValues()
  263. // update data path and logging config
  264. DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
  265. initLogging(args)
  266. }
  267. func pathExists(path string) bool {
  268. _, err := os.Stat(path)
  269. if err == nil {
  270. return true
  271. }
  272. if os.IsNotExist(err) {
  273. return false
  274. }
  275. return false
  276. }
  277. func setHomePath(args *CommandLineArgs) {
  278. if args.HomePath != "" {
  279. HomePath = args.HomePath
  280. return
  281. }
  282. HomePath, _ = filepath.Abs(".")
  283. // check if homepath is correct
  284. if pathExists(filepath.Join(HomePath, "conf/defaults.ini")) {
  285. return
  286. }
  287. // try down one path
  288. if pathExists(filepath.Join(HomePath, "../conf/defaults.ini")) {
  289. HomePath = filepath.Join(HomePath, "../")
  290. }
  291. }
  292. func NewConfigContext(args *CommandLineArgs) {
  293. setHomePath(args)
  294. loadConfiguration(args)
  295. Env = Cfg.Section("").Key("app_mode").MustString("development")
  296. server := Cfg.Section("server")
  297. AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
  298. Protocol = HTTP
  299. if server.Key("protocol").MustString("http") == "https" {
  300. Protocol = HTTPS
  301. CertFile = server.Key("cert_file").String()
  302. KeyFile = server.Key("cert_key").String()
  303. }
  304. Domain = server.Key("domain").MustString("localhost")
  305. HttpAddr = server.Key("http_addr").MustString("0.0.0.0")
  306. HttpPort = server.Key("http_port").MustString("3000")
  307. StaticRootPath = makeAbsolute(server.Key("static_root_path").String(), HomePath)
  308. RouterLogging = server.Key("router_logging").MustBool(false)
  309. EnableGzip = server.Key("enable_gzip").MustBool(false)
  310. EnforceDomain = server.Key("enforce_domain").MustBool(false)
  311. security := Cfg.Section("security")
  312. SecretKey = security.Key("secret_key").String()
  313. LogInRememberDays = security.Key("login_remember_days").MustInt()
  314. CookieUserName = security.Key("cookie_username").String()
  315. CookieRememberName = security.Key("cookie_remember_name").String()
  316. DisableGravatar = security.Key("disable_gravatar").MustBool(true)
  317. // admin
  318. AdminUser = security.Key("admin_user").String()
  319. AdminPassword = security.Key("admin_password").String()
  320. users := Cfg.Section("users")
  321. AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
  322. AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
  323. AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
  324. AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
  325. // anonymous access
  326. AnonymousEnabled = Cfg.Section("auth.anonymous").Key("enabled").MustBool(false)
  327. AnonymousOrgName = Cfg.Section("auth.anonymous").Key("org_name").String()
  328. AnonymousOrgRole = Cfg.Section("auth.anonymous").Key("org_role").String()
  329. // auth proxy
  330. authProxy := Cfg.Section("auth.proxy")
  331. AuthProxyEnabled = authProxy.Key("enabled").MustBool(false)
  332. AuthProxyHeaderName = authProxy.Key("header_name").String()
  333. AuthProxyHeaderProperty = authProxy.Key("header_property").String()
  334. AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true)
  335. authBasic := Cfg.Section("auth.basic")
  336. BasicAuthEnabled = authBasic.Key("enabled").MustBool(true)
  337. // PhantomJS rendering
  338. ImagesDir = filepath.Join(DataPath, "png")
  339. PhantomDir = filepath.Join(HomePath, "vendor/phantomjs")
  340. analytics := Cfg.Section("analytics")
  341. ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true)
  342. GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String()
  343. ldapSec := Cfg.Section("auth.ldap")
  344. LdapEnabled = ldapSec.Key("enabled").MustBool(false)
  345. readSessionConfig()
  346. readSmtpSettings()
  347. }
  348. func readSessionConfig() {
  349. sec := Cfg.Section("session")
  350. SessionOptions = session.Options{}
  351. SessionOptions.Provider = sec.Key("provider").In("memory", []string{"memory", "file", "redis", "mysql", "postgres"})
  352. SessionOptions.ProviderConfig = strings.Trim(sec.Key("provider_config").String(), "\" ")
  353. SessionOptions.CookieName = sec.Key("cookie_name").MustString("grafana_sess")
  354. SessionOptions.CookiePath = AppSubUrl
  355. SessionOptions.Secure = sec.Key("cookie_secure").MustBool()
  356. SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400)
  357. SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400)
  358. SessionOptions.IDLength = 16
  359. if SessionOptions.Provider == "file" {
  360. SessionOptions.ProviderConfig = makeAbsolute(SessionOptions.ProviderConfig, DataPath)
  361. os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
  362. }
  363. if SessionOptions.CookiePath == "" {
  364. SessionOptions.CookiePath = "/"
  365. }
  366. }
  367. var logLevels = map[string]int{
  368. "Trace": 0,
  369. "Debug": 1,
  370. "Info": 2,
  371. "Warn": 3,
  372. "Error": 4,
  373. "Critical": 5,
  374. }
  375. func initLogging(args *CommandLineArgs) {
  376. // Get and check log mode.
  377. LogModes = strings.Split(Cfg.Section("log").Key("mode").MustString("console"), ",")
  378. LogsPath = makeAbsolute(Cfg.Section("paths").Key("logs").String(), HomePath)
  379. LogConfigs = make([]util.DynMap, len(LogModes))
  380. for i, mode := range LogModes {
  381. mode = strings.TrimSpace(mode)
  382. sec, err := Cfg.GetSection("log." + mode)
  383. if err != nil {
  384. log.Fatal(4, "Unknown log mode: %s", mode)
  385. }
  386. // Log level.
  387. levelName := Cfg.Section("log."+mode).Key("level").In("Trace",
  388. []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
  389. level, ok := logLevels[levelName]
  390. if !ok {
  391. log.Fatal(4, "Unknown log level: %s", levelName)
  392. }
  393. // Generate log configuration.
  394. switch mode {
  395. case "console":
  396. LogConfigs[i] = util.DynMap{"level": level}
  397. case "file":
  398. logPath := sec.Key("file_name").MustString(filepath.Join(LogsPath, "grafana.log"))
  399. os.MkdirAll(filepath.Dir(logPath), os.ModePerm)
  400. LogConfigs[i] = util.DynMap{
  401. "level": level,
  402. "filename": logPath,
  403. "rotate": sec.Key("log_rotate").MustBool(true),
  404. "maxlines": sec.Key("max_lines").MustInt(1000000),
  405. "maxsize": 1 << uint(sec.Key("max_size_shift").MustInt(28)),
  406. "daily": sec.Key("daily_rotate").MustBool(true),
  407. "maxdays": sec.Key("max_days").MustInt(7),
  408. }
  409. case "conn":
  410. LogConfigs[i] = util.DynMap{
  411. "level": level,
  412. "reconnectOnMsg": sec.Key("reconnect_on_msg").MustBool(),
  413. "reconnect": sec.Key("reconnect").MustBool(),
  414. "net": sec.Key("protocol").In("tcp", []string{"tcp", "unix", "udp"}),
  415. "addr": sec.Key("addr").MustString(":7020"),
  416. }
  417. case "smtp":
  418. LogConfigs[i] = util.DynMap{
  419. "level": level,
  420. "user": sec.Key("user").MustString("example@example.com"),
  421. "passwd": sec.Key("passwd").MustString("******"),
  422. "host": sec.Key("host").MustString("127.0.0.1:25"),
  423. "receivers": sec.Key("receivers").MustString("[]"),
  424. "subject": sec.Key("subject").MustString("Diagnostic message from serve"),
  425. }
  426. case "database":
  427. LogConfigs[i] = util.DynMap{
  428. "level": level,
  429. "driver": sec.Key("driver").String(),
  430. "conn": sec.Key("conn").String(),
  431. }
  432. }
  433. cfgJsonBytes, _ := json.Marshal(LogConfigs[i])
  434. log.NewLogger(Cfg.Section("log").Key("buffer_len").MustInt64(10000), mode, string(cfgJsonBytes))
  435. }
  436. }
  437. func LogConfigurationInfo() {
  438. var text bytes.Buffer
  439. text.WriteString("Configuration Info\n")
  440. text.WriteString("Config files:\n")
  441. for i, file := range configFiles {
  442. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, file))
  443. }
  444. if len(appliedCommandLineProperties) > 0 {
  445. text.WriteString("Command lines overrides:\n")
  446. for i, prop := range appliedCommandLineProperties {
  447. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
  448. }
  449. }
  450. if len(appliedEnvOverrides) > 0 {
  451. text.WriteString("\tEnvironment variables used:\n")
  452. for i, prop := range appliedCommandLineProperties {
  453. text.WriteString(fmt.Sprintf(" [%d]: %s\n", i, prop))
  454. }
  455. }
  456. text.WriteString("Paths:\n")
  457. text.WriteString(fmt.Sprintf(" home: %s\n", HomePath))
  458. text.WriteString(fmt.Sprintf(" data: %s\n", DataPath))
  459. text.WriteString(fmt.Sprintf(" logs: %s\n", LogsPath))
  460. log.Info(text.String())
  461. }