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

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