github.com/haalcala/mattermost-server-change-repo/v5@v5.33.2/app/server.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "context" 8 "crypto/tls" 9 "encoding/json" 10 "fmt" 11 "hash/maphash" 12 "io/ioutil" 13 "net" 14 "net/http" 15 "net/url" 16 "os" 17 "os/exec" 18 "path" 19 "path/filepath" 20 "runtime" 21 "strconv" 22 "strings" 23 "sync" 24 "sync/atomic" 25 "syscall" 26 "time" 27 28 "gopkg.in/yaml.v2" 29 30 "github.com/getsentry/sentry-go" 31 sentryhttp "github.com/getsentry/sentry-go/http" 32 "github.com/gorilla/mux" 33 "github.com/pkg/errors" 34 "github.com/rs/cors" 35 "golang.org/x/crypto/acme/autocert" 36 37 "github.com/mattermost/mattermost-server/v5/audit" 38 "github.com/mattermost/mattermost-server/v5/config" 39 "github.com/mattermost/mattermost-server/v5/einterfaces" 40 "github.com/mattermost/mattermost-server/v5/jobs" 41 "github.com/mattermost/mattermost-server/v5/mlog" 42 "github.com/mattermost/mattermost-server/v5/model" 43 "github.com/mattermost/mattermost-server/v5/plugin" 44 "github.com/mattermost/mattermost-server/v5/services/awsmeter" 45 "github.com/mattermost/mattermost-server/v5/services/cache" 46 "github.com/mattermost/mattermost-server/v5/services/filesstore" 47 "github.com/mattermost/mattermost-server/v5/services/httpservice" 48 "github.com/mattermost/mattermost-server/v5/services/imageproxy" 49 "github.com/mattermost/mattermost-server/v5/services/mailservice" 50 "github.com/mattermost/mattermost-server/v5/services/searchengine" 51 "github.com/mattermost/mattermost-server/v5/services/searchengine/bleveengine" 52 "github.com/mattermost/mattermost-server/v5/services/telemetry" 53 "github.com/mattermost/mattermost-server/v5/services/timezones" 54 "github.com/mattermost/mattermost-server/v5/services/tracing" 55 "github.com/mattermost/mattermost-server/v5/services/upgrader" 56 "github.com/mattermost/mattermost-server/v5/store" 57 "github.com/mattermost/mattermost-server/v5/store/localcachelayer" 58 "github.com/mattermost/mattermost-server/v5/store/retrylayer" 59 "github.com/mattermost/mattermost-server/v5/store/searchlayer" 60 "github.com/mattermost/mattermost-server/v5/store/sqlstore" 61 "github.com/mattermost/mattermost-server/v5/store/timerlayer" 62 "github.com/mattermost/mattermost-server/v5/utils" 63 ) 64 65 var MaxNotificationsPerChannelDefault int64 = 1000000 66 67 // declaring this as var to allow overriding in tests 68 var SentryDSN = "placeholder_sentry_dsn" 69 70 type Server struct { 71 sqlStore *sqlstore.SqlStore 72 Store store.Store 73 WebSocketRouter *WebSocketRouter 74 AppInitializedOnce sync.Once 75 76 // RootRouter is the starting point for all HTTP requests to the server. 77 RootRouter *mux.Router 78 79 // LocalRouter is the starting point for all the local UNIX socket 80 // requests to the server 81 LocalRouter *mux.Router 82 83 // Router is the starting point for all web, api4 and ws requests to the server. It differs 84 // from RootRouter only if the SiteURL contains a /subpath. 85 Router *mux.Router 86 87 Server *http.Server 88 ListenAddr *net.TCPAddr 89 RateLimiter *RateLimiter 90 Busy *Busy 91 92 localModeServer *http.Server 93 94 didFinishListen chan struct{} 95 96 goroutineCount int32 97 goroutineExitSignal chan struct{} 98 99 PluginsEnvironment *plugin.Environment 100 PluginConfigListenerId string 101 PluginsLock sync.RWMutex 102 103 EmailService *EmailService 104 105 hubs []*Hub 106 hashSeed maphash.Seed 107 108 PushNotificationsHub PushNotificationsHub 109 pushNotificationClient *http.Client // TODO: move this to it's own package 110 111 runjobs bool 112 Jobs *jobs.JobServer 113 114 clusterLeaderListeners sync.Map 115 116 licenseValue atomic.Value 117 clientLicenseValue atomic.Value 118 licenseListeners map[string]func(*model.License, *model.License) 119 120 timezones *timezones.Timezones 121 122 newStore func() (store.Store, error) 123 124 htmlTemplateWatcher *utils.HTMLTemplateWatcher 125 sessionCache cache.Cache 126 seenPendingPostIdsCache cache.Cache 127 statusCache cache.Cache 128 configListenerId string 129 licenseListenerId string 130 logListenerId string 131 clusterLeaderListenerId string 132 searchConfigListenerId string 133 searchLicenseListenerId string 134 loggerLicenseListenerId string 135 configStore *config.Store 136 postActionCookieSecret []byte 137 138 advancedLogListenerCleanup func() 139 140 pluginCommands []*PluginCommand 141 pluginCommandsLock sync.RWMutex 142 143 asymmetricSigningKey atomic.Value 144 clientConfig atomic.Value 145 clientConfigHash atomic.Value 146 limitedClientConfig atomic.Value 147 148 telemetryService *telemetry.TelemetryService 149 150 phase2PermissionsMigrationComplete bool 151 152 HTTPService httpservice.HTTPService 153 154 ImageProxy *imageproxy.ImageProxy 155 156 Audit *audit.Audit 157 Log *mlog.Logger 158 NotificationsLog *mlog.Logger 159 160 joinCluster bool 161 startMetrics bool 162 startSearchEngine bool 163 164 SearchEngine *searchengine.Broker 165 166 AccountMigration einterfaces.AccountMigrationInterface 167 Cluster einterfaces.ClusterInterface 168 Compliance einterfaces.ComplianceInterface 169 DataRetention einterfaces.DataRetentionInterface 170 Ldap einterfaces.LdapInterface 171 MessageExport einterfaces.MessageExportInterface 172 Cloud einterfaces.CloudInterface 173 Metrics einterfaces.MetricsInterface 174 Notification einterfaces.NotificationInterface 175 Saml einterfaces.SamlInterface 176 177 CacheProvider cache.Provider 178 179 tracer *tracing.Tracer 180 181 // These are used to prevent concurrent upload requests 182 // for a given upload session which could cause inconsistencies 183 // and data corruption. 184 uploadLockMapMut sync.Mutex 185 uploadLockMap map[string]bool 186 187 featureFlagSynchronizer *config.FeatureFlagSynchronizer 188 featureFlagStop chan struct{} 189 featureFlagStopped chan struct{} 190 featureFlagSynchronizerMutex sync.Mutex 191 } 192 193 func NewServer(options ...Option) (*Server, error) { 194 rootRouter := mux.NewRouter() 195 localRouter := mux.NewRouter() 196 197 s := &Server{ 198 goroutineExitSignal: make(chan struct{}, 1), 199 RootRouter: rootRouter, 200 LocalRouter: localRouter, 201 licenseListeners: map[string]func(*model.License, *model.License){}, 202 hashSeed: maphash.MakeSeed(), 203 uploadLockMap: map[string]bool{}, 204 } 205 206 for _, option := range options { 207 if err := option(s); err != nil { 208 return nil, errors.Wrap(err, "failed to apply option") 209 } 210 } 211 212 if s.configStore == nil { 213 innerStore, err := config.NewFileStore("config.json", true) 214 if err != nil { 215 return nil, errors.Wrap(err, "failed to load config") 216 } 217 configStore, err := config.NewStoreFromBacking(innerStore, nil, false) 218 if err != nil { 219 return nil, errors.Wrap(err, "failed to load config") 220 } 221 222 s.configStore = configStore 223 } 224 225 if err := s.initLogging(); err != nil { 226 mlog.Error("Could not initiate logging", mlog.Err(err)) 227 } 228 229 // This is called after initLogging() to avoid a race condition. 230 mlog.Info("Server is initializing...", mlog.String("go_version", runtime.Version())) 231 232 // It is important to initialize the hub only after the global logger is set 233 // to avoid race conditions while logging from inside the hub. 234 fakeApp := New(ServerConnector(s)) 235 fakeApp.HubStart() 236 237 if *s.Config().LogSettings.EnableDiagnostics && *s.Config().LogSettings.EnableSentry { 238 if strings.Contains(SentryDSN, "placeholder") { 239 mlog.Warn("Sentry reporting is enabled, but SENTRY_DSN is not set. Disabling reporting.") 240 } else { 241 if err := sentry.Init(sentry.ClientOptions{ 242 Dsn: SentryDSN, 243 Release: model.BuildHash, 244 AttachStacktrace: true, 245 BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { 246 // sanitize data sent to sentry to reduce exposure of PII 247 if event.Request != nil { 248 event.Request.Cookies = "" 249 event.Request.QueryString = "" 250 event.Request.Headers = nil 251 event.Request.Data = "" 252 } 253 return event 254 }, 255 }); err != nil { 256 mlog.Warn("Sentry could not be initiated, probably bad DSN?", mlog.Err(err)) 257 } 258 } 259 } 260 261 if *s.Config().ServiceSettings.EnableOpenTracing { 262 tracer, err := tracing.New() 263 if err != nil { 264 return nil, err 265 } 266 s.tracer = tracer 267 } 268 269 s.HTTPService = httpservice.MakeHTTPService(s) 270 s.pushNotificationClient = s.HTTPService.MakeClient(true) 271 272 s.ImageProxy = imageproxy.MakeImageProxy(s, s.HTTPService, s.Log) 273 274 if err := utils.TranslationsPreInit(); err != nil { 275 return nil, errors.Wrapf(err, "unable to load Mattermost translation files") 276 } 277 model.AppErrorInit(utils.T) 278 279 searchEngine := searchengine.NewBroker(s.Config(), s.Jobs) 280 bleveEngine := bleveengine.NewBleveEngine(s.Config(), s.Jobs) 281 if err := bleveEngine.Start(); err != nil { 282 return nil, err 283 } 284 searchEngine.RegisterBleveEngine(bleveEngine) 285 s.SearchEngine = searchEngine 286 287 // at the moment we only have this implementation 288 // in the future the cache provider will be built based on the loaded config 289 s.CacheProvider = cache.NewProvider() 290 if err := s.CacheProvider.Connect(); err != nil { 291 return nil, errors.Wrapf(err, "Unable to connect to cache provider") 292 } 293 294 var err error 295 if s.sessionCache, err = s.CacheProvider.NewCache(&cache.CacheOptions{ 296 Size: model.SESSION_CACHE_SIZE, 297 Striped: true, 298 StripedBuckets: maxInt(runtime.NumCPU()-1, 1), 299 }); err != nil { 300 return nil, errors.Wrap(err, "Unable to create session cache") 301 } 302 if s.seenPendingPostIdsCache, err = s.CacheProvider.NewCache(&cache.CacheOptions{ 303 Size: PendingPostIDsCacheSize, 304 }); err != nil { 305 return nil, errors.Wrap(err, "Unable to create pending post ids cache") 306 } 307 if s.statusCache, err = s.CacheProvider.NewCache(&cache.CacheOptions{ 308 Size: model.STATUS_CACHE_SIZE, 309 Striped: true, 310 StripedBuckets: maxInt(runtime.NumCPU()-1, 1), 311 }); err != nil { 312 return nil, errors.Wrap(err, "Unable to create status cache") 313 } 314 315 s.createPushNotificationsHub() 316 317 if err2 := utils.InitTranslations(s.Config().LocalizationSettings); err2 != nil { 318 return nil, errors.Wrapf(err2, "unable to load Mattermost translation files") 319 } 320 321 s.initEnterprise() 322 323 if s.newStore == nil { 324 s.newStore = func() (store.Store, error) { 325 s.sqlStore = sqlstore.New(s.Config().SqlSettings, s.Metrics) 326 if s.sqlStore.DriverName() == model.DATABASE_DRIVER_POSTGRES { 327 ver, err2 := s.sqlStore.GetDbVersion(true) 328 if err2 != nil { 329 return nil, errors.Wrap(err2, "cannot get DB version") 330 } 331 intVer, err2 := strconv.Atoi(ver) 332 if err2 != nil { 333 return nil, errors.Wrap(err2, "cannot parse DB version") 334 } 335 if intVer < sqlstore.MinimumRequiredPostgresVersion { 336 return nil, fmt.Errorf("minimum required postgres version is %s; found %s", sqlstore.VersionString(sqlstore.MinimumRequiredPostgresVersion), sqlstore.VersionString(intVer)) 337 } 338 } 339 340 lcl, err2 := localcachelayer.NewLocalCacheLayer( 341 retrylayer.New(s.sqlStore), 342 s.Metrics, 343 s.Cluster, 344 s.CacheProvider, 345 ) 346 if err2 != nil { 347 return nil, errors.Wrap(err2, "cannot create local cache layer") 348 } 349 350 searchStore := searchlayer.NewSearchLayer( 351 lcl, 352 s.SearchEngine, 353 s.Config(), 354 ) 355 356 s.AddConfigListener(func(prevCfg, cfg *model.Config) { 357 searchStore.UpdateConfig(cfg) 358 }) 359 360 s.sqlStore.UpdateLicense(s.License()) 361 s.AddLicenseListener(func(oldLicense, newLicense *model.License) { 362 s.sqlStore.UpdateLicense(newLicense) 363 }) 364 365 return timerlayer.New( 366 searchStore, 367 s.Metrics, 368 ), nil 369 } 370 } 371 372 if htmlTemplateWatcher, err2 := utils.NewHTMLTemplateWatcher("templates"); err2 != nil { 373 mlog.Error("Failed to parse server templates", mlog.Err(err2)) 374 } else { 375 s.htmlTemplateWatcher = htmlTemplateWatcher 376 } 377 378 s.Store, err = s.newStore() 379 if err != nil { 380 return nil, errors.Wrap(err, "cannot create store") 381 } 382 383 s.configListenerId = s.AddConfigListener(func(_, _ *model.Config) { 384 s.configOrLicenseListener() 385 386 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_CONFIG_CHANGED, "", "", "", nil) 387 388 message.Add("config", s.ClientConfigWithComputed()) 389 s.Go(func() { 390 s.Publish(message) 391 }) 392 }) 393 s.licenseListenerId = s.AddLicenseListener(func(oldLicense, newLicense *model.License) { 394 s.configOrLicenseListener() 395 396 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_LICENSE_CHANGED, "", "", "", nil) 397 message.Add("license", s.GetSanitizedClientLicense()) 398 s.Go(func() { 399 s.Publish(message) 400 }) 401 402 }) 403 404 s.telemetryService = telemetry.New(s, s.Store, s.SearchEngine, s.Log) 405 406 emailService, err := NewEmailService(s) 407 if err != nil { 408 return nil, errors.Wrapf(err, "unable to initialize email service") 409 } 410 s.EmailService = emailService 411 412 if model.BuildEnterpriseReady == "true" { 413 s.LoadLicense() 414 } 415 416 s.setupFeatureFlags() 417 418 s.initJobs() 419 420 s.clusterLeaderListenerId = s.AddClusterLeaderChangedListener(func() { 421 mlog.Info("Cluster leader changed. Determining if job schedulers should be running:", mlog.Bool("isLeader", s.IsLeader())) 422 if s.Jobs != nil && s.Jobs.Schedulers != nil { 423 s.Jobs.Schedulers.HandleClusterLeaderChange(s.IsLeader()) 424 } 425 s.setupFeatureFlags() 426 }) 427 428 if s.joinCluster && s.Cluster != nil { 429 s.Cluster.StartInterNodeCommunication() 430 } 431 432 if err = s.ensureAsymmetricSigningKey(); err != nil { 433 return nil, errors.Wrapf(err, "unable to ensure asymmetric signing key") 434 } 435 436 if err = s.ensurePostActionCookieSecret(); err != nil { 437 return nil, errors.Wrapf(err, "unable to ensure PostAction cookie secret") 438 } 439 440 if err = s.ensureInstallationDate(); err != nil { 441 return nil, errors.Wrapf(err, "unable to ensure installation date") 442 } 443 444 if err = s.ensureFirstServerRunTimestamp(); err != nil { 445 return nil, errors.Wrapf(err, "unable to ensure first run timestamp") 446 } 447 448 s.regenerateClientConfig() 449 450 subpath, err := utils.GetSubpathFromConfig(s.Config()) 451 if err != nil { 452 return nil, errors.Wrap(err, "failed to parse SiteURL subpath") 453 } 454 s.Router = s.RootRouter.PathPrefix(subpath).Subrouter() 455 456 // FakeApp: remove this when we have the ServePluginRequest and ServePluginPublicRequest migrated in the server 457 pluginsRoute := s.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter() 458 pluginsRoute.HandleFunc("", fakeApp.ServePluginRequest) 459 pluginsRoute.HandleFunc("/public/{public_file:.*}", fakeApp.ServePluginPublicRequest) 460 pluginsRoute.HandleFunc("/{anything:.*}", fakeApp.ServePluginRequest) 461 462 // If configured with a subpath, redirect 404s at the root back into the subpath. 463 if subpath != "/" { 464 s.RootRouter.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 465 r.URL.Path = path.Join(subpath, r.URL.Path) 466 http.Redirect(w, r, r.URL.String(), http.StatusFound) 467 }) 468 } 469 470 s.WebSocketRouter = &WebSocketRouter{ 471 server: s, 472 handlers: make(map[string]webSocketHandler), 473 } 474 s.WebSocketRouter.app = fakeApp 475 476 mailConfig := s.MailServiceConfig() 477 478 if nErr := mailservice.TestConnection(mailConfig); nErr != nil { 479 mlog.Error("Mail server connection test is failed", mlog.Err(nErr)) 480 } 481 482 if _, err = url.ParseRequestURI(*s.Config().ServiceSettings.SiteURL); err != nil { 483 mlog.Error("SiteURL must be set. Some features will operate incorrectly if the SiteURL is not set. See documentation for details: http://about.mattermost.com/default-site-url") 484 } 485 486 backend, appErr := s.FileBackend() 487 if appErr != nil { 488 mlog.Error("Problem with file storage settings", mlog.Err(appErr)) 489 } else { 490 if nErr := backend.TestConnection(); nErr != nil { 491 mlog.Error("Problem with file storage settings", mlog.Err(nErr)) 492 } 493 } 494 495 s.timezones = timezones.New() 496 // Start email batching because it's not like the other jobs 497 s.AddConfigListener(func(_, _ *model.Config) { 498 s.EmailService.InitEmailBatching() 499 }) 500 501 // Start plugin health check job 502 pluginsEnvironment := s.PluginsEnvironment 503 if pluginsEnvironment != nil { 504 pluginsEnvironment.InitPluginHealthCheckJob(*s.Config().PluginSettings.Enable && *s.Config().PluginSettings.EnableHealthCheck) 505 } 506 s.AddConfigListener(func(_, c *model.Config) { 507 s.PluginsLock.RLock() 508 pluginsEnvironment := s.PluginsEnvironment 509 s.PluginsLock.RUnlock() 510 if pluginsEnvironment != nil { 511 pluginsEnvironment.InitPluginHealthCheckJob(*s.Config().PluginSettings.Enable && *c.PluginSettings.EnableHealthCheck) 512 } 513 }) 514 515 logCurrentVersion := fmt.Sprintf("Current version is %v (%v/%v/%v/%v)", model.CurrentVersion, model.BuildNumber, model.BuildDate, model.BuildHash, model.BuildHashEnterprise) 516 mlog.Info( 517 logCurrentVersion, 518 mlog.String("current_version", model.CurrentVersion), 519 mlog.String("build_number", model.BuildNumber), 520 mlog.String("build_date", model.BuildDate), 521 mlog.String("build_hash", model.BuildHash), 522 mlog.String("build_hash_enterprise", model.BuildHashEnterprise), 523 ) 524 if model.BuildEnterpriseReady == "true" { 525 mlog.Info("Enterprise Build", mlog.Bool("enterprise_build", true)) 526 } else { 527 mlog.Info("Team Edition Build", mlog.Bool("enterprise_build", false)) 528 } 529 530 pwd, _ := os.Getwd() 531 mlog.Info("Printing current working", mlog.String("directory", pwd)) 532 mlog.Info("Loaded config", mlog.String("source", s.configStore.String())) 533 534 s.checkPushNotificationServerUrl() 535 536 license := s.License() 537 if license == nil { 538 s.UpdateConfig(func(cfg *model.Config) { 539 cfg.TeamSettings.MaxNotificationsPerChannel = &MaxNotificationsPerChannelDefault 540 }) 541 } 542 543 s.ReloadConfig() 544 545 allowAdvancedLogging := license != nil && *license.Features.AdvancedLogging 546 547 if s.Audit == nil { 548 s.Audit = &audit.Audit{} 549 s.Audit.Init(audit.DefMaxQueueSize) 550 if err = s.configureAudit(s.Audit, allowAdvancedLogging); err != nil { 551 mlog.Error("Error configuring audit", mlog.Err(err)) 552 } 553 } 554 555 s.removeUnlicensedLogTargets(license) 556 s.enableLoggingMetrics() 557 558 s.loggerLicenseListenerId = s.AddLicenseListener(func(oldLicense, newLicense *model.License) { 559 s.removeUnlicensedLogTargets(newLicense) 560 s.enableLoggingMetrics() 561 }) 562 563 // Enable developer settings if this is a "dev" build 564 if model.BuildNumber == "dev" { 565 s.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true }) 566 } 567 568 if err = s.Store.Status().ResetAll(); err != nil { 569 mlog.Error("Error to reset the server status.", mlog.Err(err)) 570 } 571 572 if s.startMetrics && s.Metrics != nil { 573 s.Metrics.StartServer() 574 } 575 576 s.SearchEngine.UpdateConfig(s.Config()) 577 searchConfigListenerId, searchLicenseListenerId := s.StartSearchEngine() 578 s.searchConfigListenerId = searchConfigListenerId 579 s.searchLicenseListenerId = searchLicenseListenerId 580 581 // if enabled - perform initial product notices fetch 582 if *s.Config().AnnouncementSettings.AdminNoticesEnabled || *s.Config().AnnouncementSettings.UserNoticesEnabled { 583 go fakeApp.UpdateProductNotices() 584 } 585 586 return s, nil 587 } 588 589 func maxInt(a, b int) int { 590 if a > b { 591 return a 592 } 593 return b 594 } 595 596 func (s *Server) RunJobs() { 597 if s.runjobs { 598 s.Go(func() { 599 runSecurityJob(s) 600 }) 601 s.Go(func() { 602 firstRun, err := s.getFirstServerRunTimestamp() 603 if err != nil { 604 mlog.Warn("Fetching time of first server run failed. Setting to 'now'.") 605 s.ensureFirstServerRunTimestamp() 606 firstRun = utils.MillisFromTime(time.Now()) 607 } 608 s.telemetryService.RunTelemetryJob(firstRun) 609 }) 610 s.Go(func() { 611 runSessionCleanupJob(s) 612 }) 613 s.Go(func() { 614 runTokenCleanupJob(s) 615 }) 616 s.Go(func() { 617 runCommandWebhookCleanupJob(s) 618 }) 619 620 if complianceI := s.Compliance; complianceI != nil { 621 complianceI.StartComplianceDailyJob() 622 } 623 624 if *s.Config().JobSettings.RunJobs && s.Jobs != nil { 625 s.Jobs.StartWorkers() 626 } 627 if *s.Config().JobSettings.RunScheduler && s.Jobs != nil { 628 s.Jobs.StartSchedulers() 629 } 630 631 if *s.Config().ServiceSettings.EnableAWSMetering { 632 runReportToAWSMeterJob(s) 633 } 634 } 635 } 636 637 // Global app options that should be applied to apps created by this server 638 func (s *Server) AppOptions() []AppOption { 639 return []AppOption{ 640 ServerConnector(s), 641 } 642 } 643 644 // Return Database type (postgres or mysql) and current version of Mattermost 645 func (s *Server) DatabaseTypeAndMattermostVersion() (string, string) { 646 mattermostVersion, _ := s.Store.System().GetByName("Version") 647 return *s.Config().SqlSettings.DriverName, mattermostVersion.Value 648 } 649 650 // initLogging initializes and configures the logger. This may be called more than once. 651 func (s *Server) initLogging() error { 652 if s.Log == nil { 653 s.Log = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(&s.Config().LogSettings, utils.GetLogFileLocation)) 654 } 655 656 // Use this app logger as the global logger (eventually remove all instances of global logging). 657 // This is deferred because a copy is made of the logger and it must be fully configured before 658 // the copy is made. 659 defer mlog.InitGlobalLogger(s.Log) 660 661 // Redirect default Go logger to this logger. 662 defer mlog.RedirectStdLog(s.Log) 663 664 if s.NotificationsLog == nil { 665 notificationLogSettings := utils.GetLogSettingsFromNotificationsLogSettings(&s.Config().NotificationLogSettings) 666 s.NotificationsLog = mlog.NewLogger(utils.MloggerConfigFromLoggerConfig(notificationLogSettings, utils.GetNotificationsLogFileLocation)). 667 WithCallerSkip(1).With(mlog.String("logSource", "notifications")) 668 } 669 670 if s.logListenerId != "" { 671 s.RemoveConfigListener(s.logListenerId) 672 } 673 s.logListenerId = s.AddConfigListener(func(_, after *model.Config) { 674 s.Log.ChangeLevels(utils.MloggerConfigFromLoggerConfig(&after.LogSettings, utils.GetLogFileLocation)) 675 676 notificationLogSettings := utils.GetLogSettingsFromNotificationsLogSettings(&after.NotificationLogSettings) 677 s.NotificationsLog.ChangeLevels(utils.MloggerConfigFromLoggerConfig(notificationLogSettings, utils.GetNotificationsLogFileLocation)) 678 }) 679 680 // Configure advanced logging. 681 // Advanced logging is E20 only, however logging must be initialized before the license 682 // file is loaded. If no valid E20 license exists then advanced logging will be 683 // shutdown once license is loaded/checked. 684 if *s.Config().LogSettings.AdvancedLoggingConfig != "" { 685 dsn := *s.Config().LogSettings.AdvancedLoggingConfig 686 isJson := config.IsJsonMap(dsn) 687 688 // If this is a file based config we need the full path so it can be watched. 689 if !isJson && strings.HasPrefix(s.configStore.String(), "file://") && !filepath.IsAbs(dsn) { 690 configPath := strings.TrimPrefix(s.configStore.String(), "file://") 691 dsn = filepath.Join(filepath.Dir(configPath), dsn) 692 } 693 694 cfg, err := config.NewLogConfigSrc(dsn, isJson, s.configStore) 695 if err != nil { 696 return fmt.Errorf("invalid advanced logging config, %w", err) 697 } 698 699 if err := s.Log.ConfigAdvancedLogging(cfg.Get()); err != nil { 700 return fmt.Errorf("error configuring advanced logging, %w", err) 701 } 702 703 if !isJson { 704 mlog.Info("Loaded advanced logging config", mlog.String("source", dsn)) 705 } 706 707 listenerId := cfg.AddListener(func(_, newCfg mlog.LogTargetCfg) { 708 if err := s.Log.ConfigAdvancedLogging(newCfg); err != nil { 709 mlog.Error("Error re-configuring advanced logging", mlog.Err(err)) 710 } else { 711 mlog.Info("Re-configured advanced logging") 712 } 713 }) 714 715 // In case initLogging is called more than once. 716 if s.advancedLogListenerCleanup != nil { 717 s.advancedLogListenerCleanup() 718 } 719 720 s.advancedLogListenerCleanup = func() { 721 cfg.RemoveListener(listenerId) 722 } 723 } 724 return nil 725 } 726 727 func (s *Server) removeUnlicensedLogTargets(license *model.License) { 728 if license != nil && *license.Features.AdvancedLogging { 729 // advanced logging enabled via license; no need to remove any targets 730 return 731 } 732 733 timeoutCtx, cancelCtx := context.WithTimeout(context.Background(), time.Second*10) 734 defer cancelCtx() 735 736 mlog.RemoveTargets(timeoutCtx, func(ti mlog.TargetInfo) bool { 737 return ti.Type != "*target.Writer" && ti.Type != "*target.File" 738 }) 739 } 740 741 func (s *Server) enableLoggingMetrics() { 742 if s.Metrics == nil { 743 return 744 } 745 746 if err := mlog.EnableMetrics(s.Metrics.GetLoggerMetricsCollector()); err != nil { 747 mlog.Error("Failed to enable advanced logging metrics", mlog.Err(err)) 748 } else { 749 mlog.Debug("Advanced logging metrics enabled") 750 } 751 } 752 753 const TimeToWaitForConnectionsToCloseOnServerShutdown = time.Second 754 755 func (s *Server) StopHTTPServer() { 756 if s.Server != nil { 757 ctx, cancel := context.WithTimeout(context.Background(), TimeToWaitForConnectionsToCloseOnServerShutdown) 758 defer cancel() 759 didShutdown := false 760 for s.didFinishListen != nil && !didShutdown { 761 if err := s.Server.Shutdown(ctx); err != nil { 762 mlog.Warn("Unable to shutdown server", mlog.Err(err)) 763 } 764 timer := time.NewTimer(time.Millisecond * 50) 765 select { 766 case <-s.didFinishListen: 767 didShutdown = true 768 case <-timer.C: 769 } 770 timer.Stop() 771 } 772 s.Server.Close() 773 s.Server = nil 774 } 775 } 776 777 func (s *Server) Shutdown() { 778 mlog.Info("Stopping Server...") 779 780 defer sentry.Flush(2 * time.Second) 781 782 s.HubStop() 783 s.ShutDownPlugins() 784 s.RemoveLicenseListener(s.licenseListenerId) 785 s.RemoveLicenseListener(s.loggerLicenseListenerId) 786 s.RemoveClusterLeaderChangedListener(s.clusterLeaderListenerId) 787 788 if s.tracer != nil { 789 if err := s.tracer.Close(); err != nil { 790 mlog.Warn("Unable to cleanly shutdown opentracing client", mlog.Err(err)) 791 } 792 } 793 794 err := s.telemetryService.Shutdown() 795 if err != nil { 796 mlog.Warn("Unable to cleanly shutdown telemetry client", mlog.Err(err)) 797 } 798 799 s.StopHTTPServer() 800 s.stopLocalModeServer() 801 // Push notification hub needs to be shutdown after HTTP server 802 // to prevent stray requests from generating a push notification after it's shut down. 803 s.StopPushNotificationsHubWorkers() 804 805 s.WaitForGoroutines() 806 807 if s.htmlTemplateWatcher != nil { 808 s.htmlTemplateWatcher.Close() 809 } 810 811 if s.advancedLogListenerCleanup != nil { 812 s.advancedLogListenerCleanup() 813 s.advancedLogListenerCleanup = nil 814 } 815 816 s.RemoveConfigListener(s.configListenerId) 817 s.RemoveConfigListener(s.logListenerId) 818 s.stopSearchEngine() 819 820 s.Audit.Shutdown() 821 822 s.stopFeatureFlagUpdateJob() 823 824 s.configStore.Close() 825 826 if s.Cluster != nil { 827 s.Cluster.StopInterNodeCommunication() 828 } 829 830 if s.Metrics != nil { 831 s.Metrics.StopServer() 832 } 833 834 // This must be done after the cluster is stopped. 835 if s.Jobs != nil && s.runjobs { 836 s.Jobs.StopWorkers() 837 s.Jobs.StopSchedulers() 838 } 839 840 if s.Store != nil { 841 s.Store.Close() 842 } 843 844 if s.CacheProvider != nil { 845 if err = s.CacheProvider.Close(); err != nil { 846 mlog.Warn("Unable to cleanly shutdown cache", mlog.Err(err)) 847 } 848 } 849 850 timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), time.Second*15) 851 defer timeoutCancel() 852 if err := mlog.Flush(timeoutCtx); err != nil { 853 mlog.Warn("Error flushing logs", mlog.Err(err)) 854 } 855 856 mlog.Info("Server stopped") 857 858 // this should just write the "server stopped" record, the rest are already flushed. 859 timeoutCtx2, timeoutCancel2 := context.WithTimeout(context.Background(), time.Second*5) 860 defer timeoutCancel2() 861 _ = mlog.ShutdownAdvancedLogging(timeoutCtx2) 862 } 863 864 func (s *Server) Restart() error { 865 percentage, err := s.UpgradeToE0Status() 866 if err != nil || percentage != 100 { 867 return errors.Wrap(err, "unable to restart because the system has not been upgraded") 868 } 869 s.Shutdown() 870 871 argv0, err := exec.LookPath(os.Args[0]) 872 if err != nil { 873 return err 874 } 875 876 if _, err = os.Stat(argv0); err != nil { 877 return err 878 } 879 880 mlog.Info("Restarting server") 881 return syscall.Exec(argv0, os.Args, os.Environ()) 882 } 883 884 func (s *Server) isUpgradedFromTE() bool { 885 val, err := s.Store.System().GetByName(model.SYSTEM_UPGRADED_FROM_TE_ID) 886 if err != nil { 887 return false 888 } 889 return val.Value == "true" 890 } 891 892 func (s *Server) CanIUpgradeToE0() error { 893 return upgrader.CanIUpgradeToE0() 894 } 895 896 func (s *Server) UpgradeToE0() error { 897 if err := upgrader.UpgradeToE0(); err != nil { 898 return err 899 } 900 upgradedFromTE := &model.System{Name: model.SYSTEM_UPGRADED_FROM_TE_ID, Value: "true"} 901 s.Store.System().Save(upgradedFromTE) 902 return nil 903 } 904 905 func (s *Server) UpgradeToE0Status() (int64, error) { 906 return upgrader.UpgradeToE0Status() 907 } 908 909 // Go creates a goroutine, but maintains a record of it to ensure that execution completes before 910 // the server is shutdown. 911 func (s *Server) Go(f func()) { 912 atomic.AddInt32(&s.goroutineCount, 1) 913 914 go func() { 915 f() 916 917 atomic.AddInt32(&s.goroutineCount, -1) 918 select { 919 case s.goroutineExitSignal <- struct{}{}: 920 default: 921 } 922 }() 923 } 924 925 // WaitForGoroutines blocks until all goroutines created by App.Go exit. 926 func (s *Server) WaitForGoroutines() { 927 for atomic.LoadInt32(&s.goroutineCount) != 0 { 928 <-s.goroutineExitSignal 929 } 930 } 931 932 var corsAllowedMethods = []string{ 933 "POST", 934 "GET", 935 "OPTIONS", 936 "PUT", 937 "PATCH", 938 "DELETE", 939 } 940 941 // golang.org/x/crypto/acme/autocert/autocert.go 942 func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) { 943 if r.Method != "GET" && r.Method != "HEAD" { 944 http.Error(w, "Use HTTPS", http.StatusBadRequest) 945 return 946 } 947 target := "https://" + stripPort(r.Host) + r.URL.RequestURI() 948 http.Redirect(w, r, target, http.StatusFound) 949 } 950 951 // golang.org/x/crypto/acme/autocert/autocert.go 952 func stripPort(hostport string) string { 953 host, _, err := net.SplitHostPort(hostport) 954 if err != nil { 955 return hostport 956 } 957 return net.JoinHostPort(host, "443") 958 } 959 960 func (s *Server) Start() error { 961 mlog.Info("Starting Server...") 962 963 var handler http.Handler = s.RootRouter 964 965 if *s.Config().LogSettings.EnableDiagnostics && *s.Config().LogSettings.EnableSentry && !strings.Contains(SentryDSN, "placeholder") { 966 sentryHandler := sentryhttp.New(sentryhttp.Options{ 967 Repanic: true, 968 }) 969 handler = sentryHandler.Handle(handler) 970 } 971 972 if allowedOrigins := *s.Config().ServiceSettings.AllowCorsFrom; allowedOrigins != "" { 973 exposedCorsHeaders := *s.Config().ServiceSettings.CorsExposedHeaders 974 allowCredentials := *s.Config().ServiceSettings.CorsAllowCredentials 975 debug := *s.Config().ServiceSettings.CorsDebug 976 corsWrapper := cors.New(cors.Options{ 977 AllowedOrigins: strings.Fields(allowedOrigins), 978 AllowedMethods: corsAllowedMethods, 979 AllowedHeaders: []string{"*"}, 980 ExposedHeaders: strings.Fields(exposedCorsHeaders), 981 MaxAge: 86400, 982 AllowCredentials: allowCredentials, 983 Debug: debug, 984 }) 985 986 // If we have debugging of CORS turned on then forward messages to logs 987 if debug { 988 corsWrapper.Log = s.Log.StdLog(mlog.String("source", "cors")) 989 } 990 991 handler = corsWrapper.Handler(handler) 992 } 993 994 if *s.Config().RateLimitSettings.Enable { 995 mlog.Info("RateLimiter is enabled") 996 997 rateLimiter, err := NewRateLimiter(&s.Config().RateLimitSettings, s.Config().ServiceSettings.TrustedProxyIPHeader) 998 if err != nil { 999 return err 1000 } 1001 1002 s.RateLimiter = rateLimiter 1003 handler = rateLimiter.RateLimitHandler(handler) 1004 } 1005 s.Busy = NewBusy(s.Cluster) 1006 1007 // Creating a logger for logging errors from http.Server at error level 1008 errStdLog, err := s.Log.StdLogAt(mlog.LevelError, mlog.String("source", "httpserver")) 1009 if err != nil { 1010 return err 1011 } 1012 1013 s.Server = &http.Server{ 1014 Handler: handler, 1015 ReadTimeout: time.Duration(*s.Config().ServiceSettings.ReadTimeout) * time.Second, 1016 WriteTimeout: time.Duration(*s.Config().ServiceSettings.WriteTimeout) * time.Second, 1017 IdleTimeout: time.Duration(*s.Config().ServiceSettings.IdleTimeout) * time.Second, 1018 ErrorLog: errStdLog, 1019 } 1020 1021 addr := *s.Config().ServiceSettings.ListenAddress 1022 if addr == "" { 1023 if *s.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { 1024 addr = ":https" 1025 } else { 1026 addr = ":http" 1027 } 1028 } 1029 1030 listener, err := net.Listen("tcp", addr) 1031 if err != nil { 1032 return errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err) 1033 } 1034 s.ListenAddr = listener.Addr().(*net.TCPAddr) 1035 1036 logListeningPort := fmt.Sprintf("Server is listening on %v", listener.Addr().String()) 1037 mlog.Info(logListeningPort, mlog.String("address", listener.Addr().String())) 1038 1039 m := &autocert.Manager{ 1040 Cache: autocert.DirCache(*s.Config().ServiceSettings.LetsEncryptCertificateCacheFile), 1041 Prompt: autocert.AcceptTOS, 1042 } 1043 1044 if *s.Config().ServiceSettings.Forward80To443 { 1045 if host, port, err := net.SplitHostPort(addr); err != nil { 1046 mlog.Error("Unable to setup forwarding", mlog.Err(err)) 1047 } else if port != "443" { 1048 return fmt.Errorf(utils.T("api.server.start_server.forward80to443.enabled_but_listening_on_wrong_port"), port) 1049 } else { 1050 httpListenAddress := net.JoinHostPort(host, "http") 1051 1052 if *s.Config().ServiceSettings.UseLetsEncrypt { 1053 server := &http.Server{ 1054 Addr: httpListenAddress, 1055 Handler: m.HTTPHandler(nil), 1056 ErrorLog: s.Log.StdLog(mlog.String("source", "le_forwarder_server")), 1057 } 1058 go server.ListenAndServe() 1059 } else { 1060 go func() { 1061 redirectListener, err := net.Listen("tcp", httpListenAddress) 1062 if err != nil { 1063 mlog.Error("Unable to setup forwarding", mlog.Err(err)) 1064 return 1065 } 1066 defer redirectListener.Close() 1067 1068 server := &http.Server{ 1069 Handler: http.HandlerFunc(handleHTTPRedirect), 1070 ErrorLog: s.Log.StdLog(mlog.String("source", "forwarder_server")), 1071 } 1072 server.Serve(redirectListener) 1073 }() 1074 } 1075 } 1076 } else if *s.Config().ServiceSettings.UseLetsEncrypt { 1077 return errors.New(utils.T("api.server.start_server.forward80to443.disabled_while_using_lets_encrypt")) 1078 } 1079 1080 s.didFinishListen = make(chan struct{}) 1081 go func() { 1082 var err error 1083 if *s.Config().ServiceSettings.ConnectionSecurity == model.CONN_SECURITY_TLS { 1084 1085 tlsConfig := &tls.Config{ 1086 PreferServerCipherSuites: true, 1087 CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256}, 1088 } 1089 1090 switch *s.Config().ServiceSettings.TLSMinVer { 1091 case "1.0": 1092 tlsConfig.MinVersion = tls.VersionTLS10 1093 case "1.1": 1094 tlsConfig.MinVersion = tls.VersionTLS11 1095 default: 1096 tlsConfig.MinVersion = tls.VersionTLS12 1097 } 1098 1099 defaultCiphers := []uint16{ 1100 tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 1101 tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 1102 tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 1103 tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 1104 tls.TLS_RSA_WITH_AES_128_GCM_SHA256, 1105 tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 1106 } 1107 1108 if len(s.Config().ServiceSettings.TLSOverwriteCiphers) == 0 { 1109 tlsConfig.CipherSuites = defaultCiphers 1110 } else { 1111 var cipherSuites []uint16 1112 for _, cipher := range s.Config().ServiceSettings.TLSOverwriteCiphers { 1113 value, ok := model.ServerTLSSupportedCiphers[cipher] 1114 1115 if !ok { 1116 mlog.Warn("Unsupported cipher passed", mlog.String("cipher", cipher)) 1117 continue 1118 } 1119 1120 cipherSuites = append(cipherSuites, value) 1121 } 1122 1123 if len(cipherSuites) == 0 { 1124 mlog.Warn("No supported ciphers passed, fallback to default cipher suite") 1125 cipherSuites = defaultCiphers 1126 } 1127 1128 tlsConfig.CipherSuites = cipherSuites 1129 } 1130 1131 certFile := "" 1132 keyFile := "" 1133 1134 if *s.Config().ServiceSettings.UseLetsEncrypt { 1135 tlsConfig.GetCertificate = m.GetCertificate 1136 tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") 1137 } else { 1138 certFile = *s.Config().ServiceSettings.TLSCertFile 1139 keyFile = *s.Config().ServiceSettings.TLSKeyFile 1140 } 1141 1142 s.Server.TLSConfig = tlsConfig 1143 err = s.Server.ServeTLS(listener, certFile, keyFile) 1144 } else { 1145 err = s.Server.Serve(listener) 1146 } 1147 1148 if err != nil && err != http.ErrServerClosed { 1149 mlog.Critical("Error starting server", mlog.Err(err)) 1150 time.Sleep(time.Second) 1151 } 1152 1153 close(s.didFinishListen) 1154 }() 1155 1156 if *s.Config().ServiceSettings.EnableLocalMode { 1157 if err := s.startLocalModeServer(); err != nil { 1158 mlog.Critical(err.Error()) 1159 } 1160 } 1161 1162 return nil 1163 } 1164 1165 func (s *Server) startLocalModeServer() error { 1166 s.localModeServer = &http.Server{ 1167 Handler: s.LocalRouter, 1168 } 1169 1170 socket := *s.configStore.Get().ServiceSettings.LocalModeSocketLocation 1171 unixListener, err := net.Listen("unix", socket) 1172 if err != nil { 1173 return errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err) 1174 } 1175 if err = os.Chmod(socket, 0600); err != nil { 1176 return errors.Wrapf(err, utils.T("api.server.start_server.starting.critical"), err) 1177 } 1178 1179 go func() { 1180 err = s.localModeServer.Serve(unixListener) 1181 if err != nil && err != http.ErrServerClosed { 1182 mlog.Critical("Error starting unix socket server", mlog.Err(err)) 1183 } 1184 }() 1185 return nil 1186 } 1187 1188 func (s *Server) stopLocalModeServer() { 1189 if s.localModeServer != nil { 1190 s.localModeServer.Close() 1191 } 1192 } 1193 1194 func (a *App) OriginChecker() func(*http.Request) bool { 1195 if allowed := *a.Config().ServiceSettings.AllowCorsFrom; allowed != "" { 1196 if allowed != "*" { 1197 siteURL, err := url.Parse(*a.Config().ServiceSettings.SiteURL) 1198 if err == nil { 1199 siteURL.Path = "" 1200 allowed += " " + siteURL.String() 1201 } 1202 } 1203 1204 return utils.OriginChecker(allowed) 1205 } 1206 return nil 1207 } 1208 1209 func (s *Server) checkPushNotificationServerUrl() { 1210 notificationServer := *s.Config().EmailSettings.PushNotificationServer 1211 if strings.HasPrefix(notificationServer, "http://") { 1212 mlog.Warn("Your push notification server is configured with HTTP. For improved security, update to HTTPS in your configuration.") 1213 } 1214 } 1215 1216 func runSecurityJob(s *Server) { 1217 doSecurity(s) 1218 model.CreateRecurringTask("Security", func() { 1219 doSecurity(s) 1220 }, time.Hour*4) 1221 } 1222 1223 func runTokenCleanupJob(s *Server) { 1224 doTokenCleanup(s) 1225 model.CreateRecurringTask("Token Cleanup", func() { 1226 doTokenCleanup(s) 1227 }, time.Hour*1) 1228 } 1229 1230 func runCommandWebhookCleanupJob(s *Server) { 1231 doCommandWebhookCleanup(s) 1232 model.CreateRecurringTask("Command Hook Cleanup", func() { 1233 doCommandWebhookCleanup(s) 1234 }, time.Hour*1) 1235 } 1236 1237 func runSessionCleanupJob(s *Server) { 1238 doSessionCleanup(s) 1239 model.CreateRecurringTask("Session Cleanup", func() { 1240 doSessionCleanup(s) 1241 }, time.Hour*24) 1242 } 1243 1244 func runLicenseExpirationCheckJob(a *App) { 1245 doLicenseExpirationCheck(a) 1246 model.CreateRecurringTask("License Expiration Check", func() { 1247 doLicenseExpirationCheck(a) 1248 }, time.Hour*24) 1249 } 1250 1251 func runReportToAWSMeterJob(s *Server) { 1252 model.CreateRecurringTask("Collect and send usage report to AWS Metering Service", func() { 1253 doReportUsageToAWSMeteringService(s) 1254 }, time.Hour*model.AWS_METERING_REPORT_INTERVAL) 1255 } 1256 1257 func doReportUsageToAWSMeteringService(s *Server) { 1258 awsMeter := awsmeter.New(s.Store, s.Config()) 1259 if awsMeter == nil { 1260 mlog.Error("Cannot obtain instance of AWS Metering Service.") 1261 return 1262 } 1263 1264 dimensions := []string{model.AWS_METERING_DIMENSION_USAGE_HRS} 1265 reports := awsMeter.GetUserCategoryUsage(dimensions, time.Now().UTC(), time.Now().Add(-model.AWS_METERING_REPORT_INTERVAL*time.Hour).UTC()) 1266 awsMeter.ReportUserCategoryUsage(reports) 1267 } 1268 1269 func runCheckWarnMetricStatusJob(a *App) { 1270 doCheckWarnMetricStatus(a) 1271 model.CreateRecurringTask("Check Warn Metric Status Job", func() { 1272 doCheckWarnMetricStatus(a) 1273 }, time.Hour*model.WARN_METRIC_JOB_INTERVAL) 1274 } 1275 1276 func doSecurity(s *Server) { 1277 s.DoSecurityUpdateCheck() 1278 } 1279 1280 func doTokenCleanup(s *Server) { 1281 s.Store.Token().Cleanup() 1282 } 1283 1284 func doCommandWebhookCleanup(s *Server) { 1285 s.Store.CommandWebhook().Cleanup() 1286 } 1287 1288 const ( 1289 SessionsCleanupBatchSize = 1000 1290 ) 1291 1292 func doSessionCleanup(s *Server) { 1293 s.Store.Session().Cleanup(model.GetMillis(), SessionsCleanupBatchSize) 1294 } 1295 1296 func doCheckWarnMetricStatus(a *App) { 1297 license := a.Srv().License() 1298 if license != nil { 1299 mlog.Debug("License is present, skip") 1300 return 1301 } 1302 1303 // Get the system fields values from store 1304 systemDataList, nErr := a.Srv().Store.System().Get() 1305 if nErr != nil { 1306 mlog.Error("No system properties obtained", mlog.Err(nErr)) 1307 return 1308 } 1309 1310 warnMetricStatusFromStore := make(map[string]string) 1311 1312 for key, value := range systemDataList { 1313 if strings.HasPrefix(key, model.WARN_METRIC_STATUS_STORE_PREFIX) { 1314 if _, ok := model.WarnMetricsTable[key]; ok { 1315 warnMetricStatusFromStore[key] = value 1316 if value == model.WARN_METRIC_STATUS_ACK { 1317 // If any warn metric has already been acked, we return 1318 mlog.Debug("Warn metrics have been acked, skip") 1319 return 1320 } 1321 } 1322 } 1323 } 1324 1325 lastWarnMetricRunTimestamp, err := a.Srv().getLastWarnMetricTimestamp() 1326 if err != nil { 1327 mlog.Debug("Cannot obtain last advisory run timestamp", mlog.Err(err)) 1328 } else { 1329 currentTime := utils.MillisFromTime(time.Now()) 1330 // If the admin advisory has already been shown in the last 7 days 1331 if (currentTime-lastWarnMetricRunTimestamp)/(model.WARN_METRIC_JOB_WAIT_TIME) < 1 { 1332 mlog.Debug("No advisories should be shown during the wait interval time") 1333 return 1334 } 1335 } 1336 1337 numberOfActiveUsers, err0 := a.Srv().Store.User().Count(model.UserCountOptions{}) 1338 if err0 != nil { 1339 mlog.Debug("Error attempting to get active registered users.", mlog.Err(err0)) 1340 } 1341 1342 teamCount, err1 := a.Srv().Store.Team().AnalyticsTeamCount(false) 1343 if err1 != nil { 1344 mlog.Debug("Error attempting to get number of teams.", mlog.Err(err1)) 1345 } 1346 1347 openChannelCount, err2 := a.Srv().Store.Channel().AnalyticsTypeCount("", model.CHANNEL_OPEN) 1348 if err2 != nil { 1349 mlog.Debug("Error attempting to get number of public channels.", mlog.Err(err2)) 1350 } 1351 1352 // If an account is created with a different email domain 1353 // Search for an entry that has an email account different from the current domain 1354 // Get domain account from site url 1355 localDomainAccount := utils.GetHostnameFromSiteURL(*a.Srv().Config().ServiceSettings.SiteURL) 1356 isDiffEmailAccount, err3 := a.Srv().Store.User().AnalyticsGetExternalUsers(localDomainAccount) 1357 if err3 != nil { 1358 mlog.Debug("Error attempting to get number of private channels.", mlog.Err(err3)) 1359 } 1360 1361 warnMetrics := []model.WarnMetric{} 1362 1363 if numberOfActiveUsers < model.WARN_METRIC_NUMBER_OF_ACTIVE_USERS_25 { 1364 return 1365 } else if teamCount >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_TEAMS_5].Limit && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_TEAMS_5] != model.WARN_METRIC_STATUS_RUNONCE { 1366 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_TEAMS_5]) 1367 } else if *a.Config().ServiceSettings.EnableMultifactorAuthentication && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_MFA] != model.WARN_METRIC_STATUS_RUNONCE { 1368 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_MFA]) 1369 } else if isDiffEmailAccount && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_EMAIL_DOMAIN] != model.WARN_METRIC_STATUS_RUNONCE { 1370 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_EMAIL_DOMAIN]) 1371 } else if openChannelCount >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_CHANNELS_50].Limit && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_CHANNELS_50] != model.WARN_METRIC_STATUS_RUNONCE { 1372 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_CHANNELS_50]) 1373 } 1374 1375 // If the system did not cross any of the thresholds for the Contextual Advisories 1376 if len(warnMetrics) == 0 { 1377 if numberOfActiveUsers >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_100].Limit && numberOfActiveUsers < model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200].Limit && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_100] != model.WARN_METRIC_STATUS_RUNONCE { 1378 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_100]) 1379 } else if numberOfActiveUsers >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200].Limit && numberOfActiveUsers < model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_300].Limit && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200] != model.WARN_METRIC_STATUS_RUNONCE { 1380 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_200]) 1381 } else if numberOfActiveUsers >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_300].Limit && numberOfActiveUsers < model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500].Limit && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_300] != model.WARN_METRIC_STATUS_RUNONCE { 1382 warnMetrics = append(warnMetrics, model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_300]) 1383 } else if numberOfActiveUsers >= model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500].Limit { 1384 var tWarnMetric model.WarnMetric 1385 1386 if warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500] != model.WARN_METRIC_STATUS_RUNONCE { 1387 tWarnMetric = model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_ACTIVE_USERS_500] 1388 } 1389 1390 postsCount, err4 := a.Srv().Store.Post().AnalyticsPostCount("", false, false) 1391 if err4 != nil { 1392 mlog.Debug("Error attempting to get number of posts.", mlog.Err(err4)) 1393 } 1394 1395 if postsCount > model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M].Limit && warnMetricStatusFromStore[model.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M] != model.WARN_METRIC_STATUS_RUNONCE { 1396 tWarnMetric = model.WarnMetricsTable[model.SYSTEM_WARN_METRIC_NUMBER_OF_POSTS_2M] 1397 } 1398 1399 if tWarnMetric != (model.WarnMetric{}) { 1400 warnMetrics = append(warnMetrics, tWarnMetric) 1401 } 1402 } 1403 } 1404 1405 isE0Edition := model.BuildEnterpriseReady == "true" // license == nil was already validated upstream 1406 1407 for _, warnMetric := range warnMetrics { 1408 data, nErr := a.Srv().Store.System().GetByName(warnMetric.Id) 1409 if nErr == nil && data != nil && warnMetric.IsBotOnly && data.Value == model.WARN_METRIC_STATUS_RUNONCE { 1410 mlog.Debug("This metric warning is bot only and ran once") 1411 continue 1412 } 1413 1414 warnMetricStatus, _ := a.getWarnMetricStatusAndDisplayTextsForId(warnMetric.Id, nil, isE0Edition) 1415 if !warnMetric.IsBotOnly { 1416 // Banner and bot metric types - send websocket event every interval 1417 message := model.NewWebSocketEvent(model.WEBSOCKET_WARN_METRIC_STATUS_RECEIVED, "", "", "", nil) 1418 message.Add("warnMetricStatus", warnMetricStatus.ToJson()) 1419 a.Publish(message) 1420 1421 // Banner and bot metric types, send the bot message only once 1422 if data != nil && data.Value == model.WARN_METRIC_STATUS_RUNONCE { 1423 continue 1424 } 1425 } 1426 1427 if nerr := a.notifyAdminsOfWarnMetricStatus(warnMetric.Id, isE0Edition); nerr != nil { 1428 mlog.Error("Failed to send notifications to admin users.", mlog.Err(nerr)) 1429 } 1430 1431 if warnMetric.IsRunOnce { 1432 a.setWarnMetricsStatusForId(warnMetric.Id, model.WARN_METRIC_STATUS_RUNONCE) 1433 } else { 1434 a.setWarnMetricsStatusForId(warnMetric.Id, model.WARN_METRIC_STATUS_LIMIT_REACHED) 1435 } 1436 } 1437 } 1438 1439 func doLicenseExpirationCheck(a *App) { 1440 a.Srv().LoadLicense() 1441 license := a.Srv().License() 1442 1443 if license == nil { 1444 mlog.Debug("License cannot be found.") 1445 return 1446 } 1447 1448 if !license.IsPastGracePeriod() { 1449 mlog.Debug("License is not past the grace period.") 1450 return 1451 } 1452 1453 users, err := a.Srv().Store.User().GetSystemAdminProfiles() 1454 if err != nil { 1455 mlog.Error("Failed to get system admins for license expired message from Mattermost.") 1456 return 1457 } 1458 1459 //send email to admin(s) 1460 for _, user := range users { 1461 user := user 1462 if user.Email == "" { 1463 mlog.Error("Invalid system admin email.", mlog.String("user_email", user.Email)) 1464 continue 1465 } 1466 1467 mlog.Debug("Sending license expired email.", mlog.String("user_email", user.Email)) 1468 a.Srv().Go(func() { 1469 if err := a.Srv().EmailService.SendRemoveExpiredLicenseEmail(user.Email, user.Locale, *a.Config().ServiceSettings.SiteURL); err != nil { 1470 mlog.Error("Error while sending the license expired email.", mlog.String("user_email", user.Email), mlog.Err(err)) 1471 } 1472 }) 1473 } 1474 1475 //remove the license 1476 a.Srv().RemoveLicense() 1477 } 1478 1479 func (s *Server) StartSearchEngine() (string, string) { 1480 if s.SearchEngine.ElasticsearchEngine != nil && s.SearchEngine.ElasticsearchEngine.IsActive() { 1481 s.Go(func() { 1482 if err := s.SearchEngine.ElasticsearchEngine.Start(); err != nil { 1483 s.Log.Error(err.Error()) 1484 } 1485 }) 1486 } 1487 1488 configListenerId := s.AddConfigListener(func(oldConfig *model.Config, newConfig *model.Config) { 1489 if s.SearchEngine == nil { 1490 return 1491 } 1492 s.SearchEngine.UpdateConfig(newConfig) 1493 1494 if s.SearchEngine.ElasticsearchEngine != nil && !*oldConfig.ElasticsearchSettings.EnableIndexing && *newConfig.ElasticsearchSettings.EnableIndexing { 1495 s.Go(func() { 1496 if err := s.SearchEngine.ElasticsearchEngine.Start(); err != nil { 1497 mlog.Error(err.Error()) 1498 } 1499 }) 1500 } else if s.SearchEngine.ElasticsearchEngine != nil && *oldConfig.ElasticsearchSettings.EnableIndexing && !*newConfig.ElasticsearchSettings.EnableIndexing { 1501 s.Go(func() { 1502 if err := s.SearchEngine.ElasticsearchEngine.Stop(); err != nil { 1503 mlog.Error(err.Error()) 1504 } 1505 }) 1506 } else if s.SearchEngine.ElasticsearchEngine != nil && *oldConfig.ElasticsearchSettings.Password != *newConfig.ElasticsearchSettings.Password || *oldConfig.ElasticsearchSettings.Username != *newConfig.ElasticsearchSettings.Username || *oldConfig.ElasticsearchSettings.ConnectionUrl != *newConfig.ElasticsearchSettings.ConnectionUrl || *oldConfig.ElasticsearchSettings.Sniff != *newConfig.ElasticsearchSettings.Sniff { 1507 s.Go(func() { 1508 if *oldConfig.ElasticsearchSettings.EnableIndexing { 1509 if err := s.SearchEngine.ElasticsearchEngine.Stop(); err != nil { 1510 mlog.Error(err.Error()) 1511 } 1512 if err := s.SearchEngine.ElasticsearchEngine.Start(); err != nil { 1513 mlog.Error(err.Error()) 1514 } 1515 } 1516 }) 1517 } 1518 }) 1519 1520 licenseListenerId := s.AddLicenseListener(func(oldLicense, newLicense *model.License) { 1521 if s.SearchEngine == nil { 1522 return 1523 } 1524 if oldLicense == nil && newLicense != nil { 1525 if s.SearchEngine.ElasticsearchEngine != nil && s.SearchEngine.ElasticsearchEngine.IsActive() { 1526 s.Go(func() { 1527 if err := s.SearchEngine.ElasticsearchEngine.Start(); err != nil { 1528 mlog.Error(err.Error()) 1529 } 1530 }) 1531 } 1532 } else if oldLicense != nil && newLicense == nil { 1533 if s.SearchEngine.ElasticsearchEngine != nil { 1534 s.Go(func() { 1535 if err := s.SearchEngine.ElasticsearchEngine.Stop(); err != nil { 1536 mlog.Error(err.Error()) 1537 } 1538 }) 1539 } 1540 } 1541 }) 1542 1543 return configListenerId, licenseListenerId 1544 } 1545 1546 func (s *Server) stopSearchEngine() { 1547 s.RemoveConfigListener(s.searchConfigListenerId) 1548 s.RemoveLicenseListener(s.searchLicenseListenerId) 1549 if s.SearchEngine != nil && s.SearchEngine.ElasticsearchEngine != nil && s.SearchEngine.ElasticsearchEngine.IsActive() { 1550 s.SearchEngine.ElasticsearchEngine.Stop() 1551 } 1552 if s.SearchEngine != nil && s.SearchEngine.BleveEngine != nil && s.SearchEngine.BleveEngine.IsActive() { 1553 s.SearchEngine.BleveEngine.Stop() 1554 } 1555 } 1556 1557 func (s *Server) FileBackend() (filesstore.FileBackend, *model.AppError) { 1558 license := s.License() 1559 backend, err := filesstore.NewFileBackend(s.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance)) 1560 if err != nil { 1561 return nil, model.NewAppError("FileBackend", "api.file.no_driver.app_error", nil, err.Error(), http.StatusInternalServerError) 1562 } 1563 return backend, nil 1564 } 1565 1566 func (s *Server) TotalWebsocketConnections() int { 1567 // This method is only called after the hub is initialized. 1568 // Therefore, no mutex is needed to protect s.hubs. 1569 count := int64(0) 1570 for _, hub := range s.hubs { 1571 count = count + atomic.LoadInt64(&hub.connectionCount) 1572 } 1573 1574 return int(count) 1575 } 1576 1577 func (s *Server) ClusterHealthScore() int { 1578 return s.Cluster.HealthScore() 1579 } 1580 1581 func (s *Server) configOrLicenseListener() { 1582 s.regenerateClientConfig() 1583 } 1584 1585 func (s *Server) ClientConfigHash() string { 1586 return s.clientConfigHash.Load().(string) 1587 } 1588 1589 func (s *Server) initJobs() { 1590 s.Jobs = jobs.NewJobServer(s, s.Store, s.Metrics) 1591 if jobsDataRetentionJobInterface != nil { 1592 s.Jobs.DataRetentionJob = jobsDataRetentionJobInterface(s) 1593 } 1594 if jobsMessageExportJobInterface != nil { 1595 s.Jobs.MessageExportJob = jobsMessageExportJobInterface(s) 1596 } 1597 if jobsElasticsearchAggregatorInterface != nil { 1598 s.Jobs.ElasticsearchAggregator = jobsElasticsearchAggregatorInterface(s) 1599 } 1600 if jobsElasticsearchIndexerInterface != nil { 1601 s.Jobs.ElasticsearchIndexer = jobsElasticsearchIndexerInterface(s) 1602 } 1603 if jobsBleveIndexerInterface != nil { 1604 s.Jobs.BleveIndexer = jobsBleveIndexerInterface(s) 1605 } 1606 if jobsMigrationsInterface != nil { 1607 s.Jobs.Migrations = jobsMigrationsInterface(s) 1608 } 1609 } 1610 1611 func (s *Server) TelemetryId() string { 1612 if s.telemetryService == nil { 1613 return "" 1614 } 1615 return s.telemetryService.TelemetryID 1616 } 1617 1618 func (s *Server) HttpService() httpservice.HTTPService { 1619 return s.HTTPService 1620 } 1621 1622 func (s *Server) SetLog(l *mlog.Logger) { 1623 s.Log = l 1624 } 1625 1626 func (a *App) GenerateSupportPacket() []model.FileData { 1627 // If any errors we come across within this function, we will log it in a warning.txt file so that we know why certain files did not get produced if any 1628 var warnings []string 1629 1630 // Creating an array of files that we are going to be adding to our zip file 1631 fileDatas := []model.FileData{} 1632 1633 // A array of the functions that we can iterate through since they all have the same return value 1634 functions := []func() (*model.FileData, string){ 1635 a.generateSupportPacketYaml, 1636 a.createPluginsFile, 1637 a.createSanitizedConfigFile, 1638 a.getMattermostLog, 1639 a.getNotificationsLog, 1640 } 1641 1642 for _, fn := range functions { 1643 fileData, warning := fn() 1644 1645 if fileData != nil { 1646 fileDatas = append(fileDatas, *fileData) 1647 } else { 1648 warnings = append(warnings, warning) 1649 } 1650 } 1651 1652 // Adding a warning.txt file to the fileDatas if any warning 1653 if len(warnings) > 0 { 1654 finalWarning := strings.Join(warnings, "\n") 1655 fileDatas = append(fileDatas, model.FileData{ 1656 Filename: "warning.txt", 1657 Body: []byte(finalWarning), 1658 }) 1659 } 1660 1661 return fileDatas 1662 } 1663 1664 func (a *App) getNotificationsLog() (*model.FileData, string) { 1665 var warning string 1666 1667 // Getting notifications.log 1668 if *a.Srv().Config().NotificationLogSettings.EnableFile { 1669 // notifications.log 1670 notificationsLog := utils.GetNotificationsLogFileLocation(*a.Srv().Config().LogSettings.FileLocation) 1671 1672 notificationsLogFileData, notificationsLogFileDataErr := ioutil.ReadFile(notificationsLog) 1673 1674 if notificationsLogFileDataErr == nil { 1675 fileData := model.FileData{ 1676 Filename: "notifications.log", 1677 Body: notificationsLogFileData, 1678 } 1679 return &fileData, "" 1680 } 1681 1682 warning = fmt.Sprintf("ioutil.ReadFile(notificationsLog) Error: %s", notificationsLogFileDataErr.Error()) 1683 1684 } else { 1685 warning = "Unable to retrieve notifications.log because LogSettings: EnableFile is false in config.json" 1686 } 1687 1688 return nil, warning 1689 } 1690 1691 func (a *App) getMattermostLog() (*model.FileData, string) { 1692 var warning string 1693 1694 // Getting mattermost.log 1695 if *a.Srv().Config().LogSettings.EnableFile { 1696 // mattermost.log 1697 mattermostLog := utils.GetLogFileLocation(*a.Srv().Config().LogSettings.FileLocation) 1698 1699 mattermostLogFileData, mattermostLogFileDataErr := ioutil.ReadFile(mattermostLog) 1700 1701 if mattermostLogFileDataErr == nil { 1702 fileData := model.FileData{ 1703 Filename: "mattermost.log", 1704 Body: mattermostLogFileData, 1705 } 1706 return &fileData, "" 1707 } 1708 warning = fmt.Sprintf("ioutil.ReadFile(mattermostLog) Error: %s", mattermostLogFileDataErr.Error()) 1709 1710 } else { 1711 warning = "Unable to retrieve mattermost.log because LogSettings: EnableFile is false in config.json" 1712 } 1713 1714 return nil, warning 1715 } 1716 1717 func (a *App) createSanitizedConfigFile() (*model.FileData, string) { 1718 // Getting sanitized config, prettifying it, and then adding it to our file data array 1719 sanitizedConfigPrettyJSON, err := json.MarshalIndent(a.GetSanitizedConfig(), "", " ") 1720 if err == nil { 1721 fileData := model.FileData{ 1722 Filename: "sanitized_config.json", 1723 Body: sanitizedConfigPrettyJSON, 1724 } 1725 return &fileData, "" 1726 } 1727 1728 warning := fmt.Sprintf("json.MarshalIndent(c.App.GetSanitizedConfig()) Error: %s", err.Error()) 1729 return nil, warning 1730 } 1731 1732 func (a *App) createPluginsFile() (*model.FileData, string) { 1733 var warning string 1734 1735 // Getting the plugins installed on the server, prettify it, and then add them to the file data array 1736 pluginsResponse, appErr := a.GetPlugins() 1737 if appErr == nil { 1738 pluginsPrettyJSON, err := json.MarshalIndent(pluginsResponse, "", " ") 1739 if err == nil { 1740 fileData := model.FileData{ 1741 Filename: "plugins.json", 1742 Body: pluginsPrettyJSON, 1743 } 1744 1745 return &fileData, "" 1746 } 1747 1748 warning = fmt.Sprintf("json.MarshalIndent(pluginsResponse) Error: %s", err.Error()) 1749 } else { 1750 warning = fmt.Sprintf("c.App.GetPlugins() Error: %s", appErr.Error()) 1751 } 1752 1753 return nil, warning 1754 } 1755 1756 func (a *App) generateSupportPacketYaml() (*model.FileData, string) { 1757 // Here we are getting information regarding Elastic Search 1758 var elasticServerVersion string 1759 var elasticServerPlugins []string 1760 if a.Srv().SearchEngine.ElasticsearchEngine != nil { 1761 elasticServerVersion = a.Srv().SearchEngine.ElasticsearchEngine.GetFullVersion() 1762 elasticServerPlugins = a.Srv().SearchEngine.ElasticsearchEngine.GetPlugins() 1763 } 1764 1765 // Here we are getting information regarding LDAP 1766 ldapInterface := a.Srv().Ldap 1767 var vendorName, vendorVersion string 1768 if ldapInterface != nil { 1769 vendorName, vendorVersion = ldapInterface.GetVendorNameAndVendorVersion() 1770 } 1771 1772 // Here we are getting information regarding the database (mysql/postgres + current Mattermost version) 1773 databaseType, databaseVersion := a.Srv().DatabaseTypeAndMattermostVersion() 1774 1775 // Creating the struct for support packet yaml file 1776 supportPacket := model.SupportPacket{ 1777 ServerOS: runtime.GOOS, 1778 ServerArchitecture: runtime.GOARCH, 1779 DatabaseType: databaseType, 1780 DatabaseVersion: databaseVersion, 1781 LdapVendorName: vendorName, 1782 LdapVendorVersion: vendorVersion, 1783 ElasticServerVersion: elasticServerVersion, 1784 ElasticServerPlugins: elasticServerPlugins, 1785 } 1786 1787 // Marshal to a Yaml File 1788 supportPacketYaml, err := yaml.Marshal(&supportPacket) 1789 if err == nil { 1790 fileData := model.FileData{ 1791 Filename: "support_packet.yaml", 1792 Body: supportPacketYaml, 1793 } 1794 return &fileData, "" 1795 } 1796 1797 warning := fmt.Sprintf("yaml.Marshal(&supportPacket) Error: %s", err.Error()) 1798 return nil, warning 1799 }