setting.go 16 KB

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