mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
550 lines
16 KiB
550 lines
16 KiB
// 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 ( |
|
"fmt" |
|
"net/url" |
|
"os" |
|
"os/exec" |
|
"path" |
|
"path/filepath" |
|
"runtime" |
|
"strings" |
|
"time" |
|
|
|
"github.com/Unknwon/com" |
|
"github.com/macaron-contrib/oauth2" |
|
"github.com/macaron-contrib/session" |
|
"gopkg.in/ini.v1" |
|
|
|
"github.com/gogits/gogs/modules/bindata" |
|
"github.com/gogits/gogs/modules/log" |
|
// "github.com/gogits/gogs/modules/ssh" |
|
) |
|
|
|
type Scheme string |
|
|
|
const ( |
|
HTTP Scheme = "http" |
|
HTTPS Scheme = "https" |
|
FCGI Scheme = "fcgi" |
|
) |
|
|
|
type LandingPage string |
|
|
|
const ( |
|
LANDING_PAGE_HOME LandingPage = "/" |
|
LANDING_PAGE_EXPLORE LandingPage = "/explore" |
|
) |
|
|
|
var ( |
|
// App settings. |
|
AppVer string |
|
AppName string |
|
AppUrl string |
|
AppSubUrl string |
|
|
|
// Server settings. |
|
Protocol Scheme |
|
Domain string |
|
HttpAddr, HttpPort string |
|
DisableSSH bool |
|
SSHPort int |
|
OfflineMode bool |
|
DisableRouterLog bool |
|
CertFile, KeyFile string |
|
StaticRootPath string |
|
EnableGzip bool |
|
LandingPageUrl LandingPage |
|
|
|
// Security settings. |
|
InstallLock bool |
|
SecretKey string |
|
LogInRememberDays int |
|
CookieUserName string |
|
CookieRememberName string |
|
ReverseProxyAuthUser string |
|
|
|
// Database settings. |
|
UseSQLite3 bool |
|
UseMySQL bool |
|
UsePostgreSQL bool |
|
|
|
// Webhook settings. |
|
Webhook struct { |
|
TaskInterval int |
|
DeliverTimeout int |
|
SkipTLSVerify bool |
|
} |
|
|
|
// Repository settings. |
|
RepoRootPath string |
|
ScriptType string |
|
|
|
// Picture settings. |
|
PictureService string |
|
AvatarUploadPath string |
|
GravatarSource string |
|
DisableGravatar bool |
|
|
|
// Log settings. |
|
LogRootPath string |
|
LogModes []string |
|
LogConfigs []string |
|
|
|
// Attachment settings. |
|
AttachmentPath string |
|
AttachmentAllowedTypes string |
|
AttachmentMaxSize int64 |
|
AttachmentMaxFiles int |
|
AttachmentEnabled bool |
|
|
|
// Time settings. |
|
TimeFormat string |
|
|
|
// Cache settings. |
|
CacheAdapter string |
|
CacheInternal int |
|
CacheConn string |
|
|
|
EnableRedis bool |
|
EnableMemcache bool |
|
|
|
// Session settings. |
|
SessionConfig session.Options |
|
|
|
// Git settings. |
|
Git struct { |
|
MaxGitDiffLines int |
|
GcArgs []string `delim:" "` |
|
Fsck struct { |
|
Enable bool |
|
Interval int |
|
Args []string `delim:" "` |
|
} `ini:"git.fsck"` |
|
} |
|
|
|
// I18n settings. |
|
Langs, Names []string |
|
|
|
// Global setting objects. |
|
Cfg *ini.File |
|
CustomPath string // Custom directory path. |
|
CustomConf string |
|
ProdMode bool |
|
RunUser string |
|
IsWindows bool |
|
HasRobotsTxt bool |
|
) |
|
|
|
func init() { |
|
IsWindows = runtime.GOOS == "windows" |
|
log.NewLogger(0, "console", `{"level": 0}`) |
|
} |
|
|
|
func ExecPath() (string, error) { |
|
file, err := exec.LookPath(os.Args[0]) |
|
if err != nil { |
|
return "", err |
|
} |
|
p, err := filepath.Abs(file) |
|
if err != nil { |
|
return "", err |
|
} |
|
return p, nil |
|
} |
|
|
|
// WorkDir returns absolute path of work directory. |
|
func WorkDir() (string, error) { |
|
execPath, err := ExecPath() |
|
return path.Dir(strings.Replace(execPath, "\\", "/", -1)), err |
|
} |
|
|
|
func forcePathSeparator(path string) { |
|
if strings.Contains(path, "\\") { |
|
log.Fatal(4, "Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places") |
|
} |
|
} |
|
|
|
// NewConfigContext initializes configuration context. |
|
// NOTE: do not print any log except error. |
|
func NewConfigContext() { |
|
workDir, err := WorkDir() |
|
if err != nil { |
|
log.Fatal(4, "Fail to get work directory: %v", err) |
|
} |
|
|
|
Cfg, err = ini.Load(bindata.MustAsset("conf/app.ini")) |
|
if err != nil { |
|
log.Fatal(4, "Fail to parse 'conf/app.ini': %v", err) |
|
} |
|
|
|
CustomPath = os.Getenv("GOGS_CUSTOM") |
|
if len(CustomPath) == 0 { |
|
CustomPath = path.Join(workDir, "custom") |
|
} |
|
|
|
if len(CustomConf) == 0 { |
|
CustomConf = path.Join(CustomPath, "conf/app.ini") |
|
} |
|
|
|
if com.IsFile(CustomConf) { |
|
if err = Cfg.Append(CustomConf); err != nil { |
|
log.Fatal(4, "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 |
|
|
|
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: Go Git Service") |
|
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(4, "Invalid ROOT_URL(%s): %s", AppUrl, err) |
|
} |
|
AppSubUrl = strings.TrimSuffix(url.Path, "/") |
|
|
|
Protocol = HTTP |
|
if sec.Key("PROTOCOL").String() == "https" { |
|
Protocol = HTTPS |
|
CertFile = sec.Key("CERT_FILE").String() |
|
KeyFile = sec.Key("KEY_FILE").String() |
|
} else if sec.Key("PROTOCOL").String() == "fcgi" { |
|
Protocol = FCGI |
|
} |
|
Domain = sec.Key("DOMAIN").MustString("localhost") |
|
HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") |
|
HttpPort = sec.Key("HTTP_PORT").MustString("3000") |
|
DisableSSH = sec.Key("DISABLE_SSH").MustBool() |
|
SSHPort = sec.Key("SSH_PORT").MustInt(22) |
|
OfflineMode = sec.Key("OFFLINE_MODE").MustBool() |
|
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() |
|
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir) |
|
EnableGzip = sec.Key("ENABLE_GZIP").MustBool() |
|
|
|
switch sec.Key("LANDING_PAGE").MustString("home") { |
|
case "explore": |
|
LandingPageUrl = LANDING_PAGE_EXPLORE |
|
default: |
|
LandingPageUrl = LANDING_PAGE_HOME |
|
} |
|
|
|
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() |
|
ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") |
|
|
|
sec = Cfg.Section("attachment") |
|
AttachmentPath = sec.Key("PATH").MustString("data/attachments") |
|
if !filepath.IsAbs(AttachmentPath) { |
|
AttachmentPath = path.Join(workDir, AttachmentPath) |
|
} |
|
AttachmentAllowedTypes = sec.Key("ALLOWED_TYPES").MustString("image/jpeg|image/png") |
|
AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(32) |
|
AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(10) |
|
AttachmentEnabled = sec.Key("ENABLE").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() |
|
curUser := os.Getenv("USER") |
|
if len(curUser) == 0 { |
|
curUser = os.Getenv("USERNAME") |
|
} |
|
// Does not check run user when the install lock is off. |
|
if InstallLock && RunUser != curUser { |
|
log.Fatal(4, "Expect user(%s) but current user is: %s", RunUser, curUser) |
|
} |
|
|
|
// Determine and create root git repository path. |
|
homeDir, err := com.HomeDir() |
|
if err != nil { |
|
log.Fatal(4, "Fail to get home directory: %v", err) |
|
} |
|
homeDir = strings.Replace(homeDir, "\\", "/", -1) |
|
|
|
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") |
|
|
|
sec = Cfg.Section("picture") |
|
PictureService = sec.Key("SERVICE").In("server", []string{"server"}) |
|
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString("data/avatars") |
|
forcePathSeparator(AvatarUploadPath) |
|
if !filepath.IsAbs(AvatarUploadPath) { |
|
AvatarUploadPath = path.Join(workDir, AvatarUploadPath) |
|
} |
|
switch sec.Key("GRAVATAR_SOURCE").MustString("gravatar") { |
|
case "duoshuo": |
|
GravatarSource = "http://gravatar.duoshuo.com/avatar/" |
|
default: |
|
GravatarSource = "//1.gravatar.com/avatar/" |
|
} |
|
DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool() |
|
|
|
if err = Cfg.Section("git").MapTo(&Git); err != nil { |
|
log.Fatal(4, "Fail to map Git settings: %v", err) |
|
} |
|
|
|
Langs = Cfg.Section("i18n").Key("LANGS").Strings(",") |
|
Names = Cfg.Section("i18n").Key("NAMES").Strings(",") |
|
|
|
HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt")) |
|
} |
|
|
|
var Service struct { |
|
RegisterEmailConfirm bool |
|
DisableRegistration bool |
|
ShowRegistrationButton bool |
|
RequireSignInView bool |
|
EnableCacheAvatar bool |
|
EnableNotifyMail bool |
|
EnableReverseProxyAuth bool |
|
EnableReverseProxyAutoRegister bool |
|
ActiveCodeLives int |
|
ResetPwdCodeLives int |
|
} |
|
|
|
func newService() { |
|
Service.ActiveCodeLives = Cfg.Section("service").Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) |
|
Service.ResetPwdCodeLives = Cfg.Section("service").Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180) |
|
Service.DisableRegistration = Cfg.Section("service").Key("DISABLE_REGISTRATION").MustBool() |
|
Service.ShowRegistrationButton = Cfg.Section("service").Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration) |
|
Service.RequireSignInView = Cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").MustBool() |
|
Service.EnableCacheAvatar = Cfg.Section("service").Key("ENABLE_CACHE_AVATAR").MustBool() |
|
Service.EnableReverseProxyAuth = Cfg.Section("service").Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool() |
|
Service.EnableReverseProxyAutoRegister = Cfg.Section("service").Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool() |
|
} |
|
|
|
var logLevels = map[string]string{ |
|
"Trace": "0", |
|
"Debug": "1", |
|
"Info": "2", |
|
"Warn": "3", |
|
"Error": "4", |
|
"Critical": "5", |
|
} |
|
|
|
func newLogService() { |
|
log.Info("%s %s", AppName, AppVer) |
|
|
|
// Get and check log mode. |
|
LogModes = strings.Split(Cfg.Section("log").Key("MODE").MustString("console"), ",") |
|
LogConfigs = make([]string, len(LogModes)) |
|
for i, mode := range LogModes { |
|
mode = strings.TrimSpace(mode) |
|
sec, err := Cfg.GetSection("log." + mode) |
|
if err != nil { |
|
log.Fatal(4, "Unknown log mode: %s", mode) |
|
} |
|
|
|
validLevels := []string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"} |
|
// Log level. |
|
levelName := Cfg.Section("log."+mode).Key("LEVEL").In( |
|
Cfg.Section("log").Key("LEVEL").In("Trace", validLevels), |
|
validLevels) |
|
level, ok := logLevels[levelName] |
|
if !ok { |
|
log.Fatal(4, "Unknown log level: %s", levelName) |
|
} |
|
|
|
// Generate log configuration. |
|
switch mode { |
|
case "console": |
|
LogConfigs[i] = fmt.Sprintf(`{"level":%s}`, level) |
|
case "file": |
|
logPath := sec.Key("FILE_NAME").MustString(path.Join(LogRootPath, "gogs.log")) |
|
os.MkdirAll(path.Dir(logPath), os.ModePerm) |
|
LogConfigs[i] = fmt.Sprintf( |
|
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level, |
|
logPath, |
|
sec.Key("LOG_ROTATE").MustBool(true), |
|
sec.Key("MAX_LINES").MustInt(1000000), |
|
1<<uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28)), |
|
sec.Key("DAILY_ROTATE").MustBool(true), |
|
sec.Key("MAX_DAYS").MustInt(7)) |
|
case "conn": |
|
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level, |
|
sec.Key("RECONNECT_ON_MSG").MustBool(), |
|
sec.Key("RECONNECT").MustBool(), |
|
sec.Key("PROTOCOL").In("tcp", []string{"tcp", "unix", "udp"}), |
|
sec.Key("ADDR").MustString(":7020")) |
|
case "smtp": |
|
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level, |
|
sec.Key("USER").MustString("example@example.com"), |
|
sec.Key("PASSWD").MustString("******"), |
|
sec.Key("HOST").MustString("127.0.0.1:25"), |
|
sec.Key("RECEIVERS").MustString("[]"), |
|
sec.Key("SUBJECT").MustString("Diagnostic message from serve")) |
|
case "database": |
|
LogConfigs[i] = fmt.Sprintf(`{"level":%s,"driver":"%s","conn":"%s"}`, level, |
|
sec.Key("DRIVER").String(), |
|
sec.Key("CONN").String()) |
|
} |
|
|
|
log.NewLogger(Cfg.Section("log").Key("BUFFER_LEN").MustInt64(10000), mode, LogConfigs[i]) |
|
log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName) |
|
} |
|
} |
|
|
|
func newCacheService() { |
|
CacheAdapter = Cfg.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"}) |
|
if EnableRedis { |
|
log.Info("Redis Enabled") |
|
} |
|
if EnableMemcache { |
|
log.Info("Memcache Enabled") |
|
} |
|
|
|
switch CacheAdapter { |
|
case "memory": |
|
CacheInternal = Cfg.Section("cache").Key("INTERVAL").MustInt(60) |
|
case "redis", "memcache": |
|
CacheConn = strings.Trim(Cfg.Section("cache").Key("HOST").String(), "\" ") |
|
default: |
|
log.Fatal(4, "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_gogits") |
|
SessionConfig.CookiePath = AppSubUrl |
|
SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool() |
|
SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400) |
|
SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400) |
|
|
|
log.Info("Session Service Enabled") |
|
} |
|
|
|
// Mailer represents mail service. |
|
type Mailer struct { |
|
Name string |
|
Host string |
|
From string |
|
User, Passwd string |
|
SkipVerify bool |
|
UseCertificate bool |
|
CertFile, KeyFile string |
|
} |
|
|
|
type OauthInfo struct { |
|
oauth2.Options |
|
AuthUrl, TokenUrl string |
|
} |
|
|
|
// Oauther represents oauth service. |
|
type Oauther struct { |
|
GitHub, Google, Tencent, |
|
Twitter, Weibo bool |
|
OauthInfos map[string]*OauthInfo |
|
} |
|
|
|
var ( |
|
MailService *Mailer |
|
OauthService *Oauther |
|
) |
|
|
|
func newMailService() { |
|
sec := Cfg.Section("mailer") |
|
// Check mailer setting. |
|
if !sec.Key("ENABLED").MustBool() { |
|
return |
|
} |
|
|
|
MailService = &Mailer{ |
|
Name: sec.Key("NAME").MustString(AppName), |
|
Host: sec.Key("HOST").String(), |
|
User: sec.Key("USER").String(), |
|
Passwd: sec.Key("PASSWD").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(), |
|
} |
|
MailService.From = sec.Key("FROM").MustString(MailService.User) |
|
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") |
|
} |
|
|
|
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 |
|
log.Info("Notify Mail Service Enabled") |
|
} |
|
|
|
func newWebhookService() { |
|
sec := Cfg.Section("webhook") |
|
Webhook.TaskInterval = sec.Key("TASK_INTERVAL").MustInt(1) |
|
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5) |
|
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool() |
|
} |
|
|
|
func NewServices() { |
|
newService() |
|
newLogService() |
|
newCacheService() |
|
newSessionService() |
|
newMailService() |
|
newRegisterMailService() |
|
newNotifyMailService() |
|
newWebhookService() |
|
// ssh.Listen("2222") |
|
}
|
|
|