|
|
|
|
@ -5,6 +5,7 @@ package setting |
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
|
"bytes" |
|
|
|
|
"errors" |
|
|
|
|
"fmt" |
|
|
|
|
"net/http" |
|
|
|
|
"net/url" |
|
|
|
|
@ -660,73 +661,264 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.ProvisioningPath = makeAbsolute(provisioning, HomePath) |
|
|
|
|
server := iniFile.Section("server") |
|
|
|
|
AppUrl, AppSubUrl, err = parseAppUrlAndSubUrl(server) |
|
|
|
|
if err := readServerSettings(iniFile, cfg); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// read data proxy settings
|
|
|
|
|
dataproxy := iniFile.Section("dataproxy") |
|
|
|
|
DataProxyLogging = dataproxy.Key("logging").MustBool(false) |
|
|
|
|
DataProxyTimeout = dataproxy.Key("timeout").MustInt(30) |
|
|
|
|
cfg.SendUserHeader = dataproxy.Key("send_user_header").MustBool(false) |
|
|
|
|
|
|
|
|
|
if err := readSecuritySettings(iniFile, cfg); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := readSnapshotsSettings(iniFile); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// read dashboard settings
|
|
|
|
|
dashboards := iniFile.Section("dashboards") |
|
|
|
|
DashboardVersionsToKeep = dashboards.Key("versions_to_keep").MustInt(20) |
|
|
|
|
MinRefreshInterval, err = valueAsString(dashboards, "min_refresh_interval", "5s") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false) |
|
|
|
|
|
|
|
|
|
cfg.AppUrl = AppUrl |
|
|
|
|
cfg.AppSubUrl = AppSubUrl |
|
|
|
|
cfg.ServeFromSubPath = ServeFromSubPath |
|
|
|
|
cfg.DefaultHomeDashboardPath = dashboards.Key("default_home_dashboard_path").MustString("") |
|
|
|
|
|
|
|
|
|
Protocol = HTTP |
|
|
|
|
protocolStr, err := valueAsString(server, "protocol", "http") |
|
|
|
|
if err := readUserSettings(iniFile, cfg); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if err := readAuthSettings(iniFile, cfg); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if err := readRenderingSettings(iniFile, cfg); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24) |
|
|
|
|
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true) |
|
|
|
|
cfg.MetricsEndpointBasicAuthUsername, err = valueAsString(iniFile.Section("metrics"), "basic_auth_username", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if protocolStr == "https" { |
|
|
|
|
Protocol = HTTPS |
|
|
|
|
CertFile = server.Key("cert_file").String() |
|
|
|
|
KeyFile = server.Key("cert_key").String() |
|
|
|
|
cfg.MetricsEndpointBasicAuthPassword, err = valueAsString(iniFile.Section("metrics"), "basic_auth_password", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if protocolStr == "h2" { |
|
|
|
|
Protocol = HTTP2 |
|
|
|
|
CertFile = server.Key("cert_file").String() |
|
|
|
|
KeyFile = server.Key("cert_key").String() |
|
|
|
|
cfg.MetricsEndpointDisableTotalStats = iniFile.Section("metrics").Key("disable_total_stats").MustBool(false) |
|
|
|
|
|
|
|
|
|
analytics := iniFile.Section("analytics") |
|
|
|
|
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) |
|
|
|
|
CheckForUpdates = analytics.Key("check_for_updates").MustBool(true) |
|
|
|
|
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() |
|
|
|
|
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String() |
|
|
|
|
|
|
|
|
|
if err := readAlertingSettings(iniFile); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if protocolStr == "socket" { |
|
|
|
|
Protocol = SOCKET |
|
|
|
|
SocketPath = server.Key("socket").String() |
|
|
|
|
|
|
|
|
|
explore := iniFile.Section("explore") |
|
|
|
|
ExploreEnabled = explore.Key("enabled").MustBool(true) |
|
|
|
|
|
|
|
|
|
panelsSection := iniFile.Section("panels") |
|
|
|
|
cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false) |
|
|
|
|
|
|
|
|
|
pluginsSection := iniFile.Section("plugins") |
|
|
|
|
cfg.PluginsEnableAlpha = pluginsSection.Key("enable_alpha").MustBool(false) |
|
|
|
|
cfg.PluginsAppsSkipVerifyTLS = pluginsSection.Key("app_tls_skip_verify_insecure").MustBool(false) |
|
|
|
|
cfg.PluginSettings = extractPluginSettings(iniFile.Sections()) |
|
|
|
|
pluginsAllowUnsigned := pluginsSection.Key("allow_loading_unsigned_plugins").MustString("") |
|
|
|
|
for _, plug := range strings.Split(pluginsAllowUnsigned, ",") { |
|
|
|
|
plug = strings.TrimSpace(plug) |
|
|
|
|
cfg.PluginsAllowUnsigned = append(cfg.PluginsAllowUnsigned, plug) |
|
|
|
|
} |
|
|
|
|
cfg.Protocol = Protocol |
|
|
|
|
|
|
|
|
|
Domain, err = valueAsString(server, "domain", "localhost") |
|
|
|
|
// Read and populate feature toggles list
|
|
|
|
|
featureTogglesSection := iniFile.Section("feature_toggles") |
|
|
|
|
cfg.FeatureToggles = make(map[string]bool) |
|
|
|
|
featuresTogglesStr, err := valueAsString(featureTogglesSection, "enable", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
HttpAddr, err = valueAsString(server, "http_addr", DEFAULT_HTTP_ADDR) |
|
|
|
|
for _, feature := range util.SplitString(featuresTogglesStr) { |
|
|
|
|
cfg.FeatureToggles[feature] = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// check old location for this option
|
|
|
|
|
if panelsSection.Key("enable_alpha").MustBool(false) { |
|
|
|
|
cfg.PluginsEnableAlpha = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg.readLDAPConfig() |
|
|
|
|
cfg.readSessionConfig() |
|
|
|
|
cfg.readSmtpSettings() |
|
|
|
|
cfg.readQuotaSettings() |
|
|
|
|
|
|
|
|
|
if VerifyEmailEnabled && !cfg.Smtp.Enabled { |
|
|
|
|
log.Warnf("require_email_validation is enabled but smtp is disabled") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// check old key name
|
|
|
|
|
GrafanaComUrl, err = valueAsString(iniFile.Section("grafana_net"), "url", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
HttpPort, err = valueAsString(server, "http_port", "3000") |
|
|
|
|
if GrafanaComUrl == "" { |
|
|
|
|
GrafanaComUrl, err = valueAsString(iniFile.Section("grafana_com"), "url", "https://grafana.com") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
imageUploadingSection := iniFile.Section("external_image_storage") |
|
|
|
|
ImageUploadProvider, err = valueAsString(imageUploadingSection, "provider", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
RouterLogging = server.Key("router_logging").MustBool(false) |
|
|
|
|
|
|
|
|
|
EnableGzip = server.Key("enable_gzip").MustBool(false) |
|
|
|
|
EnforceDomain = server.Key("enforce_domain").MustBool(false) |
|
|
|
|
staticRoot, err := valueAsString(server, "static_root_path", "") |
|
|
|
|
enterprise := iniFile.Section("enterprise") |
|
|
|
|
cfg.EnterpriseLicensePath, err = valueAsString(enterprise, "license_path", filepath.Join(cfg.DataPath, "license.jwt")) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
StaticRootPath = makeAbsolute(staticRoot, HomePath) |
|
|
|
|
cfg.StaticRootPath = StaticRootPath |
|
|
|
|
|
|
|
|
|
if err := cfg.validateStaticRootPath(); err != nil { |
|
|
|
|
cacheServer := iniFile.Section("remote_cache") |
|
|
|
|
dbName, err := valueAsString(cacheServer, "type", "database") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
connStr, err := valueAsString(cacheServer, "connstr", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.RemoteCacheOptions = &RemoteCacheOptions{ |
|
|
|
|
Name: dbName, |
|
|
|
|
ConnStr: connStr, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// read data proxy settings
|
|
|
|
|
dataproxy := iniFile.Section("dataproxy") |
|
|
|
|
DataProxyLogging = dataproxy.Key("logging").MustBool(false) |
|
|
|
|
DataProxyTimeout = dataproxy.Key("timeout").MustInt(30) |
|
|
|
|
cfg.SendUserHeader = dataproxy.Key("send_user_header").MustBool(false) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func valueAsString(section *ini.Section, keyName string, defaultValue string) (value string, err error) { |
|
|
|
|
defer func() { |
|
|
|
|
if err_ := recover(); err_ != nil { |
|
|
|
|
err = errors.New("Invalid value for key '" + keyName + "' in configuration file") |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
return section.Key(keyName).MustString(defaultValue), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type RemoteCacheOptions struct { |
|
|
|
|
Name string |
|
|
|
|
ConnStr string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) readLDAPConfig() { |
|
|
|
|
ldapSec := cfg.Raw.Section("auth.ldap") |
|
|
|
|
LDAPConfigFile = ldapSec.Key("config_file").String() |
|
|
|
|
LDAPSyncCron = ldapSec.Key("sync_cron").String() |
|
|
|
|
LDAPEnabled = ldapSec.Key("enabled").MustBool(false) |
|
|
|
|
LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false) |
|
|
|
|
LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) readSessionConfig() { |
|
|
|
|
sec, _ := cfg.Raw.GetSection("session") |
|
|
|
|
|
|
|
|
|
if sec != nil { |
|
|
|
|
cfg.Logger.Warn( |
|
|
|
|
"[Removed] Session setting was removed in v6.2, use remote_cache option instead", |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) initLogging(file *ini.File) error { |
|
|
|
|
logModeStr, err := valueAsString(file.Section("log"), "mode", "console") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
// split on comma
|
|
|
|
|
logModes := strings.Split(logModeStr, ",") |
|
|
|
|
// also try space
|
|
|
|
|
if len(logModes) == 1 { |
|
|
|
|
logModes = strings.Split(logModeStr, " ") |
|
|
|
|
} |
|
|
|
|
logsPath, err := valueAsString(file.Section("paths"), "logs", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.LogsPath = makeAbsolute(logsPath, HomePath) |
|
|
|
|
return log.ReadLoggingConfig(logModes, cfg.LogsPath, file) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) LogConfigSources() { |
|
|
|
|
var text bytes.Buffer |
|
|
|
|
|
|
|
|
|
for _, file := range configFiles { |
|
|
|
|
cfg.Logger.Info("Config loaded from", "file", file) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(appliedCommandLineProperties) > 0 { |
|
|
|
|
for _, prop := range appliedCommandLineProperties { |
|
|
|
|
cfg.Logger.Info("Config overridden from command line", "arg", prop) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(appliedEnvOverrides) > 0 { |
|
|
|
|
text.WriteString("\tEnvironment variables used:\n") |
|
|
|
|
for _, prop := range appliedEnvOverrides { |
|
|
|
|
cfg.Logger.Info("Config overridden from Environment variable", "var", prop) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg.Logger.Info("Path Home", "path", HomePath) |
|
|
|
|
cfg.Logger.Info("Path Data", "path", cfg.DataPath) |
|
|
|
|
cfg.Logger.Info("Path Logs", "path", cfg.LogsPath) |
|
|
|
|
cfg.Logger.Info("Path Plugins", "path", PluginsPath) |
|
|
|
|
cfg.Logger.Info("Path Provisioning", "path", cfg.ProvisioningPath) |
|
|
|
|
cfg.Logger.Info("App mode " + Env) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type DynamicSection struct { |
|
|
|
|
section *ini.Section |
|
|
|
|
Logger log.Logger |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Key dynamically overrides keys with environment variables.
|
|
|
|
|
// As a side effect, the value of the setting key will be updated if an environment variable is present.
|
|
|
|
|
func (s *DynamicSection) Key(k string) *ini.Key { |
|
|
|
|
envKey := envKey(s.section.Name(), k) |
|
|
|
|
envValue := os.Getenv(envKey) |
|
|
|
|
key := s.section.Key(k) |
|
|
|
|
|
|
|
|
|
if len(envValue) == 0 { |
|
|
|
|
return key |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// read security settings
|
|
|
|
|
key.SetValue(envValue) |
|
|
|
|
if shouldRedactKey(envKey) { |
|
|
|
|
envValue = REDACTED_PASSWORD |
|
|
|
|
} |
|
|
|
|
s.Logger.Info("Config overridden from Environment variable", "var", fmt.Sprintf("%s=%s", envKey, envValue)) |
|
|
|
|
|
|
|
|
|
return key |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SectionWithEnvOverrides dynamically overrides keys with environment variables.
|
|
|
|
|
// As a side effect, the value of the setting key will be updated if an environment variable is present.
|
|
|
|
|
func (cfg *Cfg) SectionWithEnvOverrides(s string) *DynamicSection { |
|
|
|
|
return &DynamicSection{cfg.Raw.Section(s), cfg.Logger} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readSecuritySettings(iniFile *ini.File, cfg *Cfg) error { |
|
|
|
|
security := iniFile.Section("security") |
|
|
|
|
var err error |
|
|
|
|
SecretKey, err = valueAsString(security, "secret_key", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
@ -761,7 +953,6 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
cfg.CookieSameSiteMode = CookieSameSiteMode |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AllowEmbedding = security.Key("allow_embedding").MustBool(false) |
|
|
|
|
|
|
|
|
|
ContentTypeProtectionHeader = security.Key("x_content_type_options").MustBool(true) |
|
|
|
|
@ -771,38 +962,14 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
StrictTransportSecurityPreload = security.Key("strict_transport_security_preload").MustBool(false) |
|
|
|
|
StrictTransportSecuritySubDomains = security.Key("strict_transport_security_subdomains").MustBool(false) |
|
|
|
|
|
|
|
|
|
// read snapshots settings
|
|
|
|
|
snapshots := iniFile.Section("snapshots") |
|
|
|
|
ExternalSnapshotUrl, err = valueAsString(snapshots, "external_snapshot_url", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalSnapshotName, err = valueAsString(snapshots, "external_snapshot_name", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalEnabled = snapshots.Key("external_enabled").MustBool(true) |
|
|
|
|
SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true) |
|
|
|
|
SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false) |
|
|
|
|
|
|
|
|
|
// read dashboard settings
|
|
|
|
|
dashboards := iniFile.Section("dashboards") |
|
|
|
|
DashboardVersionsToKeep = dashboards.Key("versions_to_keep").MustInt(20) |
|
|
|
|
MinRefreshInterval, err = valueAsString(dashboards, "min_refresh_interval", "5s") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg.DefaultHomeDashboardPath = dashboards.Key("default_home_dashboard_path").MustString("") |
|
|
|
|
|
|
|
|
|
// read data source proxy white list
|
|
|
|
|
// read data source proxy whitelist
|
|
|
|
|
DataProxyWhiteList = make(map[string]bool) |
|
|
|
|
securityStr, err := valueAsString(security, "data_source_proxy_whitelist", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
for _, hostAndIp := range util.SplitString(securityStr) { |
|
|
|
|
DataProxyWhiteList[hostAndIp] = true |
|
|
|
|
for _, hostAndIP := range util.SplitString(securityStr) { |
|
|
|
|
DataProxyWhiteList[hostAndIP] = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// admin
|
|
|
|
|
@ -816,49 +983,18 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// users
|
|
|
|
|
users := iniFile.Section("users") |
|
|
|
|
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true) |
|
|
|
|
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) |
|
|
|
|
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) |
|
|
|
|
AutoAssignOrgId = users.Key("auto_assign_org_id").MustInt(1) |
|
|
|
|
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"}) |
|
|
|
|
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false) |
|
|
|
|
LoginHint, err = valueAsString(users, "login_hint", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
PasswordHint, err = valueAsString(users, "password_hint", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
DefaultTheme, err = valueAsString(users, "default_theme", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalUserMngLinkUrl, err = valueAsString(users, "external_manage_link_url", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalUserMngLinkName, err = valueAsString(users, "external_manage_link_name", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalUserMngInfo, err = valueAsString(users, "external_manage_info", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false) |
|
|
|
|
cfg.EditorsCanAdmin = users.Key("editors_can_admin").MustBool(false) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// auth
|
|
|
|
|
func readAuthSettings(iniFile *ini.File, cfg *Cfg) error { |
|
|
|
|
auth := iniFile.Section("auth") |
|
|
|
|
|
|
|
|
|
var err error |
|
|
|
|
LoginCookieName, err = valueAsString(auth, "login_cookie_name", "grafana_session") |
|
|
|
|
cfg.LoginCookieName = LoginCookieName |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.LoginCookieName = LoginCookieName |
|
|
|
|
cfg.LoginMaxInactiveLifetimeDays = auth.Key("login_maximum_inactive_lifetime_days").MustInt(7) |
|
|
|
|
|
|
|
|
|
LoginMaxLifetimeDays = auth.Key("login_maximum_lifetime_days").MustInt(30) |
|
|
|
|
@ -894,7 +1030,10 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
} |
|
|
|
|
cfg.AnonymousHideVersion = iniFile.Section("auth.anonymous").Key("hide_version").MustBool(false) |
|
|
|
|
|
|
|
|
|
// auth proxy
|
|
|
|
|
// basic auth
|
|
|
|
|
authBasic := iniFile.Section("auth.basic") |
|
|
|
|
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true) |
|
|
|
|
|
|
|
|
|
authProxy := iniFile.Section("auth.proxy") |
|
|
|
|
AuthProxyEnabled = authProxy.Key("enabled").MustBool(false) |
|
|
|
|
|
|
|
|
|
@ -906,42 +1045,81 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true) |
|
|
|
|
AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false) |
|
|
|
|
|
|
|
|
|
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt() |
|
|
|
|
syncVal := authProxy.Key("sync_ttl").MustInt() |
|
|
|
|
|
|
|
|
|
if ldapSyncVal != AUTH_PROXY_SYNC_TTL { |
|
|
|
|
AuthProxySyncTtl = ldapSyncVal |
|
|
|
|
cfg.Logger.Warn("[Deprecated] the configuration setting 'ldap_sync_ttl' is deprecated, please use 'sync_ttl' instead") |
|
|
|
|
} else { |
|
|
|
|
AuthProxySyncTtl = syncVal |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AuthProxyWhitelist, err = valueAsString(authProxy, "whitelist", "") |
|
|
|
|
AuthProxyAutoSignUp = authProxy.Key("auto_sign_up").MustBool(true) |
|
|
|
|
AuthProxyEnableLoginToken = authProxy.Key("enable_login_token").MustBool(false) |
|
|
|
|
|
|
|
|
|
ldapSyncVal := authProxy.Key("ldap_sync_ttl").MustInt() |
|
|
|
|
syncVal := authProxy.Key("sync_ttl").MustInt() |
|
|
|
|
|
|
|
|
|
if ldapSyncVal != AUTH_PROXY_SYNC_TTL { |
|
|
|
|
AuthProxySyncTtl = ldapSyncVal |
|
|
|
|
cfg.Logger.Warn("[Deprecated] the configuration setting 'ldap_sync_ttl' is deprecated, please use 'sync_ttl' instead") |
|
|
|
|
} else { |
|
|
|
|
AuthProxySyncTtl = syncVal |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AuthProxyWhitelist, err = valueAsString(authProxy, "whitelist", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AuthProxyHeaders = make(map[string]string) |
|
|
|
|
headers, err := valueAsString(authProxy, "headers", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
for _, propertyAndHeader := range util.SplitString(headers) { |
|
|
|
|
split := strings.SplitN(propertyAndHeader, ":", 2) |
|
|
|
|
if len(split) == 2 { |
|
|
|
|
AuthProxyHeaders[split[0]] = split[1] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readUserSettings(iniFile *ini.File, cfg *Cfg) error { |
|
|
|
|
users := iniFile.Section("users") |
|
|
|
|
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true) |
|
|
|
|
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true) |
|
|
|
|
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true) |
|
|
|
|
AutoAssignOrgId = users.Key("auto_assign_org_id").MustInt(1) |
|
|
|
|
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"}) |
|
|
|
|
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false) |
|
|
|
|
var err error |
|
|
|
|
LoginHint, err = valueAsString(users, "login_hint", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
PasswordHint, err = valueAsString(users, "password_hint", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
DefaultTheme, err = valueAsString(users, "default_theme", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalUserMngLinkUrl, err = valueAsString(users, "external_manage_link_url", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ExternalUserMngLinkName, err = valueAsString(users, "external_manage_link_name", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AuthProxyHeaders = make(map[string]string) |
|
|
|
|
headers, err := valueAsString(authProxy, "headers", "") |
|
|
|
|
ExternalUserMngInfo, err = valueAsString(users, "external_manage_info", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
for _, propertyAndHeader := range util.SplitString(headers) { |
|
|
|
|
split := strings.SplitN(propertyAndHeader, ":", 2) |
|
|
|
|
if len(split) == 2 { |
|
|
|
|
AuthProxyHeaders[split[0]] = split[1] |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false) |
|
|
|
|
cfg.EditorsCanAdmin = users.Key("editors_can_admin").MustBool(false) |
|
|
|
|
|
|
|
|
|
// basic auth
|
|
|
|
|
authBasic := iniFile.Section("auth.basic") |
|
|
|
|
BasicAuthEnabled = authBasic.Key("enabled").MustBool(true) |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Rendering
|
|
|
|
|
func readRenderingSettings(iniFile *ini.File, cfg *Cfg) error { |
|
|
|
|
renderSec := iniFile.Section("rendering") |
|
|
|
|
var err error |
|
|
|
|
cfg.RendererUrl, err = valueAsString(renderSec, "server_url", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
@ -958,34 +1136,23 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
} |
|
|
|
|
_, err := url.Parse(cfg.RendererCallbackUrl) |
|
|
|
|
if err != nil { |
|
|
|
|
// XXX: Should return an error?
|
|
|
|
|
log.Fatalf(4, "Invalid callback_url(%s): %s", cfg.RendererCallbackUrl, err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
cfg.RendererConcurrentRequestLimit = renderSec.Key("concurrent_render_request_limit").MustInt(30) |
|
|
|
|
|
|
|
|
|
cfg.ImagesDir = filepath.Join(cfg.DataPath, "png") |
|
|
|
|
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24) |
|
|
|
|
cfg.MetricsEndpointEnabled = iniFile.Section("metrics").Key("enabled").MustBool(true) |
|
|
|
|
cfg.MetricsEndpointBasicAuthUsername, err = valueAsString(iniFile.Section("metrics"), "basic_auth_username", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.MetricsEndpointBasicAuthPassword, err = valueAsString(iniFile.Section("metrics"), "basic_auth_password", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.MetricsEndpointDisableTotalStats = iniFile.Section("metrics").Key("disable_total_stats").MustBool(false) |
|
|
|
|
|
|
|
|
|
analytics := iniFile.Section("analytics") |
|
|
|
|
ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) |
|
|
|
|
CheckForUpdates = analytics.Key("check_for_updates").MustBool(true) |
|
|
|
|
GoogleAnalyticsId = analytics.Key("google_analytics_ua_id").String() |
|
|
|
|
GoogleTagManagerId = analytics.Key("google_tag_manager_id").String() |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func readAlertingSettings(iniFile *ini.File) error { |
|
|
|
|
alerting := iniFile.Section("alerting") |
|
|
|
|
AlertingEnabled = alerting.Key("enabled").MustBool(true) |
|
|
|
|
ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true) |
|
|
|
|
AlertingRenderLimit = alerting.Key("concurrent_render_limit").MustInt(5) |
|
|
|
|
var err error |
|
|
|
|
AlertingErrorOrTimeout, err = valueAsString(alerting, "error_or_timeout", "alerting") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
@ -1002,190 +1169,86 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error { |
|
|
|
|
AlertingMaxAttempts = alerting.Key("max_attempts").MustInt(3) |
|
|
|
|
AlertingMinInterval = alerting.Key("min_interval_seconds").MustInt64(1) |
|
|
|
|
|
|
|
|
|
explore := iniFile.Section("explore") |
|
|
|
|
ExploreEnabled = explore.Key("enabled").MustBool(true) |
|
|
|
|
|
|
|
|
|
panelsSection := iniFile.Section("panels") |
|
|
|
|
cfg.DisableSanitizeHtml = panelsSection.Key("disable_sanitize_html").MustBool(false) |
|
|
|
|
|
|
|
|
|
pluginsSection := iniFile.Section("plugins") |
|
|
|
|
cfg.PluginsEnableAlpha = pluginsSection.Key("enable_alpha").MustBool(false) |
|
|
|
|
cfg.PluginsAppsSkipVerifyTLS = pluginsSection.Key("app_tls_skip_verify_insecure").MustBool(false) |
|
|
|
|
cfg.PluginSettings = extractPluginSettings(iniFile.Sections()) |
|
|
|
|
pluginsAllowUnsigned := pluginsSection.Key("allow_loading_unsigned_plugins").MustString("") |
|
|
|
|
for _, plug := range strings.Split(pluginsAllowUnsigned, ",") { |
|
|
|
|
plug = strings.TrimSpace(plug) |
|
|
|
|
cfg.PluginsAllowUnsigned = append(cfg.PluginsAllowUnsigned, plug) |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Read and populate feature toggles list
|
|
|
|
|
featureTogglesSection := iniFile.Section("feature_toggles") |
|
|
|
|
cfg.FeatureToggles = make(map[string]bool) |
|
|
|
|
featuresTogglesStr, err := valueAsString(featureTogglesSection, "enable", "") |
|
|
|
|
func readSnapshotsSettings(iniFile *ini.File) error { |
|
|
|
|
snapshots := iniFile.Section("snapshots") |
|
|
|
|
var err error |
|
|
|
|
ExternalSnapshotUrl, err = valueAsString(snapshots, "external_snapshot_url", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
for _, feature := range util.SplitString(featuresTogglesStr) { |
|
|
|
|
cfg.FeatureToggles[feature] = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// check old location for this option
|
|
|
|
|
if panelsSection.Key("enable_alpha").MustBool(false) { |
|
|
|
|
cfg.PluginsEnableAlpha = true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
cfg.readLDAPConfig() |
|
|
|
|
cfg.readSessionConfig() |
|
|
|
|
cfg.readSmtpSettings() |
|
|
|
|
cfg.readQuotaSettings() |
|
|
|
|
|
|
|
|
|
if VerifyEmailEnabled && !cfg.Smtp.Enabled { |
|
|
|
|
log.Warnf("require_email_validation is enabled but smtp is disabled") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// check old key name
|
|
|
|
|
GrafanaComUrl, err = valueAsString(iniFile.Section("grafana_net"), "url", "") |
|
|
|
|
ExternalSnapshotName, err = valueAsString(snapshots, "external_snapshot_name", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if GrafanaComUrl == "" { |
|
|
|
|
GrafanaComUrl, err = valueAsString(iniFile.Section("grafana_com"), "url", "https://grafana.com") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
ExternalEnabled = snapshots.Key("external_enabled").MustBool(true) |
|
|
|
|
SnapShotRemoveExpired = snapshots.Key("snapshot_remove_expired").MustBool(true) |
|
|
|
|
SnapshotPublicMode = snapshots.Key("public_mode").MustBool(false) |
|
|
|
|
|
|
|
|
|
imageUploadingSection := iniFile.Section("external_image_storage") |
|
|
|
|
ImageUploadProvider, err = valueAsString(imageUploadingSection, "provider", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
enterprise := iniFile.Section("enterprise") |
|
|
|
|
cfg.EnterpriseLicensePath, err = valueAsString(enterprise, "license_path", filepath.Join(cfg.DataPath, "license.jwt")) |
|
|
|
|
func readServerSettings(iniFile *ini.File, cfg *Cfg) error { |
|
|
|
|
server := iniFile.Section("server") |
|
|
|
|
var err error |
|
|
|
|
AppUrl, AppSubUrl, err = parseAppUrlAndSubUrl(server) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
ServeFromSubPath = server.Key("serve_from_sub_path").MustBool(false) |
|
|
|
|
|
|
|
|
|
cacheServer := iniFile.Section("remote_cache") |
|
|
|
|
dbName, err := valueAsString(cacheServer, "type", "database") |
|
|
|
|
cfg.AppUrl = AppUrl |
|
|
|
|
cfg.AppSubUrl = AppSubUrl |
|
|
|
|
cfg.ServeFromSubPath = ServeFromSubPath |
|
|
|
|
|
|
|
|
|
Protocol = HTTP |
|
|
|
|
protocolStr, err := valueAsString(server, "protocol", "http") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
connStr, err := valueAsString(cacheServer, "connstr", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
if protocolStr == "https" { |
|
|
|
|
Protocol = HTTPS |
|
|
|
|
CertFile = server.Key("cert_file").String() |
|
|
|
|
KeyFile = server.Key("cert_key").String() |
|
|
|
|
} |
|
|
|
|
cfg.RemoteCacheOptions = &RemoteCacheOptions{ |
|
|
|
|
Name: dbName, |
|
|
|
|
ConnStr: connStr, |
|
|
|
|
if protocolStr == "h2" { |
|
|
|
|
Protocol = HTTP2 |
|
|
|
|
CertFile = server.Key("cert_file").String() |
|
|
|
|
KeyFile = server.Key("cert_key").String() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func valueAsString(section *ini.Section, keyName string, defaultValue string) (value string, err error) { |
|
|
|
|
return section.Key(keyName).MustString(defaultValue), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type RemoteCacheOptions struct { |
|
|
|
|
Name string |
|
|
|
|
ConnStr string |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) readLDAPConfig() { |
|
|
|
|
ldapSec := cfg.Raw.Section("auth.ldap") |
|
|
|
|
LDAPConfigFile = ldapSec.Key("config_file").String() |
|
|
|
|
LDAPSyncCron = ldapSec.Key("sync_cron").String() |
|
|
|
|
LDAPEnabled = ldapSec.Key("enabled").MustBool(false) |
|
|
|
|
LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false) |
|
|
|
|
LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) readSessionConfig() { |
|
|
|
|
sec, _ := cfg.Raw.GetSection("session") |
|
|
|
|
|
|
|
|
|
if sec != nil { |
|
|
|
|
cfg.Logger.Warn( |
|
|
|
|
"[Removed] Session setting was removed in v6.2, use remote_cache option instead", |
|
|
|
|
) |
|
|
|
|
if protocolStr == "socket" { |
|
|
|
|
Protocol = SOCKET |
|
|
|
|
SocketPath = server.Key("socket").String() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) initLogging(file *ini.File) error { |
|
|
|
|
logModeStr, err := valueAsString(file.Section("log"), "mode", "console") |
|
|
|
|
Domain, err = valueAsString(server, "domain", "localhost") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
// split on comma
|
|
|
|
|
logModes := strings.Split(logModeStr, ",") |
|
|
|
|
// also try space
|
|
|
|
|
if len(logModes) == 1 { |
|
|
|
|
logModes = strings.Split(logModeStr, " ") |
|
|
|
|
} |
|
|
|
|
logsPath, err := valueAsString(file.Section("paths"), "logs", "") |
|
|
|
|
HttpAddr, err = valueAsString(server, "http_addr", DEFAULT_HTTP_ADDR) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
cfg.LogsPath = makeAbsolute(logsPath, HomePath) |
|
|
|
|
return log.ReadLoggingConfig(logModes, cfg.LogsPath, file) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (cfg *Cfg) LogConfigSources() { |
|
|
|
|
var text bytes.Buffer |
|
|
|
|
|
|
|
|
|
for _, file := range configFiles { |
|
|
|
|
cfg.Logger.Info("Config loaded from", "file", file) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(appliedCommandLineProperties) > 0 { |
|
|
|
|
for _, prop := range appliedCommandLineProperties { |
|
|
|
|
cfg.Logger.Info("Config overridden from command line", "arg", prop) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if len(appliedEnvOverrides) > 0 { |
|
|
|
|
text.WriteString("\tEnvironment variables used:\n") |
|
|
|
|
for _, prop := range appliedEnvOverrides { |
|
|
|
|
cfg.Logger.Info("Config overridden from Environment variable", "var", prop) |
|
|
|
|
} |
|
|
|
|
HttpPort, err = valueAsString(server, "http_port", "3000") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
RouterLogging = server.Key("router_logging").MustBool(false) |
|
|
|
|
|
|
|
|
|
cfg.Logger.Info("Path Home", "path", HomePath) |
|
|
|
|
cfg.Logger.Info("Path Data", "path", cfg.DataPath) |
|
|
|
|
cfg.Logger.Info("Path Logs", "path", cfg.LogsPath) |
|
|
|
|
cfg.Logger.Info("Path Plugins", "path", PluginsPath) |
|
|
|
|
cfg.Logger.Info("Path Provisioning", "path", cfg.ProvisioningPath) |
|
|
|
|
cfg.Logger.Info("App mode " + Env) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type DynamicSection struct { |
|
|
|
|
section *ini.Section |
|
|
|
|
Logger log.Logger |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Key dynamically overrides keys with environment variables.
|
|
|
|
|
// As a side effect, the value of the setting key will be updated if an environment variable is present.
|
|
|
|
|
func (s *DynamicSection) Key(k string) *ini.Key { |
|
|
|
|
envKey := envKey(s.section.Name(), k) |
|
|
|
|
envValue := os.Getenv(envKey) |
|
|
|
|
key := s.section.Key(k) |
|
|
|
|
|
|
|
|
|
if len(envValue) == 0 { |
|
|
|
|
return key |
|
|
|
|
EnableGzip = server.Key("enable_gzip").MustBool(false) |
|
|
|
|
EnforceDomain = server.Key("enforce_domain").MustBool(false) |
|
|
|
|
staticRoot, err := valueAsString(server, "static_root_path", "") |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
StaticRootPath = makeAbsolute(staticRoot, HomePath) |
|
|
|
|
cfg.StaticRootPath = StaticRootPath |
|
|
|
|
|
|
|
|
|
key.SetValue(envValue) |
|
|
|
|
if shouldRedactKey(envKey) { |
|
|
|
|
envValue = REDACTED_PASSWORD |
|
|
|
|
if err := cfg.validateStaticRootPath(); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
s.Logger.Info("Config overridden from Environment variable", "var", fmt.Sprintf("%s=%s", envKey, envValue)) |
|
|
|
|
|
|
|
|
|
return key |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// SectionWithEnvOverrides dynamically overrides keys with environment variables.
|
|
|
|
|
// As a side effect, the value of the setting key will be updated if an environment variable is present.
|
|
|
|
|
func (cfg *Cfg) SectionWithEnvOverrides(s string) *DynamicSection { |
|
|
|
|
return &DynamicSection{cfg.Raw.Section(s), cfg.Logger} |
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|