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

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