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

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