mirror of
https://dev.sigpipe.me/dashie/git.txt
synced 2025-10-06 06:02:41 +02:00
491 lines
13 KiB
Go
491 lines
13 KiB
Go
package setting
|
|
|
|
import (
|
|
"github.com/Unknwon/com"
|
|
"github.com/go-macaron/session"
|
|
log "gopkg.in/clog.v1"
|
|
"gopkg.in/ini.v1"
|
|
"net/mail"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Scheme type
|
|
type Scheme string
|
|
|
|
// Schemes
|
|
const (
|
|
SchemeHTTP Scheme = "http"
|
|
SchemeHTTPS Scheme = "https"
|
|
SchemeFCGI Scheme = "fcgi"
|
|
SchemeUnixSocket Scheme = "unix"
|
|
)
|
|
|
|
// Settings
|
|
var (
|
|
// Build infos added by -ldflags
|
|
BuildTime string
|
|
BuildGitHash string
|
|
|
|
// App Settings
|
|
|
|
AppVer string
|
|
AppPath string
|
|
AppName string
|
|
AppURL string
|
|
AppSubURL string
|
|
AppSubURLDepth int // Number of slashes
|
|
CanRegister bool
|
|
AnonymousCreate bool
|
|
ProdMode bool
|
|
|
|
// Cron tasks
|
|
Cron struct {
|
|
RepoArchiveCleanup struct {
|
|
Enabled bool
|
|
RunAtStart bool
|
|
Schedule string
|
|
OlderThan time.Duration
|
|
} `ini:"cron.repo_archive_cleanup"`
|
|
}
|
|
|
|
// Server settings
|
|
|
|
Protocol Scheme
|
|
UnixSocketPermission uint32
|
|
Domain string
|
|
HTTPAddr string
|
|
HTTPPort string
|
|
DisableRouterLog bool
|
|
StaticRootPath string
|
|
|
|
HTTP struct {
|
|
AccessControlAllowOrigin string
|
|
}
|
|
|
|
// Database Settings
|
|
|
|
UseSQLite3 bool
|
|
UseMySQL bool
|
|
UsePostgreSQL bool
|
|
UseMSSQL bool
|
|
|
|
// Global setting objects
|
|
|
|
CustomConf string
|
|
IsWindows bool
|
|
Cfg *ini.File
|
|
HasRobotsTxt bool
|
|
RobotsTxtPath string
|
|
|
|
// Log settings
|
|
|
|
LogRootPath string
|
|
LogModes []string
|
|
LogConfigs []interface{}
|
|
|
|
// Repository settings
|
|
|
|
RepositoryRoot string
|
|
DisableHTTPGit bool
|
|
GitBinary string
|
|
|
|
// Session settings
|
|
|
|
SessionConfig session.Options
|
|
CSRFCookieName string
|
|
|
|
// Security settings
|
|
|
|
InstallLock bool
|
|
SecretKey string
|
|
LoginRememberDays int
|
|
CookieUserName string
|
|
CookieRememberName string
|
|
CookieSecure bool
|
|
EnableLoginStatusCookie bool
|
|
LoginStatusCookieName string
|
|
|
|
// Cache settings
|
|
|
|
CacheAdapter string
|
|
CacheInterval int
|
|
CacheConn string
|
|
|
|
// Langs settings
|
|
Langs []string
|
|
Names []string
|
|
dateLangs map[string]string
|
|
|
|
// 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
|
|
}
|
|
|
|
// Bloby struct for static limitations
|
|
Bloby struct {
|
|
MaxSizeDisplay int64
|
|
MaxPageDisplay int64
|
|
MaxRawSize int64
|
|
}
|
|
)
|
|
|
|
// 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("GITXT_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")
|
|
}
|
|
}
|
|
|
|
// InitConfig from file
|
|
func InitConfig() {
|
|
workDir, err := WorkDir()
|
|
if err != nil {
|
|
log.Fatal(2, "Fail to get work directory: %v", err)
|
|
}
|
|
|
|
if len(CustomConf) == 0 {
|
|
CustomConf = workDir + "/conf/app.ini"
|
|
}
|
|
|
|
Cfg, err = ini.Load(CustomConf)
|
|
if err != nil {
|
|
log.Fatal(2, "Fail to parse '%s': %v", CustomConf, err)
|
|
}
|
|
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("git.txt")
|
|
AppURL = sec.Key("ROOT_URL").MustString("http://localhost:4000/")
|
|
if AppURL[len(AppURL)-1] != '/' {
|
|
AppURL += "/"
|
|
}
|
|
|
|
// Check if has app suburl.
|
|
appURL, 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(appURL.Path, "/")
|
|
AppSubURLDepth = strings.Count(AppSubURL, "/")
|
|
|
|
CanRegister = Cfg.Section("").Key("CAN_REGISTER").MustBool(true)
|
|
AnonymousCreate = Cfg.Section("").Key("ANONYMOUS_CREATE").MustBool(true)
|
|
|
|
Protocol = SchemeHTTP
|
|
if sec.Key("PROTOCOL").String() == "https" {
|
|
Protocol = SchemeHTTPS
|
|
log.Warn("https not supported")
|
|
} else if sec.Key("PROTOCOL").String() == "fcgi" {
|
|
Protocol = SchemeFCGI
|
|
log.Warn("fcgi not supported")
|
|
} else if sec.Key("PROTOCOL").String() == "unix" {
|
|
Protocol = SchemeUnixSocket
|
|
log.Warn("socket not supported")
|
|
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")
|
|
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
|
|
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir)
|
|
|
|
sec = Cfg.Section("repository")
|
|
RepositoryRoot = sec.Key("ROOT").MustString(path.Join(homeDir, "gitxt-repositories"))
|
|
DisableHTTPGit = sec.Key("DISABLE_HTTP_GIT").MustBool(false)
|
|
GitBinary = sec.Key("GIT_BINARY").MustString("git")
|
|
|
|
sec = Cfg.Section("security")
|
|
SecretKey = sec.Key("SECRET_KEY").String()
|
|
|
|
HasRobotsTxt = com.IsFile(path.Join(workDir, "robots.txt"))
|
|
if HasRobotsTxt {
|
|
RobotsTxtPath = path.Join(workDir, "robots.txt")
|
|
}
|
|
|
|
Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
|
|
Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
|
|
dateLangs = Cfg.Section("i18n.datelang").KeysHash()
|
|
|
|
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)
|
|
EnableLoginStatusCookie = sec.Key("ENABLE_LOGIN_STATUS_COOKIE").MustBool(false)
|
|
LoginStatusCookieName = sec.Key("LOGIN_STATUS_COOKIE_NAME").MustString("login_status")
|
|
|
|
ProdMode = Cfg.Section("").Key("RUN_MODE").String() == "prod"
|
|
|
|
initLogging()
|
|
|
|
if err = Cfg.Section("cron").MapTo(&Cron); err != nil {
|
|
log.Fatal(2, "Fail to map Cron settings: %v", err)
|
|
} else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
|
|
log.Fatal(2, "Fail to map Markdown settings: %v", err)
|
|
} else if err = Cfg.Section("smartypants").MapTo(&Smartypants); err != nil {
|
|
log.Fatal(2, "Fail to map Smartypants settings: %v", err)
|
|
}
|
|
|
|
// Static
|
|
// Max display per file is 2M
|
|
Bloby.MaxSizeDisplay = 2000000
|
|
// Max total page display is max display per file * 2
|
|
Bloby.MaxPageDisplay = Bloby.MaxSizeDisplay * 2
|
|
// Max RAW size authorized (20M)
|
|
Bloby.MaxRawSize = 1000000 * 20
|
|
|
|
initSession()
|
|
initCache()
|
|
initMailer()
|
|
}
|
|
|
|
func initLogging() {
|
|
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, "git.txt.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(),
|
|
}
|
|
}
|
|
|
|
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 initSession() {
|
|
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_ponies")
|
|
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")
|
|
}
|
|
|
|
func initCache() {
|
|
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")
|
|
}
|
|
|
|
// Mailer represents mail service.
|
|
type Mailer struct {
|
|
QueueLength int
|
|
Subject string
|
|
Host string
|
|
From string
|
|
FromEmail string
|
|
User, Passwd string
|
|
DisableHelo bool
|
|
HeloHostname string
|
|
SkipVerify bool
|
|
UseCertificate bool
|
|
CertFile, KeyFile string
|
|
UsePlainText bool
|
|
}
|
|
|
|
var (
|
|
// MailService Mailer
|
|
MailService *Mailer
|
|
)
|
|
|
|
func initMailer() {
|
|
sec := Cfg.Section("mailer")
|
|
// Check mailer setting.
|
|
if !sec.Key("ENABLED").MustBool() {
|
|
log.Info("Mail Service Disabled")
|
|
return
|
|
}
|
|
|
|
MailService = &Mailer{
|
|
QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100),
|
|
Subject: sec.Key("SUBJECT").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(),
|
|
}
|
|
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
|
|
}
|
|
|
|
log.Info("Mail Service Enabled")
|
|
}
|