setting.go 17 KB

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