gitea源码

server.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package setting
  4. import (
  5. "encoding/base64"
  6. "net"
  7. "net/url"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/modules/json"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/util"
  16. )
  17. // Scheme describes protocol types
  18. type Scheme string
  19. // enumerates all the scheme types
  20. const (
  21. HTTP Scheme = "http"
  22. HTTPS Scheme = "https"
  23. FCGI Scheme = "fcgi"
  24. FCGIUnix Scheme = "fcgi+unix"
  25. HTTPUnix Scheme = "http+unix"
  26. )
  27. // LandingPage describes the default page
  28. type LandingPage string
  29. // enumerates all the landing page types
  30. const (
  31. LandingPageHome LandingPage = "/"
  32. LandingPageExplore LandingPage = "/explore"
  33. LandingPageOrganizations LandingPage = "/explore/organizations"
  34. LandingPageLogin LandingPage = "/user/login"
  35. )
  36. const (
  37. PublicURLAuto = "auto"
  38. PublicURLLegacy = "legacy"
  39. )
  40. // Server settings
  41. var (
  42. // AppURL is the Application ROOT_URL. It always has a '/' suffix
  43. // It maps to ini:"ROOT_URL"
  44. AppURL string
  45. // PublicURLDetection controls how to use the HTTP request headers to detect public URL
  46. PublicURLDetection string
  47. // AppSubURL represents the sub-url mounting point for gitea, parsed from "ROOT_URL"
  48. // It is either "" or starts with '/' and ends without '/', such as '/{sub-path}'.
  49. // This value is empty if site does not have sub-url.
  50. AppSubURL string
  51. // UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...",
  52. // to make it easier to debug sub-path related problems without a reverse proxy.
  53. UseSubURLPath bool
  54. // AppDataPath is the default path for storing data.
  55. // It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
  56. AppDataPath string
  57. // LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix
  58. // It maps to ini:"LOCAL_ROOT_URL" in [server]
  59. LocalURL string
  60. // AssetVersion holds an opaque value that is used for cache-busting assets
  61. AssetVersion string
  62. // appTempPathInternal is the temporary path for the app, it is only an internal variable
  63. // DO NOT use it directly, always use AppDataTempDir
  64. appTempPathInternal string
  65. Protocol Scheme
  66. UseProxyProtocol bool
  67. ProxyProtocolTLSBridging bool
  68. ProxyProtocolHeaderTimeout time.Duration
  69. ProxyProtocolAcceptUnknown bool
  70. Domain string
  71. HTTPAddr string
  72. HTTPPort string
  73. LocalUseProxyProtocol bool
  74. RedirectOtherPort bool
  75. RedirectorUseProxyProtocol bool
  76. PortToRedirect string
  77. OfflineMode bool
  78. CertFile string
  79. KeyFile string
  80. StaticRootPath string
  81. StaticCacheTime time.Duration
  82. EnableGzip bool
  83. LandingPageURL LandingPage
  84. UnixSocketPermission uint32
  85. EnablePprof bool
  86. PprofDataPath string
  87. EnableAcme bool
  88. AcmeTOS bool
  89. AcmeLiveDirectory string
  90. AcmeEmail string
  91. AcmeURL string
  92. AcmeCARoot string
  93. SSLMinimumVersion string
  94. SSLMaximumVersion string
  95. SSLCurvePreferences []string
  96. SSLCipherSuites []string
  97. GracefulRestartable bool
  98. GracefulHammerTime time.Duration
  99. StartupTimeout time.Duration
  100. PerWriteTimeout = 30 * time.Second
  101. PerWritePerKbTimeout = 10 * time.Second
  102. StaticURLPrefix string
  103. AbsoluteAssetURL string
  104. ManifestData string
  105. )
  106. // MakeManifestData generates web app manifest JSON
  107. func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte {
  108. type manifestIcon struct {
  109. Src string `json:"src"`
  110. Type string `json:"type"`
  111. Sizes string `json:"sizes"`
  112. }
  113. type manifestJSON struct {
  114. Name string `json:"name"`
  115. ShortName string `json:"short_name"`
  116. StartURL string `json:"start_url"`
  117. Icons []manifestIcon `json:"icons"`
  118. }
  119. bytes, err := json.Marshal(&manifestJSON{
  120. Name: appName,
  121. ShortName: appName,
  122. StartURL: appURL,
  123. Icons: []manifestIcon{
  124. {
  125. Src: absoluteAssetURL + "/assets/img/logo.png",
  126. Type: "image/png",
  127. Sizes: "512x512",
  128. },
  129. {
  130. Src: absoluteAssetURL + "/assets/img/logo.svg",
  131. Type: "image/svg+xml",
  132. Sizes: "512x512",
  133. },
  134. },
  135. })
  136. if err != nil {
  137. log.Error("unable to marshal manifest JSON. Error: %v", err)
  138. return make([]byte, 0)
  139. }
  140. return bytes
  141. }
  142. // MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash
  143. func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
  144. parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
  145. if err != nil {
  146. log.Fatal("Unable to parse STATIC_URL_PREFIX: %v", err)
  147. }
  148. if err == nil && parsedPrefix.Hostname() == "" {
  149. if staticURLPrefix == "" {
  150. return strings.TrimSuffix(appURL, "/")
  151. }
  152. // StaticURLPrefix is just a path
  153. return util.URLJoin(appURL, strings.TrimSuffix(staticURLPrefix, "/"))
  154. }
  155. return strings.TrimSuffix(staticURLPrefix, "/")
  156. }
  157. func loadServerFrom(rootCfg ConfigProvider) {
  158. sec := rootCfg.Section("server")
  159. AppName = rootCfg.Section("").Key("APP_NAME").MustString("Gitea: Git with a cup of tea")
  160. Domain = sec.Key("DOMAIN").MustString("localhost")
  161. HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
  162. HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
  163. // DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
  164. // if these are removed, the warning will not be shown
  165. if sec.HasKey("ENABLE_ACME") {
  166. EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
  167. } else {
  168. deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
  169. EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
  170. }
  171. protocolCfg := sec.Key("PROTOCOL").String()
  172. if protocolCfg != "https" && EnableAcme {
  173. log.Fatal("ACME could only be used with HTTPS protocol")
  174. }
  175. switch protocolCfg {
  176. case "", "http":
  177. Protocol = HTTP
  178. case "https":
  179. Protocol = HTTPS
  180. if EnableAcme {
  181. AcmeURL = sec.Key("ACME_URL").MustString("")
  182. AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
  183. if sec.HasKey("ACME_ACCEPTTOS") {
  184. AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false)
  185. } else {
  186. deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", "v1.19.0")
  187. AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false)
  188. }
  189. if !AcmeTOS {
  190. log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).")
  191. }
  192. if sec.HasKey("ACME_DIRECTORY") {
  193. AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https")
  194. } else {
  195. deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", "v1.19.0")
  196. AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
  197. }
  198. if sec.HasKey("ACME_EMAIL") {
  199. AcmeEmail = sec.Key("ACME_EMAIL").MustString("")
  200. } else {
  201. deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
  202. AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
  203. }
  204. if AcmeEmail == "" {
  205. log.Fatal("ACME Email is not set (ACME_EMAIL).")
  206. }
  207. } else {
  208. CertFile = sec.Key("CERT_FILE").String()
  209. KeyFile = sec.Key("KEY_FILE").String()
  210. if len(CertFile) > 0 && !filepath.IsAbs(CertFile) {
  211. CertFile = filepath.Join(CustomPath, CertFile)
  212. }
  213. if len(KeyFile) > 0 && !filepath.IsAbs(KeyFile) {
  214. KeyFile = filepath.Join(CustomPath, KeyFile)
  215. }
  216. }
  217. SSLMinimumVersion = sec.Key("SSL_MIN_VERSION").MustString("")
  218. SSLMaximumVersion = sec.Key("SSL_MAX_VERSION").MustString("")
  219. SSLCurvePreferences = sec.Key("SSL_CURVE_PREFERENCES").Strings(",")
  220. SSLCipherSuites = sec.Key("SSL_CIPHER_SUITES").Strings(",")
  221. case "fcgi":
  222. Protocol = FCGI
  223. case "fcgi+unix", "unix", "http+unix":
  224. switch protocolCfg {
  225. case "fcgi+unix":
  226. Protocol = FCGIUnix
  227. case "unix":
  228. log.Warn("unix PROTOCOL value is deprecated, please use http+unix")
  229. fallthrough
  230. default: // "http+unix"
  231. Protocol = HTTPUnix
  232. }
  233. UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
  234. UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32)
  235. if err != nil || UnixSocketPermissionParsed > 0o777 {
  236. log.Fatal("Failed to parse unixSocketPermission: %s", UnixSocketPermissionRaw)
  237. }
  238. UnixSocketPermission = uint32(UnixSocketPermissionParsed)
  239. if !filepath.IsAbs(HTTPAddr) {
  240. HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
  241. }
  242. default:
  243. log.Fatal("Invalid PROTOCOL %q", protocolCfg)
  244. }
  245. UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
  246. ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
  247. ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second)
  248. ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false)
  249. GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true)
  250. GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second)
  251. StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second)
  252. PerWriteTimeout = sec.Key("PER_WRITE_TIMEOUT").MustDuration(PerWriteTimeout)
  253. PerWritePerKbTimeout = sec.Key("PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout)
  254. defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort
  255. AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL)
  256. PublicURLDetection = sec.Key("PUBLIC_URL_DETECTION").MustString(PublicURLLegacy)
  257. if PublicURLDetection != PublicURLAuto && PublicURLDetection != PublicURLLegacy {
  258. log.Fatal("Invalid PUBLIC_URL_DETECTION value: %s", PublicURLDetection)
  259. }
  260. // Check validity of AppURL
  261. appURL, err := url.Parse(AppURL)
  262. if err != nil {
  263. log.Fatal("Invalid ROOT_URL %q: %s", AppURL, err)
  264. }
  265. // Remove default ports from AppURL.
  266. // (scheme-based URL normalization, RFC 3986 section 6.2.3)
  267. if (appURL.Scheme == string(HTTP) && appURL.Port() == "80") || (appURL.Scheme == string(HTTPS) && appURL.Port() == "443") {
  268. appURL.Host = appURL.Hostname()
  269. }
  270. // This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
  271. AppURL = strings.TrimRight(appURL.String(), "/") + "/"
  272. // AppSubURL should start with '/' and end without '/', such as '/{subpath}'.
  273. // This value is empty if site does not have sub-url.
  274. AppSubURL = strings.TrimSuffix(appURL.Path, "/")
  275. UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false)
  276. StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
  277. // Check if Domain differs from AppURL domain than update it to AppURL's domain
  278. urlHostname := appURL.Hostname()
  279. if urlHostname != Domain && net.ParseIP(urlHostname) == nil && urlHostname != "" {
  280. Domain = urlHostname
  281. }
  282. AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix)
  283. AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed)
  284. manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL)
  285. ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes)
  286. var defaultLocalURL string
  287. switch Protocol {
  288. case HTTPUnix:
  289. defaultLocalURL = "http://unix/"
  290. case FCGI:
  291. defaultLocalURL = AppURL
  292. case FCGIUnix:
  293. defaultLocalURL = AppURL
  294. case HTTP, HTTPS:
  295. defaultLocalURL = string(Protocol) + "://"
  296. if HTTPAddr == "0.0.0.0" {
  297. defaultLocalURL += net.JoinHostPort("localhost", HTTPPort) + "/"
  298. } else {
  299. defaultLocalURL += net.JoinHostPort(HTTPAddr, HTTPPort) + "/"
  300. }
  301. default:
  302. log.Fatal("Invalid PROTOCOL %q", Protocol)
  303. }
  304. LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
  305. LocalURL = strings.TrimRight(LocalURL, "/") + "/"
  306. LocalUseProxyProtocol = sec.Key("LOCAL_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol)
  307. RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false)
  308. PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80")
  309. RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol)
  310. OfflineMode = sec.Key("OFFLINE_MODE").MustBool(true)
  311. if len(StaticRootPath) == 0 {
  312. StaticRootPath = AppWorkPath
  313. }
  314. StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath)
  315. StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour)
  316. AppDataPath = sec.Key("APP_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data"))
  317. if !filepath.IsAbs(AppDataPath) {
  318. AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
  319. }
  320. if IsInTesting && HasInstallLock(rootCfg) {
  321. // FIXME: in testing, the "app data" directory is not correctly initialized before loading settings
  322. if _, err := os.Stat(AppDataPath); err != nil {
  323. _ = os.MkdirAll(AppDataPath, os.ModePerm)
  324. }
  325. }
  326. appTempPathInternal = sec.Key("APP_TEMP_PATH").String()
  327. if appTempPathInternal != "" {
  328. if _, err := os.Stat(appTempPathInternal); err != nil {
  329. log.Fatal("APP_TEMP_PATH %q is not accessible: %v", appTempPathInternal, err)
  330. }
  331. }
  332. EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
  333. EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
  334. PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data/tmp/pprof"))
  335. if !filepath.IsAbs(PprofDataPath) {
  336. PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
  337. }
  338. checkOverlappedPath("[server].PPROF_DATA_PATH", PprofDataPath)
  339. landingPage := sec.Key("LANDING_PAGE").MustString("home")
  340. switch landingPage {
  341. case "explore":
  342. LandingPageURL = LandingPageExplore
  343. case "organizations":
  344. LandingPageURL = LandingPageOrganizations
  345. case "login":
  346. LandingPageURL = LandingPageLogin
  347. case "", "home":
  348. LandingPageURL = LandingPageHome
  349. default:
  350. LandingPageURL = LandingPage(landingPage)
  351. }
  352. }