diff options
Diffstat (limited to 'internal/setting/setting.go')
-rw-r--r-- | internal/setting/setting.go | 962 |
1 files changed, 962 insertions, 0 deletions
diff --git a/internal/setting/setting.go b/internal/setting/setting.go new file mode 100644 index 00000000..7166f885 --- /dev/null +++ b/internal/setting/setting.go @@ -0,0 +1,962 @@ +// 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 setting + +import ( + "net/mail" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/unknwon/com" + _ "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" + log "gopkg.in/clog.v1" + "gopkg.in/ini.v1" + + "github.com/gogs/go-libravatar" + + "gogs.io/gogs/internal/bindata" + "gogs.io/gogs/internal/process" + "gogs.io/gogs/internal/user" +) + +type Scheme string + +const ( + SCHEME_HTTP Scheme = "http" + SCHEME_HTTPS Scheme = "https" + SCHEME_FCGI Scheme = "fcgi" + SCHEME_UNIX_SOCKET Scheme = "unix" +) + +type LandingPage string + +const ( + LANDING_PAGE_HOME LandingPage = "/" + LANDING_PAGE_EXPLORE LandingPage = "/explore" +) + +var ( + // Build information should only be set by -ldflags. + BuildTime string + BuildGitHash string + + // App settings + AppVer string + AppName string + AppURL string + AppSubURL string + AppSubURLDepth int // Number of slashes + AppPath string + AppDataPath string + HostAddress string // AppURL without protocol and slashes + + // Server settings + Protocol Scheme + Domain string + HTTPAddr string + HTTPPort string + LocalURL string + OfflineMode bool + DisableRouterLog bool + CertFile string + KeyFile string + TLSMinVersion string + StaticRootPath string + EnableGzip bool + LandingPageURL LandingPage + UnixSocketPermission uint32 + + HTTP struct { + AccessControlAllowOrigin string + } + + SSH struct { + Disabled bool `ini:"DISABLE_SSH"` + StartBuiltinServer bool `ini:"START_SSH_SERVER"` + Domain string `ini:"SSH_DOMAIN"` + Port int `ini:"SSH_PORT"` + ListenHost string `ini:"SSH_LISTEN_HOST"` + ListenPort int `ini:"SSH_LISTEN_PORT"` + RootPath string `ini:"SSH_ROOT_PATH"` + RewriteAuthorizedKeysAtStart bool `ini:"REWRITE_AUTHORIZED_KEYS_AT_START"` + ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` + KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` + KeygenPath string `ini:"SSH_KEYGEN_PATH"` + MinimumKeySizeCheck bool `ini:"MINIMUM_KEY_SIZE_CHECK"` + MinimumKeySizes map[string]int `ini:"-"` + } + + // 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 + ShowFooterVersion bool + ShowFooterTemplateLoadTime bool + SupportMiniWinService bool + + // Global setting objects + Cfg *ini.File + CustomPath string // Custom directory path + CustomConf string + ProdMode bool + RunUser string + IsWindows bool + 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" +} + +// execPath returns the executable path. +func execPath() (string, error) { + file, err := exec.LookPath(os.Args[0]) + if err != nil { + return "", err + } + return filepath.Abs(file) +} + +func init() { + IsWindows = runtime.GOOS == "windows" + log.New(log.CONSOLE, log.ConsoleConfig{}) + + var err error + if AppPath, err = execPath(); err != nil { + log.Fatal(2, "Fail to get app path: %v\n", err) + } + + // Note: we don't use path.Dir here because it does not handle case + // which path starts with two "/" in Windows: "//psf/Home/..." + AppPath = strings.Replace(AppPath, "\\", "/", -1) +} + +// WorkDir returns absolute path of work directory. +func WorkDir() (string, error) { + wd := os.Getenv("GOGS_WORK_DIR") + if len(wd) > 0 { + return wd, nil + } + + i := strings.LastIndex(AppPath, "/") + if i == -1 { + return AppPath, nil + } + return AppPath[:i], nil +} + +func forcePathSeparator(path string) { + if strings.Contains(path, "\\") { + log.Fatal(2, "Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places") + } +} + +// 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 IsWindows { + return "", true + } + + currentUser := user.CurrentUsername() + return currentUser, runUser == currentUser +} + +// getOpenSSHVersion parses and returns string representation of OpenSSH version +// returned by command "ssh -V". +func getOpenSSHVersion() string { + // Note: somehow version is printed to stderr + _, stderr, err := process.Exec("getOpenSSHVersion", "ssh", "-V") + if err != nil { + log.Fatal(2, "Fail to get OpenSSH version: %v - %s", err, stderr) + } + + // Trim unused information: https://gogs.io/gogs/issues/4507#issuecomment-305150441 + version := strings.TrimRight(strings.Fields(stderr)[0], ",1234567890") + version = strings.TrimSuffix(strings.TrimPrefix(version, "OpenSSH_"), "p") + return version +} + +// NewContext initializes configuration context. +// NOTE: do not print any log except error. +func NewContext() { + workDir, err := WorkDir() + if err != nil { + log.Fatal(2, "Fail to get work directory: %v", err) + } + + Cfg, err = ini.LoadSources(ini.LoadOptions{ + IgnoreInlineComment: true, + }, bindata.MustAsset("conf/app.ini")) + if err != nil { + log.Fatal(2, "Fail to parse 'conf/app.ini': %v", err) + } + + CustomPath = os.Getenv("GOGS_CUSTOM") + if len(CustomPath) == 0 { + CustomPath = workDir + "/custom" + } + + if len(CustomConf) == 0 { + CustomConf = CustomPath + "/conf/app.ini" + } + + if com.IsFile(CustomConf) { + if err = Cfg.Append(CustomConf); err != nil { + log.Fatal(2, "Fail to load custom conf '%s': %v", CustomConf, err) + } + } else { + log.Warn("Custom config '%s' not found, ignore this if you're running first time", CustomConf) + } + Cfg.NameMapper = ini.AllCapsUnderscore + + homeDir, err := com.HomeDir() + if err != nil { + log.Fatal(2, "Fail to get home directory: %v", err) + } + homeDir = strings.Replace(homeDir, "\\", "/", -1) + + LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(workDir, "log")) + forcePathSeparator(LogRootPath) + + sec := Cfg.Section("server") + AppName = Cfg.Section("").Key("APP_NAME").MustString("Gogs") + AppURL = sec.Key("ROOT_URL").MustString("http://localhost:3000/") + if AppURL[len(AppURL)-1] != '/' { + AppURL += "/" + } + + // Check if has app suburl. + url, err := url.Parse(AppURL) + if err != nil { + log.Fatal(2, "Invalid ROOT_URL '%s': %s", AppURL, err) + } + // Suburl should start with '/' and end without '/', such as '/{subpath}'. + // This value is empty if site does not have sub-url. + AppSubURL = strings.TrimSuffix(url.Path, "/") + AppSubURLDepth = strings.Count(AppSubURL, "/") + HostAddress = url.Host + + Protocol = SCHEME_HTTP + if sec.Key("PROTOCOL").String() == "https" { + Protocol = SCHEME_HTTPS + CertFile = sec.Key("CERT_FILE").String() + KeyFile = sec.Key("KEY_FILE").String() + TLSMinVersion = sec.Key("TLS_MIN_VERSION").String() + } else if sec.Key("PROTOCOL").String() == "fcgi" { + Protocol = SCHEME_FCGI + } else if sec.Key("PROTOCOL").String() == "unix" { + Protocol = SCHEME_UNIX_SOCKET + UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666") + UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32) + if err != nil || UnixSocketPermissionParsed > 0777 { + log.Fatal(2, "Fail to parse unixSocketPermission: %s", UnixSocketPermissionRaw) + } + UnixSocketPermission = uint32(UnixSocketPermissionParsed) + } + Domain = sec.Key("DOMAIN").MustString("localhost") + HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") + HTTPPort = sec.Key("HTTP_PORT").MustString("3000") + LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(string(Protocol) + "://localhost:" + HTTPPort + "/") + OfflineMode = sec.Key("OFFLINE_MODE").MustBool() + DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() + StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir) + AppDataPath = sec.Key("APP_DATA_PATH").MustString("data") + EnableGzip = sec.Key("ENABLE_GZIP").MustBool() + + switch sec.Key("LANDING_PAGE").MustString("home") { + case "explore": + LandingPageURL = LANDING_PAGE_EXPLORE + default: + LandingPageURL = LANDING_PAGE_HOME + } + + SSH.RootPath = path.Join(homeDir, ".ssh") + SSH.RewriteAuthorizedKeysAtStart = sec.Key("REWRITE_AUTHORIZED_KEYS_AT_START").MustBool() + SSH.ServerCiphers = sec.Key("SSH_SERVER_CIPHERS").Strings(",") + SSH.KeyTestPath = os.TempDir() + if err = Cfg.Section("server").MapTo(&SSH); err != nil { + log.Fatal(2, "Fail to map SSH settings: %v", err) + } + if SSH.Disabled { + SSH.StartBuiltinServer = false + SSH.MinimumKeySizeCheck = false + } + + if !SSH.Disabled && !SSH.StartBuiltinServer { + if err := os.MkdirAll(SSH.RootPath, 0700); err != nil { + log.Fatal(2, "Fail to create '%s': %v", SSH.RootPath, err) + } else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil { + log.Fatal(2, "Fail to create '%s': %v", SSH.KeyTestPath, err) + } + } + + if SSH.StartBuiltinServer { + 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 (https://gogs.io/gogs/issues/4507) + // are forced to be disabled because the "ssh-keygen" in Windows does not print key type. + if SSH.MinimumKeySizeCheck && + (IsWindows || version.Compare(getOpenSSHVersion(), "5.1", "<")) { + SSH.MinimumKeySizeCheck = false + 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`) + } + + if SSH.MinimumKeySizeCheck { + SSH.MinimumKeySizes = map[string]int{} + for _, key := range Cfg.Section("ssh.minimum_key_sizes").Keys() { + if key.MustInt() != -1 { + SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt() + } + } + } + + sec = Cfg.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") + + sec = Cfg.Section("attachment") + AttachmentPath = sec.Key("PATH").MustString(path.Join(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, + }[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")] + + RunUser = Cfg.Section("").Key("RUN_USER").String() + // Does not check run user when the install lock is off. + if InstallLock { + currentUser, match := IsRunUserMatchCurrentUser(RunUser) + if !match { + log.Fatal(2, "Expect user '%s' but current user is: %s", RunUser, currentUser) + } + } + + ProdMode = Cfg.Section("").Key("RUN_MODE").String() == "prod" + + // Determine and create root git repository path. + sec = Cfg.Section("repository") + RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gogs-repositories")) + forcePathSeparator(RepoRootPath) + if !filepath.IsAbs(RepoRootPath) { + RepoRootPath = path.Join(workDir, RepoRootPath) + } else { + RepoRootPath = path.Clean(RepoRootPath) + } + ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash") + if err = Cfg.Section("repository").MapTo(&Repository); err != nil { + log.Fatal(2, "Fail to map Repository settings: %v", err) + } else if err = Cfg.Section("repository.editor").MapTo(&Repository.Editor); err != nil { + log.Fatal(2, "Fail to map Repository.Editor settings: %v", err) + } else if err = Cfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil { + log.Fatal(2, "Fail to map Repository.Upload settings: %v", err) + } + + if !filepath.IsAbs(Repository.Upload.TempPath) { + Repository.Upload.TempPath = path.Join(workDir, Repository.Upload.TempPath) + } + + sec = Cfg.Section("picture") + AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars")) + forcePathSeparator(AvatarUploadPath) + if !filepath.IsAbs(AvatarUploadPath) { + AvatarUploadPath = path.Join(workDir, AvatarUploadPath) + } + RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars")) + forcePathSeparator(RepositoryAvatarUploadPath) + 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 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 = Cfg.Section("http").MapTo(&HTTP); err != nil { + log.Fatal(2, "Failed to map HTTP settings: %v", err) + } else if err = Cfg.Section("webhook").MapTo(&Webhook); err != nil { + log.Fatal(2, "Failed to map Webhook settings: %v", err) + } else if err = Cfg.Section("release.attachment").MapTo(&Release.Attachment); err != nil { + log.Fatal(2, "Failed to map Release.Attachment settings: %v", err) + } else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil { + log.Fatal(2, "Failed to map Markdown settings: %v", err) + } else if err = Cfg.Section("smartypants").MapTo(&Smartypants); err != nil { + log.Fatal(2, "Failed to map Smartypants settings: %v", err) + } else if err = Cfg.Section("admin").MapTo(&Admin); err != nil { + log.Fatal(2, "Failed to map Admin settings: %v", err) + } else if err = Cfg.Section("cron").MapTo(&Cron); err != nil { + log.Fatal(2, "Failed to map Cron settings: %v", err) + } else if err = Cfg.Section("git").MapTo(&Git); err != nil { + log.Fatal(2, "Failed to map Git settings: %v", err) + } else if err = Cfg.Section("mirror").MapTo(&Mirror); err != nil { + log.Fatal(2, "Failed to map Mirror settings: %v", err) + } else if err = Cfg.Section("api").MapTo(&API); err != nil { + log.Fatal(2, "Failed to map API settings: %v", err) + } else if err = Cfg.Section("ui").MapTo(&UI); err != nil { + log.Fatal(2, "Failed to map UI settings: %v", err) + } else if err = Cfg.Section("prometheus").MapTo(&Prometheus); err != nil { + log.Fatal(2, "Failed to map Prometheus settings: %v", err) + } + + if Mirror.DefaultInterval <= 0 { + Mirror.DefaultInterval = 24 + } + + Langs = Cfg.Section("i18n").Key("LANGS").Strings(",") + Names = Cfg.Section("i18n").Key("NAMES").Strings(",") + dateLangs = Cfg.Section("i18n.datelang").KeysHash() + + ShowFooterBranding = Cfg.Section("other").Key("SHOW_FOOTER_BRANDING").MustBool() + ShowFooterVersion = Cfg.Section("other").Key("SHOW_FOOTER_VERSION").MustBool() + ShowFooterTemplateLoadTime = Cfg.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool() + + HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt")) +} + +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 := Cfg.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 newLogService() { + if len(BuildTime) > 0 { + log.Trace("Build Time: %s", BuildTime) + log.Trace("Build Git Hash: %s", BuildGitHash) + } + + // Because we always create a console logger as primary logger before all settings are loaded, + // thus if user doesn't set console logger, we should remove it after other loggers are created. + hasConsole := false + + // Get and check log modes. + LogModes = strings.Split(Cfg.Section("log").Key("MODE").MustString("console"), ",") + LogConfigs = make([]interface{}, len(LogModes)) + levelNames := map[string]log.LEVEL{ + "trace": log.TRACE, + "info": log.INFO, + "warn": log.WARN, + "error": log.ERROR, + "fatal": log.FATAL, + } + for i, mode := range LogModes { + mode = strings.ToLower(strings.TrimSpace(mode)) + sec, err := Cfg.GetSection("log." + mode) + if err != nil { + log.Fatal(2, "Unknown logger mode: %s", mode) + } + + validLevels := []string{"trace", "info", "warn", "error", "fatal"} + name := Cfg.Section("log." + mode).Key("LEVEL").Validate(func(v string) string { + v = strings.ToLower(v) + if com.IsSliceContainsStr(validLevels, v) { + return v + } + return "trace" + }) + level := levelNames[name] + + // Generate log configuration. + switch log.MODE(mode) { + case log.CONSOLE: + hasConsole = true + LogConfigs[i] = log.ConsoleConfig{ + Level: level, + BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100), + } + + case log.FILE: + logPath := path.Join(LogRootPath, "gogs.log") + if err = os.MkdirAll(path.Dir(logPath), os.ModePerm); err != nil { + log.Fatal(2, "Fail to create log directory '%s': %v", path.Dir(logPath), err) + } + + LogConfigs[i] = log.FileConfig{ + Level: level, + BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100), + 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), + }, + } + + case log.SLACK: + LogConfigs[i] = log.SlackConfig{ + Level: level, + BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100), + URL: sec.Key("URL").String(), + } + + case log.DISCORD: + LogConfigs[i] = log.DiscordConfig{ + Level: level, + BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100), + URL: sec.Key("URL").String(), + Username: sec.Key("USERNAME").String(), + } + } + + log.New(log.MODE(mode), LogConfigs[i]) + log.Trace("Log Mode: %s (%s)", strings.Title(mode), strings.Title(name)) + } + + // Make sure everyone gets version info printed. + log.Info("%s %s", AppName, AppVer) + if !hasConsole { + log.Delete(log.CONSOLE) + } +} + +func newCacheService() { + CacheAdapter = Cfg.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"}) + switch CacheAdapter { + case "memory": + CacheInterval = Cfg.Section("cache").Key("INTERVAL").MustInt(60) + case "redis", "memcache": + CacheConn = strings.Trim(Cfg.Section("cache").Key("HOST").String(), "\" ") + default: + log.Fatal(2, "Unknown cache adapter: %s", CacheAdapter) + } + + log.Info("Cache Service Enabled") +} + +func newSessionService() { + SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory", + []string{"memory", "file", "redis", "mysql"}) + SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").String(), "\" ") + SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("i_like_gogs") + SessionConfig.CookiePath = AppSubURL + SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool() + SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(3600) + SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400) + CSRFCookieName = Cfg.Section("session").Key("CSRF_COOKIE_NAME").MustString("_csrf") + + log.Info("Session Service 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 := Cfg.Section("mailer") + if !sec.Key("ENABLED").MustBool() { + return + } + + MailService = &Mailer{ + QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100), + SubjectPrefix: sec.Key("SUBJECT_PREFIX").MustString("[" + AppName + "] "), + 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(2, "Invalid mailer.FROM (%s): %v", MailService.From, err) + } + MailService.FromEmail = parsed.Address + } + + if HookMode { + return + } + log.Info("Mail Service Enabled") +} + +func newRegisterMailService() { + if !Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() { + return + } else if MailService == nil { + log.Warn("Register Mail Service: Mail Service is not enabled") + return + } + Service.RegisterEmailConfirm = true + log.Info("Register Mail Service Enabled") +} + +// newNotifyMailService initializes notification email service options from configuration. +// No non-error log will be printed in hook mode. +func newNotifyMailService() { + if !Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() { + return + } else if MailService == nil { + log.Warn("Notify Mail Service: Mail Service is not enabled") + return + } + Service.EnableNotifyMail = true + + if HookMode { + return + } + log.Info("Notify Mail Service Enabled") +} + +func NewService() { + newService() +} + +func NewServices() { + newService() + newLogService() + 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() +} |