選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

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