diff options
Diffstat (limited to 'internal/conf/conf.go')
-rw-r--r-- | internal/conf/conf.go | 860 |
1 files changed, 860 insertions, 0 deletions
diff --git a/internal/conf/conf.go b/internal/conf/conf.go new file mode 100644 index 00000000..90e3060e --- /dev/null +++ b/internal/conf/conf.go @@ -0,0 +1,860 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package conf + +import ( + "net/mail" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + _ "github.com/go-macaron/cache/memcache" + _ "github.com/go-macaron/cache/redis" + "github.com/go-macaron/session" + _ "github.com/go-macaron/session/redis" + "github.com/mcuadros/go-version" + "github.com/pkg/errors" + "gopkg.in/ini.v1" + log "unknwon.dev/clog/v2" + + "github.com/gogs/go-libravatar" + + "gogs.io/gogs/internal/assets/conf" + "gogs.io/gogs/internal/osutil" + "gogs.io/gogs/internal/user" +) + +func init() { + // Initialize the primary logger until logging service is up. + err := log.NewConsole() + if err != nil { + panic("init console logger: " + err.Error()) + } +} + +// Asset is a wrapper for getting conf assets. +func Asset(name string) ([]byte, error) { + return conf.Asset(name) +} + +// AssetDir is a wrapper for getting conf assets. +func AssetDir(name string) ([]string, error) { + return conf.AssetDir(name) +} + +// MustAsset is a wrapper for getting conf assets. +func MustAsset(name string) []byte { + return conf.MustAsset(name) +} + +// File is the configuration object. +var File *ini.File + +// Init initializes configuration from conf assets and given custom configuration file. +// If `customConf` is empty, it falls back to default location, i.e. "<WORK DIR>/custom". +// It is safe to call this function multiple times with desired `customConf`, but it is +// not concurrent safe. +// +// ⚠️ WARNING: Do not print anything in this function other than wanrings. +func Init(customConf string) error { + var err error + File, err = ini.LoadSources(ini.LoadOptions{ + IgnoreInlineComment: true, + }, conf.MustAsset("conf/app.ini")) + if err != nil { + return errors.Wrap(err, "parse 'conf/app.ini'") + } + File.NameMapper = ini.SnackCase + + customConf, err = filepath.Abs(customConf) + if err != nil { + return errors.Wrap(err, "get absolute path") + } + if customConf == "" { + customConf = filepath.Join(CustomDir(), "conf/app.ini") + } + CustomConf = customConf + + if osutil.IsFile(customConf) { + if err = File.Append(customConf); err != nil { + return errors.Wrapf(err, "append %q", customConf) + } + } else { + log.Warn("Custom config %q not found. Ignore this warning if you're running for the first time", customConf) + } + + if err = File.Section(ini.DefaultSection).MapTo(&App); err != nil { + return errors.Wrap(err, "mapping default section") + } + + // *************************** + // ----- Server settings ----- + // *************************** + + if err = File.Section("server").MapTo(&Server); err != nil { + return errors.Wrap(err, "mapping [server] section") + } + + if !strings.HasSuffix(Server.ExternalURL, "/") { + Server.ExternalURL += "/" + } + Server.URL, err = url.Parse(Server.ExternalURL) + if err != nil { + return errors.Wrapf(err, "parse '[server] EXTERNAL_URL' %q", err) + } + + // Subpath should start with '/' and end without '/', i.e. '/{subpath}'. + Server.Subpath = strings.TrimRight(Server.URL.Path, "/") + Server.SubpathDepth = strings.Count(Server.Subpath, "/") + + unixSocketMode, err := strconv.ParseUint(Server.UnixSocketPermission, 8, 32) + if err != nil { + return errors.Wrapf(err, "parse '[server] UNIX_SOCKET_PERMISSION' %q", Server.UnixSocketPermission) + } + if unixSocketMode > 0777 { + unixSocketMode = 0666 + } + Server.UnixSocketMode = os.FileMode(unixSocketMode) + + // ************************ + // ----- SSH settings ----- + // ************************ + + if err = File.Section("server").MapTo(&SSH); err != nil { + return errors.Wrap(err, "mapping SSH settings from [server] section") + } + + if !SSH.Disabled { + if !SSH.StartBuiltinServer { + SSH.RootPath = filepath.Join(HomeDir(), ".ssh") + SSH.KeyTestPath = os.TempDir() + + if err := os.MkdirAll(SSH.RootPath, 0700); err != nil { + return errors.Wrap(err, "create SSH root directory") + } else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil { + return errors.Wrap(err, "create SSH key test directory") + } + } else { + SSH.RewriteAuthorizedKeysAtStart = false + } + + // Check if server is eligible for minimum key size check when user choose to enable. + // Windows server and OpenSSH version lower than 5.1 are forced to be disabled because + // the "ssh-keygen" in Windows does not print key type. + // See https://github.com/gogs/gogs/issues/4507. + if SSH.MinimumKeySizeCheck { + sshVersion, err := openSSHVersion() + if err != nil { + return errors.Wrap(err, "get OpenSSH version") + } + + if IsWindowsRuntime() || version.Compare(sshVersion, "5.1", "<") { + log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible: + 1. Windows server + 2. OpenSSH version is lower than 5.1`) + } else { + SSH.MinimumKeySizes = map[string]int{} + for _, key := range File.Section("ssh.minimum_key_sizes").Keys() { + if key.MustInt() != -1 { + SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt() + } + } + } + } + } + + transferDeprecated() + + // TODO + + sec := File.Section("security") + InstallLock = sec.Key("INSTALL_LOCK").MustBool() + SecretKey = sec.Key("SECRET_KEY").String() + LoginRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt() + CookieUserName = sec.Key("COOKIE_USERNAME").String() + CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String() + CookieSecure = sec.Key("COOKIE_SECURE").MustBool(false) + ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") + EnableLoginStatusCookie = sec.Key("ENABLE_LOGIN_STATUS_COOKIE").MustBool(false) + LoginStatusCookieName = sec.Key("LOGIN_STATUS_COOKIE_NAME").MustString("login_status") + + // Does not check run user when the install lock is off. + if InstallLock { + currentUser, match := IsRunUserMatchCurrentUser(App.RunUser) + if !match { + log.Fatal("The user configured to run Gogs is %q, but the current user is %q", App.RunUser, currentUser) + } + } + + sec = File.Section("attachment") + AttachmentPath = sec.Key("PATH").MustString(filepath.Join(Server.AppDataPath, "attachments")) + if !filepath.IsAbs(AttachmentPath) { + AttachmentPath = path.Join(workDir, AttachmentPath) + } + AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1) + AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4) + AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5) + AttachmentEnabled = sec.Key("ENABLED").MustBool(true) + + TimeFormat = map[string]string{ + "ANSIC": time.ANSIC, + "UnixDate": time.UnixDate, + "RubyDate": time.RubyDate, + "RFC822": time.RFC822, + "RFC822Z": time.RFC822Z, + "RFC850": time.RFC850, + "RFC1123": time.RFC1123, + "RFC1123Z": time.RFC1123Z, + "RFC3339": time.RFC3339, + "RFC3339Nano": time.RFC3339Nano, + "Kitchen": time.Kitchen, + "Stamp": time.Stamp, + "StampMilli": time.StampMilli, + "StampMicro": time.StampMicro, + "StampNano": time.StampNano, + }[File.Section("time").Key("FORMAT").MustString("RFC1123")] + + // Determine and create root git repository path. + sec = File.Section("repository") + RepoRootPath = sec.Key("ROOT").MustString(filepath.Join(HomeDir(), "gogs-repositories")) + if !filepath.IsAbs(RepoRootPath) { + RepoRootPath = path.Join(workDir, RepoRootPath) + } else { + RepoRootPath = path.Clean(RepoRootPath) + } + ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash") + if err = File.Section("repository").MapTo(&Repository); err != nil { + log.Fatal("Failed to map Repository settings: %v", err) + } else if err = File.Section("repository.editor").MapTo(&Repository.Editor); err != nil { + log.Fatal("Failed to map Repository.Editor settings: %v", err) + } else if err = File.Section("repository.upload").MapTo(&Repository.Upload); err != nil { + log.Fatal("Failed to map Repository.Upload settings: %v", err) + } + + if !filepath.IsAbs(Repository.Upload.TempPath) { + Repository.Upload.TempPath = path.Join(workDir, Repository.Upload.TempPath) + } + + sec = File.Section("picture") + AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(filepath.Join(Server.AppDataPath, "avatars")) + if !filepath.IsAbs(AvatarUploadPath) { + AvatarUploadPath = path.Join(workDir, AvatarUploadPath) + } + RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(filepath.Join(Server.AppDataPath, "repo-avatars")) + if !filepath.IsAbs(RepositoryAvatarUploadPath) { + RepositoryAvatarUploadPath = path.Join(workDir, RepositoryAvatarUploadPath) + } + switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source { + case "duoshuo": + GravatarSource = "http://gravatar.duoshuo.com/avatar/" + case "gravatar": + GravatarSource = "https://secure.gravatar.com/avatar/" + case "libravatar": + GravatarSource = "https://seccdn.libravatar.org/avatar/" + default: + GravatarSource = source + } + DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool() + EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(true) + if Server.OfflineMode { + DisableGravatar = true + EnableFederatedAvatar = false + } + if DisableGravatar { + EnableFederatedAvatar = false + } + + if EnableFederatedAvatar { + LibravatarService = libravatar.New() + parts := strings.Split(GravatarSource, "/") + if len(parts) >= 3 { + if parts[0] == "https:" { + LibravatarService.SetUseHTTPS(true) + LibravatarService.SetSecureFallbackHost(parts[2]) + } else { + LibravatarService.SetUseHTTPS(false) + LibravatarService.SetFallbackHost(parts[2]) + } + } + } + + if err = File.Section("http").MapTo(&HTTP); err != nil { + log.Fatal("Failed to map HTTP settings: %v", err) + } else if err = File.Section("webhook").MapTo(&Webhook); err != nil { + log.Fatal("Failed to map Webhook settings: %v", err) + } else if err = File.Section("release.attachment").MapTo(&Release.Attachment); err != nil { + log.Fatal("Failed to map Release.Attachment settings: %v", err) + } else if err = File.Section("markdown").MapTo(&Markdown); err != nil { + log.Fatal("Failed to map Markdown settings: %v", err) + } else if err = File.Section("smartypants").MapTo(&Smartypants); err != nil { + log.Fatal("Failed to map Smartypants settings: %v", err) + } else if err = File.Section("admin").MapTo(&Admin); err != nil { + log.Fatal("Failed to map Admin settings: %v", err) + } else if err = File.Section("cron").MapTo(&Cron); err != nil { + log.Fatal("Failed to map Cron settings: %v", err) + } else if err = File.Section("git").MapTo(&Git); err != nil { + log.Fatal("Failed to map Git settings: %v", err) + } else if err = File.Section("mirror").MapTo(&Mirror); err != nil { + log.Fatal("Failed to map Mirror settings: %v", err) + } else if err = File.Section("api").MapTo(&API); err != nil { + log.Fatal("Failed to map API settings: %v", err) + } else if err = File.Section("ui").MapTo(&UI); err != nil { + log.Fatal("Failed to map UI settings: %v", err) + } else if err = File.Section("prometheus").MapTo(&Prometheus); err != nil { + log.Fatal("Failed to map Prometheus settings: %v", err) + } + + if Mirror.DefaultInterval <= 0 { + Mirror.DefaultInterval = 24 + } + + Langs = File.Section("i18n").Key("LANGS").Strings(",") + Names = File.Section("i18n").Key("NAMES").Strings(",") + dateLangs = File.Section("i18n.datelang").KeysHash() + + ShowFooterBranding = File.Section("other").Key("SHOW_FOOTER_BRANDING").MustBool() + ShowFooterTemplateLoadTime = File.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool() + + HasRobotsTxt = osutil.IsFile(path.Join(CustomDir(), "robots.txt")) + return nil +} + +// MustInit panics if configuration initialization failed. +func MustInit(customConf string) { + err := Init(customConf) + if err != nil { + panic(err) + } +} + +// TODO + +var ( + HTTP struct { + AccessControlAllowOrigin string + } + + // Security settings + InstallLock bool + SecretKey string + LoginRememberDays int + CookieUserName string + CookieRememberName string + CookieSecure bool + ReverseProxyAuthUser string + EnableLoginStatusCookie bool + LoginStatusCookieName string + + // Database settings + UseSQLite3 bool + UseMySQL bool + UsePostgreSQL bool + UseMSSQL bool + + // Repository settings + Repository struct { + AnsiCharset string + ForcePrivate bool + MaxCreationLimit int + MirrorQueueLength int + PullRequestQueueLength int + PreferredLicenses []string + DisableHTTPGit bool `ini:"DISABLE_HTTP_GIT"` + EnableLocalPathMigration bool + CommitsFetchConcurrency int + EnableRawFileRenderMode bool + + // Repository editor settings + Editor struct { + LineWrapExtensions []string + PreviewableFileModes []string + } `ini:"-"` + + // Repository upload settings + Upload struct { + Enabled bool + TempPath string + AllowedTypes []string `delim:"|"` + FileMaxSize int64 + MaxFiles int + } `ini:"-"` + } + RepoRootPath string + ScriptType string + + // Webhook settings + Webhook struct { + Types []string + QueueLength int + DeliverTimeout int + SkipTLSVerify bool `ini:"SKIP_TLS_VERIFY"` + PagingNum int + } + + // Release settigns + Release struct { + Attachment struct { + Enabled bool + TempPath string + AllowedTypes []string `delim:"|"` + MaxSize int64 + MaxFiles int + } `ini:"-"` + } + + // Markdown sttings + Markdown struct { + EnableHardLineBreak bool + CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"` + FileExtensions []string + } + + // Smartypants settings + Smartypants struct { + Enabled bool + Fractions bool + Dashes bool + LatexDashes bool + AngledQuotes bool + } + + // Admin settings + Admin struct { + DisableRegularOrgCreation bool + } + + // Picture settings + AvatarUploadPath string + RepositoryAvatarUploadPath string + GravatarSource string + DisableGravatar bool + EnableFederatedAvatar bool + LibravatarService *libravatar.Libravatar + + // Log settings + LogRootPath string + LogModes []string + LogConfigs []interface{} + + // Attachment settings + AttachmentPath string + AttachmentAllowedTypes string + AttachmentMaxSize int64 + AttachmentMaxFiles int + AttachmentEnabled bool + + // Time settings + TimeFormat string + + // Cache settings + CacheAdapter string + CacheInterval int + CacheConn string + + // Session settings + SessionConfig session.Options + CSRFCookieName string + + // Cron tasks + Cron struct { + UpdateMirror struct { + Enabled bool + RunAtStart bool + Schedule string + } `ini:"cron.update_mirrors"` + RepoHealthCheck struct { + Enabled bool + RunAtStart bool + Schedule string + Timeout time.Duration + Args []string `delim:" "` + } `ini:"cron.repo_health_check"` + CheckRepoStats struct { + Enabled bool + RunAtStart bool + Schedule string + } `ini:"cron.check_repo_stats"` + RepoArchiveCleanup struct { + Enabled bool + RunAtStart bool + Schedule string + OlderThan time.Duration + } `ini:"cron.repo_archive_cleanup"` + } + + // Git settings + Git struct { + Version string `ini:"-"` + DisableDiffHighlight bool + MaxGitDiffLines int + MaxGitDiffLineCharacters int + MaxGitDiffFiles int + GCArgs []string `ini:"GC_ARGS" delim:" "` + Timeout struct { + Migrate int + Mirror int + Clone int + Pull int + GC int `ini:"GC"` + } `ini:"git.timeout"` + } + + // Mirror settings + Mirror struct { + DefaultInterval int + } + + // API settings + API struct { + MaxResponseItems int + } + + // UI settings + UI struct { + ExplorePagingNum int + IssuePagingNum int + FeedMaxCommitNum int + ThemeColorMetaTag string + MaxDisplayFileSize int64 + + Admin struct { + UserPagingNum int + RepoPagingNum int + NoticePagingNum int + OrgPagingNum int + } `ini:"ui.admin"` + User struct { + RepoPagingNum int + NewsFeedPagingNum int + CommitsPagingNum int + } `ini:"ui.user"` + } + + // Prometheus settings + Prometheus struct { + Enabled bool + EnableBasicAuth bool + BasicAuthUsername string + BasicAuthPassword string + } + + // I18n settings + Langs []string + Names []string + dateLangs map[string]string + + // Highlight settings are loaded in modules/template/hightlight.go + + // Other settings + ShowFooterBranding bool + ShowFooterTemplateLoadTime bool + + // Global setting objects + HasRobotsTxt bool +) + +// DateLang transforms standard language locale name to corresponding value in datetime plugin. +func DateLang(lang string) string { + name, ok := dateLangs[lang] + if ok { + return name + } + return "en" +} + +// IsRunUserMatchCurrentUser returns false if configured run user does not match +// actual user that runs the app. The first return value is the actual user name. +// This check is ignored under Windows since SSH remote login is not the main +// method to login on Windows. +func IsRunUserMatchCurrentUser(runUser string) (string, bool) { + if IsWindowsRuntime() { + return "", true + } + + currentUser := user.CurrentUsername() + return currentUser, runUser == currentUser +} + +// InitLogging initializes the logging service of the application. +func InitLogging() { + LogRootPath = File.Section("log").Key("ROOT_PATH").MustString(filepath.Join(WorkDir(), "log")) + + // Because we always create a console logger as the primary logger at init time, + // we need to remove it in case the user doesn't configure to use it after the + // logging service is initalized. + hasConsole := false + + // Iterate over [log.*] sections to initialize individual logger. + LogModes = strings.Split(File.Section("log").Key("MODE").MustString("console"), ",") + LogConfigs = make([]interface{}, len(LogModes)) + levelMappings := map[string]log.Level{ + "trace": log.LevelTrace, + "info": log.LevelInfo, + "warn": log.LevelWarn, + "error": log.LevelError, + "fatal": log.LevelFatal, + } + + type config struct { + Buffer int64 + Config interface{} + } + for i, mode := range LogModes { + mode = strings.ToLower(strings.TrimSpace(mode)) + secName := "log." + mode + sec, err := File.GetSection(secName) + if err != nil { + log.Fatal("Missing configuration section [%s] for %q logger", secName, mode) + return + } + + level := levelMappings[sec.Key("LEVEL").MustString("trace")] + buffer := sec.Key("BUFFER_LEN").MustInt64(100) + c := new(config) + switch mode { + case log.DefaultConsoleName: + hasConsole = true + c = &config{ + Buffer: buffer, + Config: log.ConsoleConfig{ + Level: level, + }, + } + err = log.NewConsole(c.Buffer, c.Config) + + case log.DefaultFileName: + logPath := filepath.Join(LogRootPath, "gogs.log") + logDir := filepath.Dir(logPath) + err = os.MkdirAll(logDir, os.ModePerm) + if err != nil { + log.Fatal("Failed to create log directory %q: %v", logDir, err) + return + } + + c = &config{ + Buffer: buffer, + Config: log.FileConfig{ + Level: level, + Filename: logPath, + FileRotationConfig: log.FileRotationConfig{ + Rotate: sec.Key("LOG_ROTATE").MustBool(true), + Daily: sec.Key("DAILY_ROTATE").MustBool(true), + MaxSize: 1 << uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28)), + MaxLines: sec.Key("MAX_LINES").MustInt64(1000000), + MaxDays: sec.Key("MAX_DAYS").MustInt64(7), + }, + }, + } + err = log.NewFile(c.Buffer, c.Config) + + case log.DefaultSlackName: + c = &config{ + Buffer: buffer, + Config: log.SlackConfig{ + Level: level, + URL: sec.Key("URL").String(), + }, + } + err = log.NewSlack(c.Buffer, c.Config) + + case log.DefaultDiscordName: + c = &config{ + Buffer: buffer, + Config: log.DiscordConfig{ + Level: level, + URL: sec.Key("URL").String(), + Username: sec.Key("USERNAME").String(), + }, + } + + default: + continue + } + + if err != nil { + log.Fatal("Failed to init %s logger: %v", mode, err) + return + } + LogConfigs[i] = c + + log.Trace("Log mode: %s (%s)", strings.Title(mode), strings.Title(strings.ToLower(level.String()))) + } + + if !hasConsole { + log.Remove(log.DefaultConsoleName) + } +} + +var Service struct { + ActiveCodeLives int + ResetPwdCodeLives int + RegisterEmailConfirm bool + DisableRegistration bool + ShowRegistrationButton bool + RequireSignInView bool + EnableNotifyMail bool + EnableReverseProxyAuth bool + EnableReverseProxyAutoRegister bool + EnableCaptcha bool +} + +func newService() { + sec := File.Section("service") + Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) + Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180) + Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool() + Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration) + Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool() + Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() + Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() + Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool() +} + +func newCacheService() { + CacheAdapter = File.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"}) + switch CacheAdapter { + case "memory": + CacheInterval = File.Section("cache").Key("INTERVAL").MustInt(60) + case "redis", "memcache": + CacheConn = strings.Trim(File.Section("cache").Key("HOST").String(), "\" ") + default: + log.Fatal("Unrecognized cache adapter %q", CacheAdapter) + return + } + + log.Trace("Cache service is enabled") +} + +func newSessionService() { + SessionConfig.Provider = File.Section("session").Key("PROVIDER").In("memory", + []string{"memory", "file", "redis", "mysql"}) + SessionConfig.ProviderConfig = strings.Trim(File.Section("session").Key("PROVIDER_CONFIG").String(), "\" ") + SessionConfig.CookieName = File.Section("session").Key("COOKIE_NAME").MustString("i_like_gogs") + SessionConfig.CookiePath = Server.Subpath + SessionConfig.Secure = File.Section("session").Key("COOKIE_SECURE").MustBool() + SessionConfig.Gclifetime = File.Section("session").Key("GC_INTERVAL_TIME").MustInt64(3600) + SessionConfig.Maxlifetime = File.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400) + CSRFCookieName = File.Section("session").Key("CSRF_COOKIE_NAME").MustString("_csrf") + + log.Trace("Session service is enabled") +} + +// Mailer represents mail service. +type Mailer struct { + QueueLength int + SubjectPrefix string + Host string + From string + FromEmail string + User, Passwd string + DisableHelo bool + HeloHostname string + SkipVerify bool + UseCertificate bool + CertFile, KeyFile string + UsePlainText bool + AddPlainTextAlt bool +} + +var ( + MailService *Mailer +) + +// newMailService initializes mail service options from configuration. +// No non-error log will be printed in hook mode. +func newMailService() { + sec := File.Section("mailer") + if !sec.Key("ENABLED").MustBool() { + return + } + + MailService = &Mailer{ + QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100), + SubjectPrefix: sec.Key("SUBJECT_PREFIX").MustString("[" + App.BrandName + "] "), + Host: sec.Key("HOST").String(), + User: sec.Key("USER").String(), + Passwd: sec.Key("PASSWD").String(), + DisableHelo: sec.Key("DISABLE_HELO").MustBool(), + HeloHostname: sec.Key("HELO_HOSTNAME").String(), + SkipVerify: sec.Key("SKIP_VERIFY").MustBool(), + UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(), + CertFile: sec.Key("CERT_FILE").String(), + KeyFile: sec.Key("KEY_FILE").String(), + UsePlainText: sec.Key("USE_PLAIN_TEXT").MustBool(), + AddPlainTextAlt: sec.Key("ADD_PLAIN_TEXT_ALT").MustBool(), + } + MailService.From = sec.Key("FROM").MustString(MailService.User) + + if len(MailService.From) > 0 { + parsed, err := mail.ParseAddress(MailService.From) + if err != nil { + log.Fatal("Failed to parse value %q for '[mailer] FROM': %v", MailService.From, err) + return + } + MailService.FromEmail = parsed.Address + } + + if HookMode { + return + } + log.Trace("Mail service is enabled") +} + +func newRegisterMailService() { + if !File.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() { + return + } else if MailService == nil { + log.Warn("Email confirmation is not enabled due to the mail service is not available") + return + } + Service.RegisterEmailConfirm = true + log.Trace("Email confirmation is enabled") +} + +// newNotifyMailService initializes notification email service options from configuration. +// No non-error log will be printed in hook mode. +func newNotifyMailService() { + if !File.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() { + return + } else if MailService == nil { + log.Warn("Email notification is not enabled due to the mail service is not available") + return + } + Service.EnableNotifyMail = true + + if HookMode { + return + } + log.Trace("Email notification is enabled") +} + +func NewService() { + newService() +} + +func NewServices() { + newService() + newCacheService() + newSessionService() + newMailService() + newRegisterMailService() + newNotifyMailService() +} + +// HookMode indicates whether program starts as Git server-side hook callback. +var HookMode bool + +// NewPostReceiveHookServices initializes all services that are needed by +// Git server-side post-receive hook callback. +func NewPostReceiveHookServices() { + HookMode = true + newService() + newMailService() + newNotifyMailService() +} |