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

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