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 32KB

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