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.

setting.go 33KB

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