本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1066 lines
34KB

  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Copyright 2017 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package setting
  6. import (
  7. "encoding/base64"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net"
  12. "net/url"
  13. "os"
  14. "os/exec"
  15. "path"
  16. "path/filepath"
  17. "runtime"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "code.gitea.io/gitea/modules/generate"
  22. "code.gitea.io/gitea/modules/git"
  23. "code.gitea.io/gitea/modules/log"
  24. _ "code.gitea.io/gitea/modules/minwinsvc" // import minwinsvc for windows services
  25. "code.gitea.io/gitea/modules/user"
  26. shellquote "github.com/kballard/go-shellquote"
  27. version "github.com/mcuadros/go-version"
  28. "github.com/unknwon/cae/zip"
  29. "github.com/unknwon/com"
  30. ini "gopkg.in/ini.v1"
  31. "strk.kbt.io/projects/go/libravatar"
  32. )
  33. // Scheme describes protocol types
  34. type Scheme string
  35. // enumerates all the scheme types
  36. const (
  37. HTTP Scheme = "http"
  38. HTTPS Scheme = "https"
  39. FCGI Scheme = "fcgi"
  40. UnixSocket Scheme = "unix"
  41. )
  42. // LandingPage describes the default page
  43. type LandingPage string
  44. // enumerates all the landing page types
  45. const (
  46. LandingPageHome LandingPage = "/"
  47. LandingPageExplore LandingPage = "/explore"
  48. LandingPageOrganizations LandingPage = "/explore/organizations"
  49. )
  50. // enumerates all the types of captchas
  51. const (
  52. ImageCaptcha = "image"
  53. ReCaptcha = "recaptcha"
  54. )
  55. // settings
  56. var (
  57. // AppVer settings
  58. AppVer string
  59. AppBuiltWith string
  60. AppName string
  61. AppURL string
  62. AppSubURL string
  63. AppSubURLDepth int // Number of slashes
  64. AppPath string
  65. AppDataPath string
  66. AppWorkPath string
  67. // Server settings
  68. Protocol Scheme
  69. Domain string
  70. HTTPAddr string
  71. HTTPPort string
  72. LocalURL string
  73. RedirectOtherPort bool
  74. PortToRedirect string
  75. OfflineMode bool
  76. CertFile string
  77. KeyFile string
  78. StaticRootPath string
  79. StaticCacheTime time.Duration
  80. EnableGzip bool
  81. LandingPageURL LandingPage
  82. UnixSocketPermission uint32
  83. EnablePprof bool
  84. PprofDataPath string
  85. EnableLetsEncrypt bool
  86. LetsEncryptTOS bool
  87. LetsEncryptDirectory string
  88. LetsEncryptEmail string
  89. GracefulRestartable bool
  90. GracefulHammerTime time.Duration
  91. StaticURLPrefix string
  92. SSH = struct {
  93. Disabled bool `ini:"DISABLE_SSH"`
  94. StartBuiltinServer bool `ini:"START_SSH_SERVER"`
  95. BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"`
  96. Domain string `ini:"SSH_DOMAIN"`
  97. Port int `ini:"SSH_PORT"`
  98. ListenHost string `ini:"SSH_LISTEN_HOST"`
  99. ListenPort int `ini:"SSH_LISTEN_PORT"`
  100. RootPath string `ini:"SSH_ROOT_PATH"`
  101. ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"`
  102. ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"`
  103. ServerMACs []string `ini:"SSH_SERVER_MACS"`
  104. KeyTestPath string `ini:"SSH_KEY_TEST_PATH"`
  105. KeygenPath string `ini:"SSH_KEYGEN_PATH"`
  106. AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"`
  107. MinimumKeySizeCheck bool `ini:"-"`
  108. MinimumKeySizes map[string]int `ini:"-"`
  109. CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"`
  110. ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"`
  111. }{
  112. Disabled: false,
  113. StartBuiltinServer: false,
  114. Domain: "",
  115. Port: 22,
  116. ServerCiphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128"},
  117. ServerKeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "curve25519-sha256@libssh.org"},
  118. ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1", "hmac-sha1-96"},
  119. KeygenPath: "ssh-keygen",
  120. }
  121. LFS struct {
  122. StartServer bool `ini:"LFS_START_SERVER"`
  123. ContentPath string `ini:"LFS_CONTENT_PATH"`
  124. JWTSecretBase64 string `ini:"LFS_JWT_SECRET"`
  125. JWTSecretBytes []byte `ini:"-"`
  126. HTTPAuthExpiry time.Duration `ini:"LFS_HTTP_AUTH_EXPIRY"`
  127. }
  128. // Security settings
  129. InstallLock bool
  130. SecretKey string
  131. LogInRememberDays int
  132. CookieUserName string
  133. CookieRememberName string
  134. ReverseProxyAuthUser string
  135. ReverseProxyAuthEmail string
  136. MinPasswordLength int
  137. ImportLocalPaths bool
  138. DisableGitHooks bool
  139. PasswordComplexity []string
  140. PasswordHashAlgo string
  141. // UI settings
  142. UI = struct {
  143. ExplorePagingNum int
  144. IssuePagingNum int
  145. RepoSearchPagingNum int
  146. FeedMaxCommitNum int
  147. GraphMaxCommitNum int
  148. CodeCommentLines int
  149. ReactionMaxUserNum int
  150. ThemeColorMetaTag string
  151. MaxDisplayFileSize int64
  152. ShowUserEmail bool
  153. DefaultShowFullName bool
  154. DefaultTheme string
  155. Themes []string
  156. SearchRepoDescription bool
  157. Admin struct {
  158. UserPagingNum int
  159. RepoPagingNum int
  160. NoticePagingNum int
  161. OrgPagingNum int
  162. } `ini:"ui.admin"`
  163. User struct {
  164. RepoPagingNum int
  165. } `ini:"ui.user"`
  166. Meta struct {
  167. Author string
  168. Description string
  169. Keywords string
  170. } `ini:"ui.meta"`
  171. }{
  172. ExplorePagingNum: 20,
  173. IssuePagingNum: 10,
  174. RepoSearchPagingNum: 10,
  175. FeedMaxCommitNum: 5,
  176. GraphMaxCommitNum: 100,
  177. CodeCommentLines: 4,
  178. ReactionMaxUserNum: 10,
  179. ThemeColorMetaTag: `#6cc644`,
  180. MaxDisplayFileSize: 8388608,
  181. DefaultTheme: `gitea`,
  182. Themes: []string{`gitea`, `arc-green`},
  183. Admin: struct {
  184. UserPagingNum int
  185. RepoPagingNum int
  186. NoticePagingNum int
  187. OrgPagingNum int
  188. }{
  189. UserPagingNum: 50,
  190. RepoPagingNum: 50,
  191. NoticePagingNum: 25,
  192. OrgPagingNum: 50,
  193. },
  194. User: struct {
  195. RepoPagingNum int
  196. }{
  197. RepoPagingNum: 15,
  198. },
  199. Meta: struct {
  200. Author string
  201. Description string
  202. Keywords string
  203. }{
  204. Author: "Gitea - Git with a cup of tea",
  205. Description: "Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go",
  206. Keywords: "go,git,self-hosted,gitea",
  207. },
  208. }
  209. // Markdown settings
  210. Markdown = struct {
  211. EnableHardLineBreak bool
  212. CustomURLSchemes []string `ini:"CUSTOM_URL_SCHEMES"`
  213. FileExtensions []string
  214. }{
  215. EnableHardLineBreak: false,
  216. FileExtensions: strings.Split(".md,.markdown,.mdown,.mkd", ","),
  217. }
  218. // Admin settings
  219. Admin struct {
  220. DisableRegularOrgCreation bool
  221. DefaultEmailNotification string
  222. }
  223. // Picture settings
  224. AvatarUploadPath string
  225. AvatarMaxWidth int
  226. AvatarMaxHeight int
  227. GravatarSource string
  228. GravatarSourceURL *url.URL
  229. DisableGravatar bool
  230. EnableFederatedAvatar bool
  231. LibravatarService *libravatar.Libravatar
  232. AvatarMaxFileSize int64
  233. RepositoryAvatarUploadPath string
  234. RepositoryAvatarFallback string
  235. RepositoryAvatarFallbackImage string
  236. // Log settings
  237. LogLevel string
  238. StacktraceLogLevel string
  239. LogRootPath string
  240. LogDescriptions = make(map[string]*LogDescription)
  241. RedirectMacaronLog bool
  242. DisableRouterLog bool
  243. RouterLogLevel log.Level
  244. RouterLogMode string
  245. EnableAccessLog bool
  246. AccessLogTemplate string
  247. EnableXORMLog bool
  248. // Attachment settings
  249. AttachmentPath string
  250. AttachmentAllowedTypes string
  251. AttachmentMaxSize int64
  252. AttachmentMaxFiles int
  253. AttachmentEnabled bool
  254. // Time settings
  255. TimeFormat string
  256. // UILocation is the location on the UI, so that we can display the time on UI.
  257. DefaultUILocation = time.Local
  258. CSRFCookieName = "_csrf"
  259. CSRFCookieHTTPOnly = true
  260. // Mirror settings
  261. Mirror struct {
  262. DefaultInterval time.Duration
  263. MinInterval time.Duration
  264. }
  265. // API settings
  266. API = struct {
  267. EnableSwagger bool
  268. SwaggerURL string
  269. MaxResponseItems int
  270. DefaultPagingNum int
  271. DefaultGitTreesPerPage int
  272. DefaultMaxBlobSize int64
  273. }{
  274. EnableSwagger: true,
  275. SwaggerURL: "",
  276. MaxResponseItems: 50,
  277. DefaultPagingNum: 30,
  278. DefaultGitTreesPerPage: 1000,
  279. DefaultMaxBlobSize: 10485760,
  280. }
  281. OAuth2 = struct {
  282. Enable bool
  283. AccessTokenExpirationTime int64
  284. RefreshTokenExpirationTime int64
  285. InvalidateRefreshTokens bool
  286. JWTSecretBytes []byte `ini:"-"`
  287. JWTSecretBase64 string `ini:"JWT_SECRET"`
  288. }{
  289. Enable: true,
  290. AccessTokenExpirationTime: 3600,
  291. RefreshTokenExpirationTime: 730,
  292. InvalidateRefreshTokens: false,
  293. }
  294. U2F = struct {
  295. AppID string
  296. TrustedFacets []string
  297. }{}
  298. // Metrics settings
  299. Metrics = struct {
  300. Enabled bool
  301. Token string
  302. }{
  303. Enabled: false,
  304. Token: "",
  305. }
  306. // I18n settings
  307. Langs []string
  308. Names []string
  309. dateLangs map[string]string
  310. // Highlight settings are loaded in modules/template/highlight.go
  311. // Other settings
  312. ShowFooterBranding bool
  313. ShowFooterVersion bool
  314. ShowFooterTemplateLoadTime bool
  315. // Global setting objects
  316. Cfg *ini.File
  317. CustomPath string // Custom directory path
  318. CustomConf string
  319. CustomPID string
  320. ProdMode bool
  321. RunUser string
  322. IsWindows bool
  323. HasRobotsTxt bool
  324. InternalToken string // internal access token
  325. // UILocation is the location on the UI, so that we can display the time on UI.
  326. // Currently only show the default time.Local, it could be added to app.ini after UI is ready
  327. UILocation = time.Local
  328. )
  329. // DateLang transforms standard language locale name to corresponding value in datetime plugin.
  330. func DateLang(lang string) string {
  331. name, ok := dateLangs[lang]
  332. if ok {
  333. return name
  334. }
  335. return "en"
  336. }
  337. func getAppPath() (string, error) {
  338. var appPath string
  339. var err error
  340. if IsWindows && filepath.IsAbs(os.Args[0]) {
  341. appPath = filepath.Clean(os.Args[0])
  342. } else {
  343. appPath, err = exec.LookPath(os.Args[0])
  344. }
  345. if err != nil {
  346. return "", err
  347. }
  348. appPath, err = filepath.Abs(appPath)
  349. if err != nil {
  350. return "", err
  351. }
  352. // Note: we don't use path.Dir here because it does not handle case
  353. // which path starts with two "/" in Windows: "//psf/Home/..."
  354. return strings.Replace(appPath, "\\", "/", -1), err
  355. }
  356. func getWorkPath(appPath string) string {
  357. workPath := AppWorkPath
  358. if giteaWorkPath, ok := os.LookupEnv("GITEA_WORK_DIR"); ok {
  359. workPath = giteaWorkPath
  360. }
  361. if len(workPath) == 0 {
  362. i := strings.LastIndex(appPath, "/")
  363. if i == -1 {
  364. workPath = appPath
  365. } else {
  366. workPath = appPath[:i]
  367. }
  368. }
  369. return strings.Replace(workPath, "\\", "/", -1)
  370. }
  371. func init() {
  372. IsWindows = runtime.GOOS == "windows"
  373. // We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically
  374. log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "trace", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
  375. var err error
  376. if AppPath, err = getAppPath(); err != nil {
  377. log.Fatal("Failed to get app path: %v", err)
  378. }
  379. AppWorkPath = getWorkPath(AppPath)
  380. }
  381. func forcePathSeparator(path string) {
  382. if strings.Contains(path, "\\") {
  383. log.Fatal("Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places")
  384. }
  385. }
  386. // IsRunUserMatchCurrentUser returns false if configured run user does not match
  387. // actual user that runs the app. The first return value is the actual user name.
  388. // This check is ignored under Windows since SSH remote login is not the main
  389. // method to login on Windows.
  390. func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
  391. if IsWindows || SSH.StartBuiltinServer {
  392. return "", true
  393. }
  394. currentUser := user.CurrentUsername()
  395. return currentUser, runUser == currentUser
  396. }
  397. func createPIDFile(pidPath string) {
  398. currentPid := os.Getpid()
  399. if err := os.MkdirAll(filepath.Dir(pidPath), os.ModePerm); err != nil {
  400. log.Fatal("Failed to create PID folder: %v", err)
  401. }
  402. file, err := os.Create(pidPath)
  403. if err != nil {
  404. log.Fatal("Failed to create PID file: %v", err)
  405. }
  406. defer file.Close()
  407. if _, err := file.WriteString(strconv.FormatInt(int64(currentPid), 10)); err != nil {
  408. log.Fatal("Failed to write PID information: %v", err)
  409. }
  410. }
  411. // CheckLFSVersion will check lfs version, if not satisfied, then disable it.
  412. func CheckLFSVersion() {
  413. if LFS.StartServer {
  414. //Disable LFS client hooks if installed for the current OS user
  415. //Needs at least git v2.1.2
  416. binVersion, err := git.BinVersion()
  417. if err != nil {
  418. log.Fatal("Error retrieving git version: %v", err)
  419. }
  420. if !version.Compare(binVersion, "2.1.2", ">=") {
  421. LFS.StartServer = false
  422. log.Error("LFS server support needs at least Git v2.1.2")
  423. } else {
  424. git.GlobalCommandArgs = append(git.GlobalCommandArgs, "-c", "filter.lfs.required=",
  425. "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=")
  426. }
  427. }
  428. }
  429. // SetCustomPathAndConf will set CustomPath and CustomConf with reference to the
  430. // GITEA_CUSTOM environment variable and with provided overrides before stepping
  431. // back to the default
  432. func SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath string) {
  433. if len(providedWorkPath) != 0 {
  434. AppWorkPath = filepath.ToSlash(providedWorkPath)
  435. }
  436. if giteaCustom, ok := os.LookupEnv("GITEA_CUSTOM"); ok {
  437. CustomPath = giteaCustom
  438. }
  439. if len(providedCustom) != 0 {
  440. CustomPath = providedCustom
  441. }
  442. if len(CustomPath) == 0 {
  443. CustomPath = path.Join(AppWorkPath, "custom")
  444. } else if !filepath.IsAbs(CustomPath) {
  445. CustomPath = path.Join(AppWorkPath, CustomPath)
  446. }
  447. if len(providedConf) != 0 {
  448. CustomConf = providedConf
  449. }
  450. if len(CustomConf) == 0 {
  451. CustomConf = path.Join(CustomPath, "conf/app.ini")
  452. } else if !filepath.IsAbs(CustomConf) {
  453. CustomConf = path.Join(CustomPath, CustomConf)
  454. }
  455. }
  456. // NewContext initializes configuration context.
  457. // NOTE: do not print any log except error.
  458. func NewContext() {
  459. Cfg = ini.Empty()
  460. if len(CustomPID) > 0 {
  461. createPIDFile(CustomPID)
  462. }
  463. if com.IsFile(CustomConf) {
  464. if err := Cfg.Append(CustomConf); err != nil {
  465. log.Fatal("Failed to load custom conf '%s': %v", CustomConf, err)
  466. }
  467. } else {
  468. log.Warn("Custom config '%s' not found, ignore this if you're running first time", CustomConf)
  469. }
  470. Cfg.NameMapper = ini.SnackCase
  471. homeDir, err := com.HomeDir()
  472. if err != nil {
  473. log.Fatal("Failed to get home directory: %v", err)
  474. }
  475. homeDir = strings.Replace(homeDir, "\\", "/", -1)
  476. LogLevel = getLogLevel(Cfg.Section("log"), "LEVEL", "Info")
  477. StacktraceLogLevel = getStacktraceLogLevel(Cfg.Section("log"), "STACKTRACE_LEVEL", "None")
  478. LogRootPath = Cfg.Section("log").Key("ROOT_PATH").MustString(path.Join(AppWorkPath, "log"))
  479. forcePathSeparator(LogRootPath)
  480. RedirectMacaronLog = Cfg.Section("log").Key("REDIRECT_MACARON_LOG").MustBool(false)
  481. RouterLogLevel = log.FromString(Cfg.Section("log").Key("ROUTER_LOG_LEVEL").MustString("Info"))
  482. sec := Cfg.Section("server")
  483. AppName = Cfg.Section("").Key("APP_NAME").MustString("Gitea: Git with a cup of tea")
  484. Protocol = HTTP
  485. switch sec.Key("PROTOCOL").String() {
  486. case "https":
  487. Protocol = HTTPS
  488. CertFile = sec.Key("CERT_FILE").String()
  489. KeyFile = sec.Key("KEY_FILE").String()
  490. case "fcgi":
  491. Protocol = FCGI
  492. case "unix":
  493. Protocol = UnixSocket
  494. UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
  495. UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32)
  496. if err != nil || UnixSocketPermissionParsed > 0777 {
  497. log.Fatal("Failed to parse unixSocketPermission: %s", UnixSocketPermissionRaw)
  498. }
  499. UnixSocketPermission = uint32(UnixSocketPermissionParsed)
  500. }
  501. EnableLetsEncrypt = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
  502. LetsEncryptTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false)
  503. if !LetsEncryptTOS && EnableLetsEncrypt {
  504. log.Warn("Failed to enable Let's Encrypt due to Let's Encrypt TOS not being accepted")
  505. EnableLetsEncrypt = false
  506. }
  507. LetsEncryptDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
  508. LetsEncryptEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
  509. Domain = sec.Key("DOMAIN").MustString("localhost")
  510. HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
  511. HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
  512. GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true)
  513. GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second)
  514. defaultAppURL := string(Protocol) + "://" + Domain
  515. if (Protocol == HTTP && HTTPPort != "80") || (Protocol == HTTPS && HTTPPort != "443") {
  516. defaultAppURL += ":" + HTTPPort
  517. }
  518. AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL)
  519. AppURL = strings.TrimSuffix(AppURL, "/") + "/"
  520. // Check if has app suburl.
  521. appURL, err := url.Parse(AppURL)
  522. if err != nil {
  523. log.Fatal("Invalid ROOT_URL '%s': %s", AppURL, err)
  524. }
  525. // Suburl should start with '/' and end without '/', such as '/{subpath}'.
  526. // This value is empty if site does not have sub-url.
  527. AppSubURL = strings.TrimSuffix(appURL.Path, "/")
  528. StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
  529. AppSubURLDepth = strings.Count(AppSubURL, "/")
  530. // Check if Domain differs from AppURL domain than update it to AppURL's domain
  531. // TODO: Can be replaced with url.Hostname() when minimal GoLang version is 1.8
  532. urlHostname := strings.SplitN(appURL.Host, ":", 2)[0]
  533. if urlHostname != Domain && net.ParseIP(urlHostname) == nil {
  534. Domain = urlHostname
  535. }
  536. var defaultLocalURL string
  537. switch Protocol {
  538. case UnixSocket:
  539. defaultLocalURL = "http://unix/"
  540. case FCGI:
  541. defaultLocalURL = AppURL
  542. default:
  543. defaultLocalURL = string(Protocol) + "://"
  544. if HTTPAddr == "0.0.0.0" {
  545. defaultLocalURL += "localhost"
  546. } else {
  547. defaultLocalURL += HTTPAddr
  548. }
  549. defaultLocalURL += ":" + HTTPPort + "/"
  550. }
  551. LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
  552. RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false)
  553. PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80")
  554. OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
  555. DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
  556. StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(AppWorkPath)
  557. StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour)
  558. AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data"))
  559. EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
  560. EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
  561. PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(path.Join(AppWorkPath, "data/tmp/pprof"))
  562. if !filepath.IsAbs(PprofDataPath) {
  563. PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
  564. }
  565. switch sec.Key("LANDING_PAGE").MustString("home") {
  566. case "explore":
  567. LandingPageURL = LandingPageExplore
  568. case "organizations":
  569. LandingPageURL = LandingPageOrganizations
  570. default:
  571. LandingPageURL = LandingPageHome
  572. }
  573. if len(SSH.Domain) == 0 {
  574. SSH.Domain = Domain
  575. }
  576. SSH.RootPath = path.Join(homeDir, ".ssh")
  577. serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",")
  578. if len(serverCiphers) > 0 {
  579. SSH.ServerCiphers = serverCiphers
  580. }
  581. serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",")
  582. if len(serverKeyExchanges) > 0 {
  583. SSH.ServerKeyExchanges = serverKeyExchanges
  584. }
  585. serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",")
  586. if len(serverMACs) > 0 {
  587. SSH.ServerMACs = serverMACs
  588. }
  589. SSH.KeyTestPath = os.TempDir()
  590. if err = Cfg.Section("server").MapTo(&SSH); err != nil {
  591. log.Fatal("Failed to map SSH settings: %v", err)
  592. }
  593. SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen")
  594. SSH.Port = sec.Key("SSH_PORT").MustInt(22)
  595. SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port)
  596. // When disable SSH, start builtin server value is ignored.
  597. if SSH.Disabled {
  598. SSH.StartBuiltinServer = false
  599. }
  600. if !SSH.Disabled && !SSH.StartBuiltinServer {
  601. if err := os.MkdirAll(SSH.RootPath, 0700); err != nil {
  602. log.Fatal("Failed to create '%s': %v", SSH.RootPath, err)
  603. } else if err = os.MkdirAll(SSH.KeyTestPath, 0644); err != nil {
  604. log.Fatal("Failed to create '%s': %v", SSH.KeyTestPath, err)
  605. }
  606. }
  607. SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool()
  608. SSH.MinimumKeySizes = map[string]int{}
  609. minimumKeySizes := Cfg.Section("ssh.minimum_key_sizes").Keys()
  610. for _, key := range minimumKeySizes {
  611. if key.MustInt() != -1 {
  612. SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt()
  613. }
  614. }
  615. SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true)
  616. SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true)
  617. SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false)
  618. sec = Cfg.Section("server")
  619. if err = sec.MapTo(&LFS); err != nil {
  620. log.Fatal("Failed to map LFS settings: %v", err)
  621. }
  622. LFS.ContentPath = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs"))
  623. if !filepath.IsAbs(LFS.ContentPath) {
  624. LFS.ContentPath = filepath.Join(AppWorkPath, LFS.ContentPath)
  625. }
  626. LFS.HTTPAuthExpiry = sec.Key("LFS_HTTP_AUTH_EXPIRY").MustDuration(20 * time.Minute)
  627. if LFS.StartServer {
  628. if err := os.MkdirAll(LFS.ContentPath, 0700); err != nil {
  629. log.Fatal("Failed to create '%s': %v", LFS.ContentPath, err)
  630. }
  631. LFS.JWTSecretBytes = make([]byte, 32)
  632. n, err := base64.RawURLEncoding.Decode(LFS.JWTSecretBytes, []byte(LFS.JWTSecretBase64))
  633. if err != nil || n != 32 {
  634. LFS.JWTSecretBase64, err = generate.NewJwtSecret()
  635. if err != nil {
  636. log.Fatal("Error generating JWT Secret for custom config: %v", err)
  637. return
  638. }
  639. // Save secret
  640. cfg := ini.Empty()
  641. if com.IsFile(CustomConf) {
  642. // Keeps custom settings if there is already something.
  643. if err := cfg.Append(CustomConf); err != nil {
  644. log.Error("Failed to load custom conf '%s': %v", CustomConf, err)
  645. }
  646. }
  647. cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
  648. if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
  649. log.Fatal("Failed to create '%s': %v", CustomConf, err)
  650. }
  651. if err := cfg.SaveTo(CustomConf); err != nil {
  652. log.Fatal("Error saving generated JWT Secret to custom config: %v", err)
  653. return
  654. }
  655. }
  656. }
  657. if err = Cfg.Section("oauth2").MapTo(&OAuth2); err != nil {
  658. log.Fatal("Failed to OAuth2 settings: %v", err)
  659. return
  660. }
  661. if OAuth2.Enable {
  662. OAuth2.JWTSecretBytes = make([]byte, 32)
  663. n, err := base64.RawURLEncoding.Decode(OAuth2.JWTSecretBytes, []byte(OAuth2.JWTSecretBase64))
  664. if err != nil || n != 32 {
  665. OAuth2.JWTSecretBase64, err = generate.NewJwtSecret()
  666. if err != nil {
  667. log.Fatal("error generating JWT secret: %v", err)
  668. return
  669. }
  670. cfg := ini.Empty()
  671. if com.IsFile(CustomConf) {
  672. if err := cfg.Append(CustomConf); err != nil {
  673. log.Error("failed to load custom conf %s: %v", CustomConf, err)
  674. return
  675. }
  676. }
  677. cfg.Section("oauth2").Key("JWT_SECRET").SetValue(OAuth2.JWTSecretBase64)
  678. if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
  679. log.Fatal("failed to create '%s': %v", CustomConf, err)
  680. return
  681. }
  682. if err := cfg.SaveTo(CustomConf); err != nil {
  683. log.Fatal("error saving generating JWT secret to custom config: %v", err)
  684. return
  685. }
  686. }
  687. }
  688. sec = Cfg.Section("admin")
  689. Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled")
  690. sec = Cfg.Section("security")
  691. InstallLock = sec.Key("INSTALL_LOCK").MustBool(false)
  692. SecretKey = sec.Key("SECRET_KEY").MustString("!#@FDEWREWR&*(")
  693. LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
  694. CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
  695. CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible")
  696. ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER")
  697. ReverseProxyAuthEmail = sec.Key("REVERSE_PROXY_AUTHENTICATION_EMAIL").MustString("X-WEBAUTH-EMAIL")
  698. MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6)
  699. ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false)
  700. DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(false)
  701. PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2")
  702. CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
  703. InternalToken = loadInternalToken(sec)
  704. cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",")
  705. PasswordComplexity = make([]string, 0, len(cfgdata))
  706. for _, name := range cfgdata {
  707. name := strings.ToLower(strings.Trim(name, `"`))
  708. if name != "" {
  709. PasswordComplexity = append(PasswordComplexity, name)
  710. }
  711. }
  712. sec = Cfg.Section("attachment")
  713. AttachmentPath = sec.Key("PATH").MustString(path.Join(AppDataPath, "attachments"))
  714. if !filepath.IsAbs(AttachmentPath) {
  715. AttachmentPath = path.Join(AppWorkPath, AttachmentPath)
  716. }
  717. AttachmentAllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1)
  718. AttachmentMaxSize = sec.Key("MAX_SIZE").MustInt64(4)
  719. AttachmentMaxFiles = sec.Key("MAX_FILES").MustInt(5)
  720. AttachmentEnabled = sec.Key("ENABLED").MustBool(true)
  721. timeFormatKey := Cfg.Section("time").Key("FORMAT").MustString("")
  722. if timeFormatKey != "" {
  723. TimeFormat = map[string]string{
  724. "ANSIC": time.ANSIC,
  725. "UnixDate": time.UnixDate,
  726. "RubyDate": time.RubyDate,
  727. "RFC822": time.RFC822,
  728. "RFC822Z": time.RFC822Z,
  729. "RFC850": time.RFC850,
  730. "RFC1123": time.RFC1123,
  731. "RFC1123Z": time.RFC1123Z,
  732. "RFC3339": time.RFC3339,
  733. "RFC3339Nano": time.RFC3339Nano,
  734. "Kitchen": time.Kitchen,
  735. "Stamp": time.Stamp,
  736. "StampMilli": time.StampMilli,
  737. "StampMicro": time.StampMicro,
  738. "StampNano": time.StampNano,
  739. }[timeFormatKey]
  740. // When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05'
  741. if len(TimeFormat) == 0 {
  742. TimeFormat = timeFormatKey
  743. TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat)
  744. if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" {
  745. log.Fatal("Can't create time properly, please check your time format has 2006, 01, 02, 15, 04 and 05")
  746. }
  747. log.Trace("Custom TimeFormat: %s", TimeFormat)
  748. }
  749. }
  750. zone := Cfg.Section("time").Key("DEFAULT_UI_LOCATION").String()
  751. if zone != "" {
  752. DefaultUILocation, err = time.LoadLocation(zone)
  753. if err != nil {
  754. log.Fatal("Load time zone failed: %v", err)
  755. } else {
  756. log.Info("Default UI Location is %v", zone)
  757. }
  758. }
  759. if DefaultUILocation == nil {
  760. DefaultUILocation = time.Local
  761. }
  762. RunUser = Cfg.Section("").Key("RUN_USER").MustString(user.CurrentUsername())
  763. // Does not check run user when the install lock is off.
  764. if InstallLock {
  765. currentUser, match := IsRunUserMatchCurrentUser(RunUser)
  766. if !match {
  767. log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser)
  768. }
  769. }
  770. SSH.BuiltinServerUser = Cfg.Section("server").Key("BUILTIN_SSH_SERVER_USER").MustString(RunUser)
  771. newRepository()
  772. sec = Cfg.Section("picture")
  773. AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
  774. forcePathSeparator(AvatarUploadPath)
  775. if !filepath.IsAbs(AvatarUploadPath) {
  776. AvatarUploadPath = path.Join(AppWorkPath, AvatarUploadPath)
  777. }
  778. RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars"))
  779. forcePathSeparator(RepositoryAvatarUploadPath)
  780. if !filepath.IsAbs(RepositoryAvatarUploadPath) {
  781. RepositoryAvatarUploadPath = path.Join(AppWorkPath, RepositoryAvatarUploadPath)
  782. }
  783. RepositoryAvatarFallback = sec.Key("REPOSITORY_AVATAR_FALLBACK").MustString("none")
  784. RepositoryAvatarFallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString("/img/repo_default.png")
  785. AvatarMaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096)
  786. AvatarMaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072)
  787. AvatarMaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576)
  788. switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source {
  789. case "duoshuo":
  790. GravatarSource = "http://gravatar.duoshuo.com/avatar/"
  791. case "gravatar":
  792. GravatarSource = "https://secure.gravatar.com/avatar/"
  793. case "libravatar":
  794. GravatarSource = "https://seccdn.libravatar.org/avatar/"
  795. default:
  796. GravatarSource = source
  797. }
  798. DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
  799. EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(!InstallLock)
  800. if OfflineMode {
  801. DisableGravatar = true
  802. EnableFederatedAvatar = false
  803. }
  804. if DisableGravatar {
  805. EnableFederatedAvatar = false
  806. }
  807. if EnableFederatedAvatar || !DisableGravatar {
  808. GravatarSourceURL, err = url.Parse(GravatarSource)
  809. if err != nil {
  810. log.Fatal("Failed to parse Gravatar URL(%s): %v",
  811. GravatarSource, err)
  812. }
  813. }
  814. if EnableFederatedAvatar {
  815. LibravatarService = libravatar.New()
  816. if GravatarSourceURL.Scheme == "https" {
  817. LibravatarService.SetUseHTTPS(true)
  818. LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
  819. } else {
  820. LibravatarService.SetUseHTTPS(false)
  821. LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
  822. }
  823. }
  824. if err = Cfg.Section("ui").MapTo(&UI); err != nil {
  825. log.Fatal("Failed to map UI settings: %v", err)
  826. } else if err = Cfg.Section("markdown").MapTo(&Markdown); err != nil {
  827. log.Fatal("Failed to map Markdown settings: %v", err)
  828. } else if err = Cfg.Section("admin").MapTo(&Admin); err != nil {
  829. log.Fatal("Fail to map Admin settings: %v", err)
  830. } else if err = Cfg.Section("api").MapTo(&API); err != nil {
  831. log.Fatal("Failed to map API settings: %v", err)
  832. } else if err = Cfg.Section("metrics").MapTo(&Metrics); err != nil {
  833. log.Fatal("Failed to map Metrics settings: %v", err)
  834. }
  835. u := *appURL
  836. u.Path = path.Join(u.Path, "api", "swagger")
  837. API.SwaggerURL = u.String()
  838. newCron()
  839. newGit()
  840. sec = Cfg.Section("mirror")
  841. Mirror.MinInterval = sec.Key("MIN_INTERVAL").MustDuration(10 * time.Minute)
  842. Mirror.DefaultInterval = sec.Key("DEFAULT_INTERVAL").MustDuration(8 * time.Hour)
  843. if Mirror.MinInterval.Minutes() < 1 {
  844. log.Warn("Mirror.MinInterval is too low")
  845. Mirror.MinInterval = 1 * time.Minute
  846. }
  847. if Mirror.DefaultInterval < Mirror.MinInterval {
  848. log.Warn("Mirror.DefaultInterval is less than Mirror.MinInterval")
  849. Mirror.DefaultInterval = time.Hour * 8
  850. }
  851. Langs = Cfg.Section("i18n").Key("LANGS").Strings(",")
  852. if len(Langs) == 0 {
  853. Langs = []string{
  854. "en-US", "zh-CN", "zh-HK", "zh-TW", "de-DE", "fr-FR", "nl-NL", "lv-LV",
  855. "ru-RU", "uk-UA", "ja-JP", "es-ES", "pt-BR", "pl-PL", "bg-BG", "it-IT",
  856. "fi-FI", "tr-TR", "cs-CZ", "sr-SP", "sv-SE", "ko-KR"}
  857. }
  858. Names = Cfg.Section("i18n").Key("NAMES").Strings(",")
  859. if len(Names) == 0 {
  860. Names = []string{"English", "简体中文", "繁體中文(香港)", "繁體中文(台灣)", "Deutsch",
  861. "français", "Nederlands", "latviešu", "русский", "Українська", "日本語",
  862. "español", "português do Brasil", "polski", "български", "italiano",
  863. "suomi", "Türkçe", "čeština", "српски", "svenska", "한국어"}
  864. }
  865. dateLangs = Cfg.Section("i18n.datelang").KeysHash()
  866. ShowFooterBranding = Cfg.Section("other").Key("SHOW_FOOTER_BRANDING").MustBool(false)
  867. ShowFooterVersion = Cfg.Section("other").Key("SHOW_FOOTER_VERSION").MustBool(true)
  868. ShowFooterTemplateLoadTime = Cfg.Section("other").Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool(true)
  869. UI.ShowUserEmail = Cfg.Section("ui").Key("SHOW_USER_EMAIL").MustBool(true)
  870. UI.DefaultShowFullName = Cfg.Section("ui").Key("DEFAULT_SHOW_FULL_NAME").MustBool(false)
  871. UI.SearchRepoDescription = Cfg.Section("ui").Key("SEARCH_REPO_DESCRIPTION").MustBool(true)
  872. HasRobotsTxt = com.IsFile(path.Join(CustomPath, "robots.txt"))
  873. newMarkup()
  874. sec = Cfg.Section("U2F")
  875. U2F.TrustedFacets, _ = shellquote.Split(sec.Key("TRUSTED_FACETS").MustString(strings.TrimRight(AppURL, "/")))
  876. U2F.AppID = sec.Key("APP_ID").MustString(strings.TrimRight(AppURL, "/"))
  877. zip.Verbose = false
  878. }
  879. func loadInternalToken(sec *ini.Section) string {
  880. uri := sec.Key("INTERNAL_TOKEN_URI").String()
  881. if len(uri) == 0 {
  882. return loadOrGenerateInternalToken(sec)
  883. }
  884. tempURI, err := url.Parse(uri)
  885. if err != nil {
  886. log.Fatal("Failed to parse INTERNAL_TOKEN_URI (%s): %v", uri, err)
  887. }
  888. switch tempURI.Scheme {
  889. case "file":
  890. fp, err := os.OpenFile(tempURI.RequestURI(), os.O_RDWR, 0600)
  891. if err != nil {
  892. log.Fatal("Failed to open InternalTokenURI (%s): %v", uri, err)
  893. }
  894. defer fp.Close()
  895. buf, err := ioutil.ReadAll(fp)
  896. if err != nil {
  897. log.Fatal("Failed to read InternalTokenURI (%s): %v", uri, err)
  898. }
  899. // No token in the file, generate one and store it.
  900. if len(buf) == 0 {
  901. token, err := generate.NewInternalToken()
  902. if err != nil {
  903. log.Fatal("Error generate internal token: %v", err)
  904. }
  905. if _, err := io.WriteString(fp, token); err != nil {
  906. log.Fatal("Error writing to InternalTokenURI (%s): %v", uri, err)
  907. }
  908. return token
  909. }
  910. return string(buf)
  911. default:
  912. log.Fatal("Unsupported URI-Scheme %q (INTERNAL_TOKEN_URI = %q)", tempURI.Scheme, uri)
  913. }
  914. return ""
  915. }
  916. func loadOrGenerateInternalToken(sec *ini.Section) string {
  917. var err error
  918. token := sec.Key("INTERNAL_TOKEN").String()
  919. if len(token) == 0 {
  920. token, err = generate.NewInternalToken()
  921. if err != nil {
  922. log.Fatal("Error generate internal token: %v", err)
  923. }
  924. // Save secret
  925. cfgSave := ini.Empty()
  926. if com.IsFile(CustomConf) {
  927. // Keeps custom settings if there is already something.
  928. if err := cfgSave.Append(CustomConf); err != nil {
  929. log.Error("Failed to load custom conf '%s': %v", CustomConf, err)
  930. }
  931. }
  932. cfgSave.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
  933. if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
  934. log.Fatal("Failed to create '%s': %v", CustomConf, err)
  935. }
  936. if err := cfgSave.SaveTo(CustomConf); err != nil {
  937. log.Fatal("Error saving generated INTERNAL_TOKEN to custom config: %v", err)
  938. }
  939. }
  940. return token
  941. }
  942. // NewServices initializes the services
  943. func NewServices() {
  944. InitDBConfig()
  945. newService()
  946. NewLogServices(false)
  947. newCacheService()
  948. newSessionService()
  949. newCORSService()
  950. newMailService()
  951. newRegisterMailService()
  952. newNotifyMailService()
  953. newWebhookService()
  954. newIndexerService()
  955. newTaskService()
  956. }
上海开阖软件有限公司 沪ICP备12045867号-1