setting.go 15 KB

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