mirror of
https://github.com/gogs/gogs.git
synced 2026-05-06 15:06:53 +02:00
conf: overhaul server settings (#5928)
* conf: rename package * Requires Go 1.12 * Fix lint * Fix lint * Overhaul * db: fix tests * Save my work * Fix tests * Server.UnixSocketPermission * Server.LocalRootURL * SSH settings * Server.OfflineMode * Save my work * App.Version * Remove [server] STATIC_ROOT_PATH * Server.LandingURL
This commit is contained in:
116
internal/conf/computed.go
Normal file
116
internal/conf/computed.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright 2020 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 (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ℹ️ README: This file contains configuration values that require computation to be useful.
|
||||
|
||||
// IsWindowsRuntime returns true if the current runtime in Windows.
|
||||
func IsWindowsRuntime() bool {
|
||||
return runtime.GOOS == "windows"
|
||||
}
|
||||
|
||||
// IsProdMode returns true if the application is running in production mode.
|
||||
func IsProdMode() bool {
|
||||
return strings.EqualFold(App.RunMode, "prod")
|
||||
}
|
||||
|
||||
var (
|
||||
appPath string
|
||||
appPathOnce sync.Once
|
||||
)
|
||||
|
||||
// AppPath returns the absolute path of the application's binary.
|
||||
func AppPath() string {
|
||||
appPathOnce.Do(func() {
|
||||
var err error
|
||||
appPath, err = exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
panic("look executable path: " + err.Error())
|
||||
}
|
||||
|
||||
appPath, err = filepath.Abs(appPath)
|
||||
if err != nil {
|
||||
panic("get absolute executable path: " + err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
return appPath
|
||||
}
|
||||
|
||||
var (
|
||||
workDir string
|
||||
workDirOnce sync.Once
|
||||
)
|
||||
|
||||
// WorkDir returns the absolute path of work directory. It reads the value of envrionment
|
||||
// variable GOGS_WORK_DIR. When not set, it uses the directory where the application's
|
||||
// binary is located.
|
||||
func WorkDir() string {
|
||||
workDirOnce.Do(func() {
|
||||
workDir = os.Getenv("GOGS_WORK_DIR")
|
||||
if workDir != "" {
|
||||
return
|
||||
}
|
||||
|
||||
workDir = filepath.Dir(AppPath())
|
||||
})
|
||||
|
||||
return workDir
|
||||
}
|
||||
|
||||
var (
|
||||
customDir string
|
||||
customDirOnce sync.Once
|
||||
)
|
||||
|
||||
// CustomDir returns the absolute path of the custom directory that contains local overrides.
|
||||
// It reads the value of envrionment variable GOGS_CUSTOM. When not set, it uses the work
|
||||
// directory returned by WorkDir fucntion.
|
||||
func CustomDir() string {
|
||||
customDirOnce.Do(func() {
|
||||
customDir = os.Getenv("GOGS_CUSTOM")
|
||||
if customDir != "" {
|
||||
return
|
||||
}
|
||||
|
||||
customDir = filepath.Join(WorkDir(), "custom")
|
||||
})
|
||||
|
||||
return customDir
|
||||
}
|
||||
|
||||
var (
|
||||
homeDir string
|
||||
homeDirOnce sync.Once
|
||||
)
|
||||
|
||||
// HomeDir returns the home directory by reading environment variables. It may return empty
|
||||
// string when environment variables are not set.
|
||||
func HomeDir() string {
|
||||
homeDirOnce.Do(func() {
|
||||
if !IsWindowsRuntime() {
|
||||
homeDir = os.Getenv("HOME")
|
||||
return
|
||||
}
|
||||
|
||||
homeDir = os.Getenv("USERPROFILE")
|
||||
if homeDir != "" {
|
||||
return
|
||||
}
|
||||
|
||||
homeDir = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
})
|
||||
|
||||
return homeDir
|
||||
}
|
||||
860
internal/conf/conf.go
Normal file
860
internal/conf/conf.go
Normal file
@@ -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()
|
||||
}
|
||||
107
internal/conf/static.go
Normal file
107
internal/conf/static.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2020 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/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ℹ️ README: This file contains static values that should only be set at initialization time.
|
||||
|
||||
// HasMinWinSvc is whether the application is built with Windows Service support.
|
||||
var HasMinWinSvc bool
|
||||
|
||||
// Build information should only be set by -ldflags.
|
||||
var (
|
||||
BuildTime string
|
||||
BuildCommit string
|
||||
)
|
||||
|
||||
// CustomConf returns the absolute path of custom configuration file that is used.
|
||||
var CustomConf string
|
||||
|
||||
var (
|
||||
// Application settings
|
||||
App struct {
|
||||
// ⚠️ WARNING: Should only be set by gogs.go.
|
||||
Version string `ini:"-"`
|
||||
|
||||
BrandName string
|
||||
RunUser string
|
||||
RunMode string
|
||||
|
||||
// Deprecated: Use BrandName instead, will be removed in 0.13.
|
||||
AppName string
|
||||
}
|
||||
|
||||
// Server settings
|
||||
Server struct {
|
||||
ExternalURL string `ini:"EXTERNAL_URL"`
|
||||
Domain string
|
||||
Protocol string
|
||||
HTTPAddr string `ini:"HTTP_ADDR"`
|
||||
HTTPPort string `ini:"HTTP_PORT"`
|
||||
CertFile string
|
||||
KeyFile string
|
||||
TLSMinVersion string `ini:"TLS_MIN_VERSION"`
|
||||
UnixSocketPermission string
|
||||
LocalRootURL string `ini:"LOCAL_ROOT_URL"`
|
||||
|
||||
OfflineMode bool
|
||||
DisableRouterLog bool
|
||||
EnableGzip bool
|
||||
|
||||
AppDataPath string
|
||||
LoadAssetsFromDisk bool
|
||||
|
||||
LandingURL string `ini:"LANDING_URL"`
|
||||
|
||||
// Derived from other static values
|
||||
URL *url.URL `ini:"-"` // Parsed URL object of ExternalURL.
|
||||
Subpath string `ini:"-"` // Subpath found the ExternalURL. Should be empty when not found.
|
||||
SubpathDepth int `ini:"-"` // The number of slashes found in the Subpath.
|
||||
UnixSocketMode os.FileMode `ini:"-"` // Parsed file mode of UnixSocketPermission.
|
||||
|
||||
// Deprecated: Use ExternalURL instead, will be removed in 0.13.
|
||||
RootURL string `ini:"ROOT_URL"`
|
||||
// Deprecated: Use LandingURL instead, will be removed in 0.13.
|
||||
LangdingPage string `ini:"LANDING_PAGE"`
|
||||
}
|
||||
|
||||
// SSH settings
|
||||
SSH struct {
|
||||
Disabled bool `ini:"DISABLE_SSH"`
|
||||
Domain string `ini:"SSH_DOMAIN"`
|
||||
Port int `ini:"SSH_PORT"`
|
||||
RootPath string `ini:"SSH_ROOT_PATH"`
|
||||
KeygenPath string `ini:"SSH_KEYGEN_PATH"`
|
||||
KeyTestPath string `ini:"SSH_KEY_TEST_PATH"`
|
||||
StartBuiltinServer bool `ini:"START_SSH_SERVER"`
|
||||
ListenHost string `ini:"SSH_LISTEN_HOST"`
|
||||
ListenPort int `ini:"SSH_LISTEN_PORT"`
|
||||
ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"`
|
||||
MinimumKeySizeCheck bool `ini:"MINIMUM_KEY_SIZE_CHECK"`
|
||||
MinimumKeySizes map[string]int `ini:"-"` // Load from [ssh.minimum_key_sizes]
|
||||
RewriteAuthorizedKeysAtStart bool `ini:"REWRITE_AUTHORIZED_KEYS_AT_START"`
|
||||
}
|
||||
)
|
||||
|
||||
// transferDeprecated transfers deprecated values to the new ones when set.
|
||||
func transferDeprecated() {
|
||||
if App.AppName != "" {
|
||||
App.BrandName = App.AppName
|
||||
App.AppName = ""
|
||||
}
|
||||
|
||||
if Server.RootURL != "" {
|
||||
Server.ExternalURL = Server.RootURL
|
||||
Server.RootURL = ""
|
||||
}
|
||||
if Server.LangdingPage == "explore" {
|
||||
Server.LandingURL = "/explore"
|
||||
Server.LangdingPage = ""
|
||||
}
|
||||
}
|
||||
15
internal/conf/static_minwinsvc.go
Normal file
15
internal/conf/static_minwinsvc.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build minwinsvc
|
||||
|
||||
// Copyright 2015 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 (
|
||||
_ "github.com/gogs/minwinsvc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
HasMinWinSvc = true
|
||||
}
|
||||
27
internal/conf/utils.go
Normal file
27
internal/conf/utils.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2020 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"gogs.io/gogs/internal/process"
|
||||
)
|
||||
|
||||
// openSSHVersion returns string representation of OpenSSH version via command "ssh -V".
|
||||
func openSSHVersion() (string, error) {
|
||||
// NOTE: Somehow the version is printed to stderr.
|
||||
_, stderr, err := process.Exec("conf.openSSHVersion", "ssh", "-V")
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, stderr)
|
||||
}
|
||||
|
||||
// Trim unused information, see https://github.com/gogs/gogs/issues/4507#issuecomment-305150441.
|
||||
v := strings.TrimRight(strings.Fields(stderr)[0], ",1234567890")
|
||||
v = strings.TrimSuffix(strings.TrimPrefix(v, "OpenSSH_"), "p")
|
||||
return v, nil
|
||||
}
|
||||
Reference in New Issue
Block a user