mirror of https://github.com/gogs/gogs.git
865 lines
25 KiB
Go
865 lines
25 KiB
Go
// 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)
|
|
|
|
if !filepath.IsAbs(Server.AppDataPath) {
|
|
Server.AppDataPath = filepath.Join(WorkDir(), Server.AppDataPath)
|
|
}
|
|
|
|
// ************************
|
|
// ----- SSH settings -----
|
|
// ************************
|
|
|
|
if err = File.Section("server").MapTo(&SSH); err != nil {
|
|
return errors.Wrap(err, "mapping SSH settings from [server] section")
|
|
}
|
|
|
|
if !SSH.Disabled {
|
|
SSH.RootPath = filepath.Join(HomeDir(), ".ssh")
|
|
SSH.KeyTestPath = os.TempDir()
|
|
|
|
if !SSH.StartBuiltinServer {
|
|
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()
|
|
}
|