setting.go 17 KB

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