setting.go 28 KB

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