setting.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975
  1. package setting
  2. import (
  3. "gitote/gitote/pkg/bindata"
  4. "gitote/gitote/pkg/process"
  5. "gitote/gitote/pkg/user"
  6. "net/mail"
  7. "net/url"
  8. "os"
  9. "os/exec"
  10. "path"
  11. "path/filepath"
  12. "runtime"
  13. "strconv"
  14. "strings"
  15. "time"
  16. "github.com/Unknwon/com"
  17. raven "github.com/getsentry/raven-go"
  18. _ "github.com/go-macaron/cache/memcache"
  19. _ "github.com/go-macaron/cache/redis"
  20. "github.com/go-macaron/session"
  21. _ "github.com/go-macaron/session/redis"
  22. "github.com/mcuadros/go-version"
  23. "gitlab.com/gitote/go-libravatar"
  24. log "gopkg.in/clog.v1"
  25. "gopkg.in/ini.v1"
  26. )
  27. type Scheme string
  28. const (
  29. SCHEME_HTTP Scheme = "http"
  30. SCHEME_HTTPS Scheme = "https"
  31. SCHEME_FCGI Scheme = "fcgi"
  32. SCHEME_UNIX_SOCKET Scheme = "unix"
  33. )
  34. type LandingPage string
  35. const (
  36. LANDING_PAGE_HOME LandingPage = "/"
  37. LANDING_PAGE_EXPLORE LandingPage = "/explore"
  38. )
  39. var (
  40. // Build information should only be set by -ldflags.
  41. BuildTime string
  42. BuildGitHash string
  43. // App settings
  44. AppVer string
  45. APIVer string
  46. AppURL string
  47. AppSubURL string
  48. AppSubURLDepth int // Number of slashes
  49. AppPath string
  50. AppDataPath string
  51. // Server settings
  52. Protocol Scheme
  53. Domain string
  54. HTTPAddr string
  55. HTTPPort string
  56. LocalURL string
  57. OfflineMode bool
  58. DisableRouterLog bool
  59. CertFile string
  60. KeyFile string
  61. TLSMinVersion string
  62. StaticRootPath string
  63. EnableGzip bool
  64. LandingPageURL LandingPage
  65. UnixSocketPermission uint32
  66. HTTP struct {
  67. AccessControlAllowOrigin string
  68. }
  69. SSH struct {
  70. Disabled bool `ini:"DISABLE_SSH"`
  71. StartBuiltinServer bool `ini:"START_SSH_SERVER"`
  72. Domain string `ini:"SSH_DOMAIN"`
  73. Port int `ini:"SSH_PORT"`
  74. ListenHost string `ini:"SSH_LISTEN_HOST"`
  75. ListenPort int `ini:"SSH_LISTEN_PORT"`
  76. RootPath string `ini:"SSH_ROOT_PATH"`
  77. RewriteAuthorizedKeysAtStart bool `ini:"REWRITE_AUTHORIZED_KEYS_AT_START"`
  78. ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"`
  79. KeyTestPath string `ini:"SSH_KEY_TEST_PATH"`
  80. KeygenPath string `ini:"SSH_KEYGEN_PATH"`
  81. MinimumKeySizeCheck bool `ini:"MINIMUM_KEY_SIZE_CHECK"`
  82. MinimumKeySizes map[string]int `ini:"-"`
  83. }
  84. // Security settings
  85. InstallLock bool
  86. SecretKey string
  87. LoginRememberDays int
  88. CookieUserName string
  89. CookieRememberName string
  90. CookieSecure bool
  91. ReverseProxyAuthUser string
  92. EnableLoginStatusCookie bool
  93. LoginStatusCookieName string
  94. // Database settings
  95. UseSQLite3 bool
  96. UseMySQL bool
  97. UsePostgreSQL bool
  98. UseMSSQL bool
  99. // Repository settings
  100. Repository struct {
  101. AnsiCharset string
  102. ForcePrivate bool
  103. MaxCreationLimit int
  104. MirrorQueueLength int
  105. PullRequestQueueLength int
  106. PreferredLicenses []string
  107. DisableHTTPGit bool `ini:"DISABLE_HTTP_GIT"`
  108. EnableLocalPathMigration bool
  109. CommitsFetchConcurrency int
  110. EnableRawFileRenderMode bool
  111. // Repository editor settings
  112. Editor struct {
  113. LineWrapExtensions []string
  114. PreviewableFileModes []string
  115. } `ini:"-"`
  116. // Repository upload settings
  117. Upload struct {
  118. Enabled bool
  119. TempPath string
  120. AllowedTypes []string `delim:"|"`
  121. FileMaxSize int64
  122. MaxFiles int
  123. } `ini:"-"`
  124. }
  125. RepoRootPath string
  126. ScriptType string
  127. // Webhook settings
  128. Webhook struct {
  129. Types []string
  130. QueueLength int
  131. DeliverTimeout int
  132. SkipTLSVerify bool `ini:"SKIP_TLS_VERIFY"`
  133. PagingNum int
  134. }
  135. // Release settings
  136. Release struct {
  137. Attachment struct {
  138. Enabled bool
  139. TempPath string
  140. AllowedTypes []string `delim:"|"`
  141. MaxSize int64
  142. MaxFiles int
  143. } `ini:"-"`
  144. }
  145. // Markdown sttings
  146. Markdown struct {
  147. EnableHardLineBreak bool
  148. CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"`
  149. FileExtensions []string
  150. }
  151. // Smartypants settings
  152. Smartypants struct {
  153. Enabled bool
  154. Fractions bool
  155. Dashes bool
  156. LatexDashes bool
  157. AngledQuotes bool
  158. }
  159. // Admin settings
  160. Admin struct {
  161. DisableRegularOrgCreation bool
  162. }
  163. // Picture settings
  164. AvatarUploadPath string
  165. RepositoryAvatarUploadPath string
  166. GravatarSource string
  167. DisableGravatar bool
  168. EnableFederatedAvatar bool
  169. LibravatarService *libravatar.Libravatar
  170. // Log settings
  171. LogRootPath string
  172. LogModes []string
  173. LogConfigs []interface{}
  174. // Attachment settings
  175. AttachmentPath string
  176. AttachmentAllowedTypes string
  177. AttachmentMaxSize int64
  178. AttachmentMaxFiles int
  179. AttachmentEnabled bool
  180. // Time settings
  181. TimeFormat string
  182. // Cache settings
  183. CacheAdapter string
  184. CacheInterval int
  185. CacheConn string
  186. // Session settings
  187. SessionConfig session.Options
  188. CSRFCookieName string
  189. // Cron tasks
  190. Cron struct {
  191. UpdateMirror struct {
  192. Enabled bool
  193. RunAtStart bool
  194. Schedule string
  195. } `ini:"cron.update_mirrors"`
  196. RepoHealthCheck struct {
  197. Enabled bool
  198. RunAtStart bool
  199. Schedule string
  200. Timeout time.Duration
  201. Args []string `delim:" "`
  202. } `ini:"cron.repo_health_check"`
  203. CheckRepoStats struct {
  204. Enabled bool
  205. RunAtStart bool
  206. Schedule string
  207. } `ini:"cron.check_repo_stats"`
  208. RepoArchiveCleanup struct {
  209. Enabled bool
  210. RunAtStart bool
  211. Schedule string
  212. OlderThan time.Duration
  213. } `ini:"cron.repo_archive_cleanup"`
  214. }
  215. // Git settings
  216. Git struct {
  217. Version string `ini:"-"`
  218. DisableDiffHighlight bool
  219. MaxGitDiffLines int
  220. MaxGitDiffLineCharacters int
  221. MaxGitDiffFiles int
  222. GCArgs []string `ini:"GC_ARGS" delim:" "`
  223. Timeout struct {
  224. Migrate int
  225. Mirror int
  226. Clone int
  227. Pull int
  228. GC int `ini:"GC"`
  229. } `ini:"git.timeout"`
  230. }
  231. // Mirror settings
  232. Mirror struct {
  233. DefaultInterval int
  234. }
  235. // API settings
  236. API struct {
  237. MaxResponseItems int
  238. }
  239. // UI settings
  240. UI struct {
  241. ExplorePagingNum int
  242. IssuePagingNum int
  243. FeedMaxCommitNum int
  244. MaxDisplayFileSize int64
  245. Admin struct {
  246. UserPagingNum int
  247. RepoPagingNum int
  248. NoticePagingNum int
  249. OrgPagingNum int
  250. } `ini:"ui.admin"`
  251. User struct {
  252. RepoPagingNum int
  253. NewsFeedPagingNum int
  254. CommitsPagingNum int
  255. } `ini:"ui.user"`
  256. }
  257. // Prometheus settings
  258. Prometheus struct {
  259. Enabled bool
  260. EnableBasicAuth bool
  261. BasicAuthUsername string
  262. BasicAuthPassword string
  263. }
  264. // I18n settings
  265. Langs []string
  266. Names []string
  267. dateLangs map[string]string
  268. // Highlight settings are loaded in modules/template/hightlight.go
  269. // Other settings
  270. SupportMiniWinService bool
  271. // Global setting objects
  272. Cfg *ini.File
  273. CustomPath string // Custom directory path
  274. CustomConf string
  275. ProdMode bool
  276. RunUser string
  277. IsWindows bool
  278. HasRobotsTxt bool
  279. )
  280. // DateLang transforms standard language locale name to corresponding value in datetime plugin.
  281. func DateLang(lang string) string {
  282. name, ok := dateLangs[lang]
  283. if ok {
  284. return name
  285. }
  286. return "en"
  287. }
  288. // execPath returns the executable path.
  289. func execPath() (string, error) {
  290. file, err := exec.LookPath(os.Args[0])
  291. if err != nil {
  292. return "", err
  293. }
  294. return filepath.Abs(file)
  295. }
  296. func init() {
  297. IsWindows = runtime.GOOS == "windows"
  298. log.New(log.CONSOLE, log.ConsoleConfig{})
  299. var err error
  300. if AppPath, err = execPath(); err != nil {
  301. raven.CaptureErrorAndWait(err, nil)
  302. log.Fatal(2, "Fail to get app path: %v\n", err)
  303. }
  304. // Note: we don't use path.Dir here because it does not handle case
  305. // which path starts with two "/" in Windows: "//psf/Home/..."
  306. AppPath = strings.Replace(AppPath, "\\", "/", -1)
  307. }
  308. // WorkDir returns absolute path of work directory.
  309. func WorkDir() (string, error) {
  310. wd := os.Getenv("GITOTE_WORK_DIR")
  311. if len(wd) > 0 {
  312. return wd, nil
  313. }
  314. i := strings.LastIndex(AppPath, "/")
  315. if i == -1 {
  316. return AppPath, nil
  317. }
  318. return AppPath[:i], nil
  319. }
  320. func forcePathSeparator(path string) {
  321. if strings.Contains(path, "\\") {
  322. log.Fatal(2, "Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places")
  323. }
  324. }
  325. // IsRunUserMatchCurrentUser returns false if configured run user does not match
  326. // actual user that runs the app. The first return value is the actual user name.
  327. // This check is ignored under Windows since SSH remote login is not the main
  328. // method to login on Windows.
  329. func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
  330. if IsWindows {
  331. return "", true
  332. }
  333. currentUser := user.CurrentUsername()
  334. return currentUser, runUser == currentUser
  335. }
  336. // getOpenSSHVersion parses and returns string representation of OpenSSH version
  337. // returned by command "ssh -V".
  338. func getOpenSSHVersion() string {
  339. // Note: somehow version is printed to stderr
  340. _, stderr, err := process.Exec("getOpenSSHVersion", "ssh", "-V")
  341. if err != nil {
  342. raven.CaptureErrorAndWait(err, nil)
  343. log.Fatal(2, "Fail to get OpenSSH version: %v - %s", err, stderr)
  344. }
  345. // Trim unused information
  346. version := strings.TrimRight(strings.Fields(stderr)[0], ",1234567890")
  347. version = strings.TrimSuffix(strings.TrimPrefix(version, "OpenSSH_"), "p")
  348. return version
  349. }
  350. // NewContext initializes configuration context.
  351. // NOTE: do not print any log except error.
  352. func NewContext() {
  353. workDir, err := WorkDir()
  354. if err != nil {
  355. raven.CaptureErrorAndWait(err, nil)
  356. log.Fatal(2, "Fail to get work directory: %v", err)
  357. }
  358. Cfg, err = ini.LoadSources(ini.LoadOptions{
  359. IgnoreInlineComment: true,
  360. }, bindata.MustAsset("conf/app.ini"))
  361. if err != nil {
  362. raven.CaptureErrorAndWait(err, nil)
  363. log.Fatal(2, "Fail to parse 'conf/app.ini': %v", err)
  364. }
  365. CustomPath = os.Getenv("GITOTE_CUSTOM")
  366. if len(CustomPath) == 0 {
  367. CustomPath = workDir + "/custom"
  368. }
  369. if len(CustomConf) == 0 {
  370. CustomConf = CustomPath + "/conf/app.ini"
  371. }
  372. if com.IsFile(CustomConf) {
  373. if err = Cfg.Append(CustomConf); err != nil {
  374. raven.CaptureErrorAndWait(err, nil)
  375. log.Fatal(2, "Fail to load custom conf '%s': %v", CustomConf, err)
  376. }
  377. } else {
  378. log.Warn("Custom config '%s' not found, ignore this if you're running first time", CustomConf)
  379. }
  380. Cfg.NameMapper = ini.AllCapsUnderscore
  381. homeDir, err := com.HomeDir()
  382. if err != nil {
  383. raven.CaptureErrorAndWait(err, nil)
  384. log.Fatal(2, "Fail to get home directory: %v", err)
  385. }
  386. homeDir = strings.Replace(homeDir, "\\", "/", -1)
  387. LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(workDir, "log"))
  388. forcePathSeparator(LogRootPath)
  389. sec := Cfg.Section("server")
  390. AppURL = sec.Key("ROOT_URL").MustString("http://localhost:3000/")
  391. if AppURL[len(AppURL)-1] != '/' {
  392. AppURL += "/"
  393. }
  394. // Check if has app suburl.
  395. url, err := url.Parse(AppURL)
  396. if err != nil {
  397. raven.CaptureErrorAndWait(err, nil)
  398. log.Fatal(2, "Invalid ROOT_URL '%s': %s", AppURL, err)
  399. }
  400. // Suburl should start with '/' and end without '/', such as '/{subpath}'.
  401. // This value is empty if site does not have sub-url.
  402. AppSubURL = strings.TrimSuffix(url.Path, "/")
  403. AppSubURLDepth = strings.Count(AppSubURL, "/")
  404. Protocol = SCHEME_HTTP
  405. if sec.Key("PROTOCOL").String() == "https" {
  406. Protocol = SCHEME_HTTPS
  407. CertFile = sec.Key("CERT_FILE").String()
  408. KeyFile = sec.Key("KEY_FILE").String()
  409. TLSMinVersion = sec.Key("TLS_MIN_VERSION").String()
  410. } else if sec.Key("PROTOCOL").String() == "fcgi" {
  411. Protocol = SCHEME_FCGI
  412. } else if sec.Key("PROTOCOL").String() == "unix" {
  413. Protocol = SCHEME_UNIX_SOCKET
  414. UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
  415. UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32)
  416. if err != nil || UnixSocketPermissionParsed > 0777 {
  417. raven.CaptureErrorAndWait(err, nil)
  418. log.Fatal(2, "Fail to parse unixSocketPermission: %s", UnixSocketPermissionRaw)
  419. }
  420. UnixSocketPermission = uint32(UnixSocketPermissionParsed)
  421. }
  422. Domain = sec.Key("DOMAIN").MustString("localhost")
  423. HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
  424. HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
  425. LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(string(Protocol) + "://localhost:" + HTTPPort + "/")
  426. OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
  427. DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
  428. StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(workDir)
  429. AppDataPath = sec.Key("APP_DATA_PATH").MustString("data")
  430. EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
  431. switch sec.Key("LANDING_PAGE").MustString("home") {
  432. case "explore":
  433. LandingPageURL = LANDING_PAGE_EXPLORE
  434. default:
  435. LandingPageURL = LANDING_PAGE_HOME
  436. }
  437. SSH.RootPath = path.Join(homeDir, ".ssh")
  438. SSH.RewriteAuthorizedKeysAtStart = sec.Key("REWRITE_AUTHORIZED_KEYS_AT_START").MustBool()
  439. SSH.ServerCiphers = sec.Key("SSH_SERVER_CIPHERS").Strings(",")
  440. SSH.KeyTestPath = os.TempDir()
  441. if err = Cfg.Section("server").MapTo(&SSH); err != nil {
  442. raven.CaptureErrorAndWait(err, nil)
  443. log.Fatal(2, "Fail to map SSH settings: %v", err)
  444. }
  445. if SSH.Disabled {
  446. SSH.StartBuiltinServer = false
  447. SSH.MinimumKeySizeCheck = false
  448. }
  449. if !SSH.Disabled && !SSH.StartBuiltinServer {
  450. if err := os.MkdirAll(SSH.RootPath, 0700); err != nil {
  451. raven.CaptureErrorAndWait(err, nil)
  452. log.Fatal(2, "Fail to create '%s': %v", SSH.RootPath, err)
  453. } else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
  454. raven.CaptureErrorAndWait(err, nil)
  455. log.Fatal(2, "Fail to create '%s': %v", SSH.KeyTestPath, err)
  456. }
  457. }
  458. if SSH.StartBuiltinServer {
  459. SSH.RewriteAuthorizedKeysAtStart = false
  460. }
  461. // Check if server is eligible for minimum key size check when user choose to enable.
  462. // Windows server and OpenSSH version lower than 5.1
  463. // are forced to be disabled because the "ssh-keygen" in Windows does not print key type.
  464. if SSH.MinimumKeySizeCheck &&
  465. (IsWindows || version.Compare(getOpenSSHVersion(), "5.1", "<")) {
  466. SSH.MinimumKeySizeCheck = false
  467. log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
  468. 1. Windows server
  469. 2. OpenSSH version is lower than 5.1`)
  470. }
  471. if SSH.MinimumKeySizeCheck {
  472. SSH.MinimumKeySizes = map[string]int{}
  473. for _, key := range Cfg.Section("ssh.minimum_key_sizes").Keys() {
  474. if key.MustInt() != -1 {
  475. SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt()
  476. }
  477. }
  478. }
  479. sec = Cfg.Section("security")
  480. InstallLock = sec.Key("INSTALL_LOCK").MustBool()
  481. SecretKey = sec.Key("SECRET_KEY").String()
  482. LoginRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt()
  483. CookieUserName = sec.Key("COOKIE_USERNAME").String()
  484. CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").String()
  485. CookieSecure = sec.Key("COOKIE_SECURE").MustBool(false)
  486. ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
  487. EnableLoginStatusCookie = sec.Key("ENABLE_LOGIN_STATUS_COOKIE").MustBool(false)
  488. LoginStatusCookieName = sec.Key("LOGIN_STATUS_COOKIE_NAME").MustString("login_status")
  489. sec = Cfg.Section("attachment")
  490. AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
  491. if !filepath.IsAbs(AttachmentPath) {
  492. AttachmentPath = path.Join(workDir, AttachmentPath)
  493. }
  494. AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png"), "|", ",", -1)
  495. AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
  496. AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
  497. AttachmentEnabled = sec.Key("ENABLED").MustBool(true)
  498. TimeFormat = map[string]string{
  499. "ANSIC": time.ANSIC,
  500. "UnixDate": time.UnixDate,
  501. "RubyDate": time.RubyDate,
  502. "RFC822": time.RFC822,
  503. "RFC822Z": time.RFC822Z,
  504. "RFC850": time.RFC850,
  505. "RFC1123": time.RFC1123,
  506. "RFC1123Z": time.RFC1123Z,
  507. "RFC3339": time.RFC3339,
  508. "RFC3339Nano": time.RFC3339Nano,
  509. "Kitchen": time.Kitchen,
  510. "Stamp": time.Stamp,
  511. "StampMilli": time.StampMilli,
  512. "StampMicro": time.StampMicro,
  513. "StampNano": time.StampNano,
  514. }[Cfg.Section("time").Key("FORMAT").MustString("RFC1123")]
  515. RunUser = Cfg.Section("").Key("RUN_USER").String()
  516. // Does not check run user when the install lock is off.
  517. if InstallLock {
  518. currentUser, match := IsRunUserMatchCurrentUser(RunUser)
  519. if !match {
  520. raven.CaptureErrorAndWait(err, nil)
  521. log.Fatal(2, "Expect user '%s' but current user is: %s", RunUser, currentUser)
  522. }
  523. }
  524. ProdMode = Cfg.Section("").Key("RUN_MODE").String() == "prod"
  525. // Determine and create root git repository path.
  526. sec = Cfg.Section("repository")
  527. RepoRootPath = sec.Key("ROOT").MustString(path.Join(homeDir, "gitote-repositories"))
  528. forcePathSeparator(RepoRootPath)
  529. if !filepath.IsAbs(RepoRootPath) {
  530. RepoRootPath = path.Join(workDir, RepoRootPath)
  531. } else {
  532. RepoRootPath = path.Clean(RepoRootPath)
  533. }
  534. ScriptType = sec.Key("SCRIPT_TYPE").MustString("bash")
  535. if err = Cfg.Section("repository").MapTo(&Repository); err != nil {
  536. raven.CaptureErrorAndWait(err, nil)
  537. log.Fatal(2, "Fail to map Repository settings: %v", err)
  538. } else if err = Cfg.Section("repository.editor").MapTo(&Repository.Editor); err != nil {
  539. raven.CaptureErrorAndWait(err, nil)
  540. log.Fatal(2, "Fail to map Repository.Editor settings: %v", err)
  541. } else if err = Cfg.Section("repository.upload").MapTo(&Repository.Upload); err != nil {
  542. raven.CaptureErrorAndWait(err, nil)
  543. log.Fatal(2, "Fail to map Repository.Upload settings: %v", err)
  544. }
  545. if !filepath.IsAbs(Repository.Upload.TempPath) {
  546. Repository.Upload.TempPath = path.Join(workDir, Repository.Upload.TempPath)
  547. }
  548. sec = Cfg.Section("picture")
  549. AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
  550. forcePathSeparator(AvatarUploadPath)
  551. if !filepath.IsAbs(AvatarUploadPath) {
  552. AvatarUploadPath = path.Join(workDir, AvatarUploadPath)
  553. }
  554. RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars"))
  555. forcePathSeparator(RepositoryAvatarUploadPath)
  556. if !filepath.IsAbs(RepositoryAvatarUploadPath) {
  557. RepositoryAvatarUploadPath = path.Join(workDir, RepositoryAvatarUploadPath)
  558. }
  559. switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
  560. case "duoshuo":
  561. GravatarSource = "http://gravatar.duoshuo.com/avatar/"
  562. case "gravatar":
  563. GravatarSource = "https://secure.gravatar.com/avatar/"
  564. case "libravatar":
  565. GravatarSource = "https://seccdn.libravatar.org/avatar/"
  566. default:
  567. GravatarSource = source
  568. }
  569. DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
  570. EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(true)
  571. if OfflineMode {
  572. DisableGravatar = true
  573. EnableFederatedAvatar = false
  574. }
  575. if DisableGravatar {
  576. EnableFederatedAvatar = false
  577. }
  578. if EnableFederatedAvatar {
  579. LibravatarService = libravatar.New()
  580. parts := strings.Split(GravatarSource, "/")
  581. if len(parts) >= 3 {
  582. if parts[0] == "https:" {
  583. LibravatarService.SetUseHTTPS(true)
  584. LibravatarService.SetSecureFallbackHost(parts[2])
  585. } else {
  586. LibravatarService.SetUseHTTPS(false)
  587. LibravatarService.SetFallbackHost(parts[2])
  588. }
  589. }
  590. }
  591. if err = Cfg.Section("http").MapTo(&HTTP); err != nil {
  592. raven.CaptureErrorAndWait(err, nil)
  593. log.Fatal(2, "Failed to map HTTP settings: %v", err)
  594. } else if err = Cfg.Section("webhook").MapTo(&Webhook); err != nil {
  595. raven.CaptureErrorAndWait(err, nil)
  596. log.Fatal(2, "Failed to map Webhook settings: %v", err)
  597. } else if err = Cfg.Section("release.attachment").MapTo(&Release.Attachment); err != nil {
  598. raven.CaptureErrorAndWait(err, nil)
  599. log.Fatal(2, "Failed to map Release.Attachment settings: %v", err)
  600. } else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
  601. raven.CaptureErrorAndWait(err, nil)
  602. log.Fatal(2, "Failed to map Markdown settings: %v", err)
  603. } else if err = Cfg.Section("smartypants").MapTo(&Smartypants); err != nil {
  604. raven.CaptureErrorAndWait(err, nil)
  605. log.Fatal(2, "Failed to map Smartypants settings: %v", err)
  606. } else if err = Cfg.Section("admin").MapTo(&Admin); err != nil {
  607. raven.CaptureErrorAndWait(err, nil)
  608. log.Fatal(2, "Failed to map Admin settings: %v", err)
  609. } else if err = Cfg.Section("cron").MapTo(&Cron); err != nil {
  610. raven.CaptureErrorAndWait(err, nil)
  611. log.Fatal(2, "Failed to map Cron settings: %v", err)
  612. } else if err = Cfg.Section("git").MapTo(&Git); err != nil {
  613. raven.CaptureErrorAndWait(err, nil)
  614. log.Fatal(2, "Failed to map Git settings: %v", err)
  615. } else if err = Cfg.Section("mirror").MapTo(&Mirror); err != nil {
  616. raven.CaptureErrorAndWait(err, nil)
  617. log.Fatal(2, "Failed to map Mirror settings: %v", err)
  618. } else if err = Cfg.Section("api").MapTo(&API); err != nil {
  619. raven.CaptureErrorAndWait(err, nil)
  620. log.Fatal(2, "Failed to map API settings: %v", err)
  621. } else if err = Cfg.Section("ui").MapTo(&UI); err != nil {
  622. raven.CaptureErrorAndWait(err, nil)
  623. log.Fatal(2, "Failed to map UI settings: %v", err)
  624. } else if err = Cfg.Section("prometheus").MapTo(&Prometheus); err != nil {
  625. raven.CaptureErrorAndWait(err, nil)
  626. log.Fatal(2, "Failed to map Prometheus settings: %v", err)
  627. }
  628. if Mirror.DefaultInterval <= 0 {
  629. Mirror.DefaultInterval = 24
  630. }
  631. Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
  632. Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
  633. dateLangs = Cfg.Section("i18n.datelang").KeysHash()
  634. HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))
  635. }
  636. var Service struct {
  637. ActiveCodeLives int
  638. ResetPwdCodeLives int
  639. RegisterEmailConfirm bool
  640. DisableRegistration bool
  641. ShowRegistrationButton bool
  642. RequireSignInView bool
  643. EnableNotifyMail bool
  644. EnableReverseProxyAuth bool
  645. EnableReverseProxyAutoRegister bool
  646. EnableCaptcha bool
  647. }
  648. func newService() {
  649. sec := Cfg.Section("service")
  650. Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180)
  651. Service.ResetPwdCodeLives = sec.Key("RESET_PASSWD_CODE_LIVE_MINUTES").MustInt(180)
  652. Service.DisableRegistration = sec.Key("DISABLE_REGISTRATION").MustBool()
  653. Service.ShowRegistrationButton = sec.Key("SHOW_REGISTRATION_BUTTON").MustBool(!Service.DisableRegistration)
  654. Service.RequireSignInView = sec.Key("REQUIRE_SIGNIN_VIEW").MustBool()
  655. Service.EnableReverseProxyAuth = sec.Key("ENABLE_REVERSE_PROXY_AUTHENTICATION").MustBool()
  656. Service.EnableReverseProxyAutoRegister = sec.Key("ENABLE_REVERSE_PROXY_AUTO_REGISTRATION").MustBool()
  657. Service.EnableCaptcha = sec.Key("ENABLE_CAPTCHA").MustBool()
  658. }
  659. func newLogService() {
  660. if len(BuildTime) > 0 {
  661. log.Trace("Build Time: %s", BuildTime)
  662. log.Trace("Build Git Hash: %s", BuildGitHash)
  663. }
  664. // Because we always create a console logger as primary logger before all settings are loaded,
  665. // thus if user doesn't set console logger, we should remove it after other loggers are created.
  666. hasConsole := false
  667. // Get and check log modes.
  668. LogModes = strings.Split(Cfg.Section("log").Key("MODE").MustString("console"), ",")
  669. LogConfigs = make([]interface{}, len(LogModes))
  670. levelNames := map[string]log.LEVEL{
  671. "trace": log.TRACE,
  672. "info": log.INFO,
  673. "warn": log.WARN,
  674. "error": log.ERROR,
  675. "fatal": log.FATAL,
  676. }
  677. for i, mode := range LogModes {
  678. mode = strings.ToLower(strings.TrimSpace(mode))
  679. sec, err := Cfg.GetSection("log." + mode)
  680. if err != nil {
  681. raven.CaptureErrorAndWait(err, nil)
  682. log.Fatal(2, "Unknown logger mode: %s", mode)
  683. }
  684. validLevels := []string{"trace", "info", "warn", "error", "fatal"}
  685. name := Cfg.Section("log." + mode).Key("LEVEL").Validate(func(v string) string {
  686. v = strings.ToLower(v)
  687. if com.IsSliceContainsStr(validLevels, v) {
  688. return v
  689. }
  690. return "trace"
  691. })
  692. level := levelNames[name]
  693. // Generate log configuration.
  694. switch log.MODE(mode) {
  695. case log.CONSOLE:
  696. hasConsole = true
  697. LogConfigs[i] = log.ConsoleConfig{
  698. Level: level,
  699. BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100),
  700. }
  701. case log.FILE:
  702. logPath := path.Join(LogRootPath, "gitote.log")
  703. if err = os.MkdirAll(path.Dir(logPath), os.ModePerm); err != nil {
  704. raven.CaptureErrorAndWait(err, nil)
  705. log.Fatal(2, "Fail to create log directory '%s': %v", path.Dir(logPath), err)
  706. }
  707. LogConfigs[i] = log.FileConfig{
  708. Level: level,
  709. BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100),
  710. Filename: logPath,
  711. FileRotationConfig: log.FileRotationConfig{
  712. Rotate: sec.Key("LOG_ROTATE").MustBool(true),
  713. Daily: sec.Key("DAILY_ROTATE").MustBool(true),
  714. MaxSize: 1 << uint(sec.Key("MAX_SIZE_SHIFT").MustInt(28)),
  715. MaxLines: sec.Key("MAX_LINES").MustInt64(1000000),
  716. MaxDays: sec.Key("MAX_DAYS").MustInt64(7),
  717. },
  718. }
  719. case log.SLACK:
  720. LogConfigs[i] = log.SlackConfig{
  721. Level: level,
  722. BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100),
  723. URL: sec.Key("URL").String(),
  724. }
  725. case log.DISCORD:
  726. LogConfigs[i] = log.DiscordConfig{
  727. Level: level,
  728. BufferSize: Cfg.Section("log").Key("BUFFER_LEN").MustInt64(100),
  729. URL: sec.Key("URL").String(),
  730. Username: sec.Key("USERNAME").String(),
  731. }
  732. }
  733. log.New(log.MODE(mode), LogConfigs[i])
  734. log.Trace("Log Mode: %s (%s)", strings.Title(mode), strings.Title(name))
  735. }
  736. // Make sure everyone gets version info printed.
  737. log.Info("%s %s", "Gitote", AppVer)
  738. if !hasConsole {
  739. log.Delete(log.CONSOLE)
  740. }
  741. }
  742. func newCacheService() {
  743. CacheAdapter = Cfg.Section("cache").Key("ADAPTER").In("memory", []string{"memory", "redis", "memcache"})
  744. switch CacheAdapter {
  745. case "memory":
  746. CacheInterval = Cfg.Section("cache").Key("INTERVAL").MustInt(60)
  747. case "redis", "memcache":
  748. CacheConn = strings.Trim(Cfg.Section("cache").Key("HOST").String(), "\" ")
  749. default:
  750. log.Fatal(2, "Unknown cache adapter: %s", CacheAdapter)
  751. }
  752. log.Info("Cache Service Enabled")
  753. }
  754. func newSessionService() {
  755. SessionConfig.Provider = Cfg.Section("session").Key("PROVIDER").In("memory",
  756. []string{"memory", "file", "redis", "mysql"})
  757. SessionConfig.ProviderConfig = strings.Trim(Cfg.Section("session").Key("PROVIDER_CONFIG").String(), "\" ")
  758. SessionConfig.CookieName = Cfg.Section("session").Key("COOKIE_NAME").MustString("gitote_sess")
  759. SessionConfig.CookiePath = AppSubURL
  760. SessionConfig.Secure = Cfg.Section("session").Key("COOKIE_SECURE").MustBool()
  761. SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(3600)
  762. SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
  763. CSRFCookieName = Cfg.Section("session").Key("CSRF_COOKIE_NAME").MustString("_csrf")
  764. log.Info("Session Service Enabled")
  765. }
  766. // Mailer represents mail service.
  767. type Mailer struct {
  768. QueueLength int
  769. SubjectPrefix string
  770. Host string
  771. From string
  772. FromEmail string
  773. User, Passwd string
  774. DisableHelo bool
  775. HeloHostname string
  776. SkipVerify bool
  777. UseCertificate bool
  778. CertFile, KeyFile string
  779. UsePlainText bool
  780. }
  781. var (
  782. MailService *Mailer
  783. )
  784. // newMailService initializes mail service options from configuration.
  785. // No non-error log will be printed in hook mode.
  786. func newMailService() {
  787. sec := Cfg.Section("mailer")
  788. if !sec.Key("ENABLED").MustBool() {
  789. return
  790. }
  791. MailService = &Mailer{
  792. QueueLength: sec.Key("SEND_BUFFER_LEN").MustInt(100),
  793. SubjectPrefix: sec.Key("SUBJECT_PREFIX").MustString("[" + "Gitote" + "] "),
  794. Host: sec.Key("HOST").String(),
  795. User: sec.Key("USER").String(),
  796. Passwd: sec.Key("PASSWD").String(),
  797. DisableHelo: sec.Key("DISABLE_HELO").MustBool(),
  798. HeloHostname: sec.Key("HELO_HOSTNAME").String(),
  799. SkipVerify: sec.Key("SKIP_VERIFY").MustBool(),
  800. UseCertificate: sec.Key("USE_CERTIFICATE").MustBool(),
  801. CertFile: sec.Key("CERT_FILE").String(),
  802. KeyFile: sec.Key("KEY_FILE").String(),
  803. UsePlainText: sec.Key("USE_PLAIN_TEXT").MustBool(),
  804. }
  805. MailService.From = sec.Key("FROM").MustString(MailService.User)
  806. if len(MailService.From) > 0 {
  807. parsed, err := mail.ParseAddress(MailService.From)
  808. if err != nil {
  809. raven.CaptureErrorAndWait(err, nil)
  810. log.Fatal(2, "Invalid mailer.FROM (%s): %v", MailService.From, err)
  811. }
  812. MailService.FromEmail = parsed.Address
  813. }
  814. if HookMode {
  815. return
  816. }
  817. log.Info("Mail Service Enabled")
  818. }
  819. func newRegisterMailService() {
  820. if !Cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
  821. return
  822. } else if MailService == nil {
  823. log.Warn("Register Mail Service: Mail Service is not enabled")
  824. return
  825. }
  826. Service.RegisterEmailConfirm = true
  827. log.Info("Register Mail Service Enabled")
  828. }
  829. // newNotifyMailService initializes notification email service options from configuration.
  830. // No non-error log will be printed in hook mode.
  831. func newNotifyMailService() {
  832. if !Cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
  833. return
  834. } else if MailService == nil {
  835. log.Warn("Notify Mail Service: Mail Service is not enabled")
  836. return
  837. }
  838. Service.EnableNotifyMail = true
  839. if HookMode {
  840. return
  841. }
  842. log.Info("Notify Mail Service Enabled")
  843. }
  844. func NewService() {
  845. newService()
  846. }
  847. func NewServices() {
  848. newService()
  849. newLogService()
  850. newCacheService()
  851. newSessionService()
  852. newMailService()
  853. newRegisterMailService()
  854. newNotifyMailService()
  855. }
  856. // HookMode indicates whether program starts as Git server-side hook callback.
  857. var HookMode bool
  858. // NewPostReceiveHookServices initializes all services that are needed by
  859. // Git server-side post-receive hook callback.
  860. func NewPostReceiveHookServices() {
  861. HookMode = true
  862. newService()
  863. newMailService()
  864. newNotifyMailService()
  865. }