setting.go 29 KB

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