gitea源码

web.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package cmd
  4. import (
  5. "context"
  6. "fmt"
  7. "net"
  8. "net/http"
  9. "os"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "time"
  14. _ "net/http/pprof" // Used for debugging if enabled and a web server is running
  15. "code.gitea.io/gitea/modules/container"
  16. "code.gitea.io/gitea/modules/graceful"
  17. "code.gitea.io/gitea/modules/gtprof"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/process"
  20. "code.gitea.io/gitea/modules/public"
  21. "code.gitea.io/gitea/modules/setting"
  22. "code.gitea.io/gitea/modules/util"
  23. "code.gitea.io/gitea/routers"
  24. "code.gitea.io/gitea/routers/install"
  25. "github.com/felixge/fgprof"
  26. "github.com/urfave/cli/v3"
  27. )
  28. // PIDFile could be set from build tag
  29. var PIDFile = "/run/gitea.pid"
  30. // CmdWeb represents the available web sub-command.
  31. var CmdWeb = &cli.Command{
  32. Name: "web",
  33. Usage: "Start Gitea web server",
  34. Description: `Gitea web server is the only thing you need to run,
  35. and it takes care of all the other things for you`,
  36. Before: PrepareConsoleLoggerLevel(log.INFO),
  37. Action: runWeb,
  38. Flags: []cli.Flag{
  39. &cli.StringFlag{
  40. Name: "port",
  41. Aliases: []string{"p"},
  42. Value: "3000",
  43. Usage: "Temporary port number to prevent conflict",
  44. },
  45. &cli.StringFlag{
  46. Name: "install-port",
  47. Value: "3000",
  48. Usage: "Temporary port number to run the install page on to prevent conflict",
  49. },
  50. &cli.StringFlag{
  51. Name: "pid",
  52. Aliases: []string{"P"},
  53. Value: PIDFile,
  54. Usage: "Custom pid file path",
  55. },
  56. &cli.BoolFlag{
  57. Name: "quiet",
  58. Aliases: []string{"q"},
  59. Usage: "Only display Fatal logging errors until logging is set-up",
  60. },
  61. &cli.BoolFlag{
  62. Name: "verbose",
  63. Usage: "Set initial logging to TRACE level until logging is properly set-up",
  64. },
  65. },
  66. }
  67. func runHTTPRedirector() {
  68. _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: HTTP Redirector", process.SystemProcessType, true)
  69. defer finished()
  70. source := fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.PortToRedirect)
  71. dest := strings.TrimSuffix(setting.AppURL, "/")
  72. log.Info("Redirecting: %s to %s", source, dest)
  73. handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  74. target := dest + r.URL.Path
  75. if len(r.URL.RawQuery) > 0 {
  76. target += "?" + r.URL.RawQuery
  77. }
  78. http.Redirect(w, r, target, http.StatusTemporaryRedirect)
  79. })
  80. err := runHTTP("tcp", source, "HTTP Redirector", handler, setting.RedirectorUseProxyProtocol)
  81. if err != nil {
  82. log.Fatal("Failed to start port redirection: %v", err)
  83. }
  84. }
  85. func createPIDFile(pidPath string) {
  86. currentPid := os.Getpid()
  87. if err := os.MkdirAll(filepath.Dir(pidPath), os.ModePerm); err != nil {
  88. log.Fatal("Failed to create PID folder: %v", err)
  89. }
  90. file, err := os.Create(pidPath)
  91. if err != nil {
  92. log.Fatal("Failed to create PID file: %v", err)
  93. }
  94. defer file.Close()
  95. if _, err := file.WriteString(strconv.FormatInt(int64(currentPid), 10)); err != nil {
  96. log.Fatal("Failed to write PID information: %v", err)
  97. }
  98. }
  99. func showWebStartupMessage(msg string) {
  100. log.Info("Gitea version: %s%s", setting.AppVer, setting.AppBuiltWith)
  101. log.Info("* RunMode: %s", setting.RunMode)
  102. log.Info("* AppPath: %s", setting.AppPath)
  103. log.Info("* WorkPath: %s", setting.AppWorkPath)
  104. log.Info("* CustomPath: %s", setting.CustomPath)
  105. log.Info("* ConfigFile: %s", setting.CustomConf)
  106. log.Info("%s", msg) // show startup message
  107. if setting.CORSConfig.Enabled {
  108. log.Info("CORS Service Enabled")
  109. }
  110. if setting.DefaultUILocation != time.Local {
  111. log.Info("Default UI Location is %v", setting.DefaultUILocation.String())
  112. }
  113. if setting.MailService != nil {
  114. log.Info("Mail Service Enabled: RegisterEmailConfirm=%v, Service.EnableNotifyMail=%v", setting.Service.RegisterEmailConfirm, setting.Service.EnableNotifyMail)
  115. }
  116. }
  117. func serveInstall(cmd *cli.Command) error {
  118. showWebStartupMessage("Prepare to run install page")
  119. routers.InitWebInstallPage(graceful.GetManager().HammerContext())
  120. // Flag for port number in case first time run conflict
  121. if cmd.IsSet("port") {
  122. if err := setPort(cmd.String("port")); err != nil {
  123. return err
  124. }
  125. }
  126. if cmd.IsSet("install-port") {
  127. if err := setPort(cmd.String("install-port")); err != nil {
  128. return err
  129. }
  130. }
  131. c := install.Routes()
  132. err := listen(c, false)
  133. if err != nil {
  134. log.Critical("Unable to open listener for installer. Is Gitea already running?")
  135. graceful.GetManager().DoGracefulShutdown()
  136. }
  137. select {
  138. case <-graceful.GetManager().IsShutdown():
  139. <-graceful.GetManager().Done()
  140. log.Info("PID: %d Gitea Web Finished", os.Getpid())
  141. log.GetManager().Close()
  142. return err
  143. default:
  144. }
  145. return nil
  146. }
  147. func serveInstalled(c *cli.Command) error {
  148. setting.InitCfgProvider(setting.CustomConf)
  149. setting.LoadCommonSettings()
  150. setting.MustInstalled()
  151. showWebStartupMessage("Prepare to run web server")
  152. if setting.AppWorkPathMismatch {
  153. log.Error("WORK_PATH from config %q doesn't match other paths from environment variables or command arguments. "+
  154. "Only WORK_PATH in config should be set and used. Please make sure the path in config file is correct, "+
  155. "remove the other outdated work paths from environment variables and command arguments", setting.CustomConf)
  156. }
  157. rootCfg := setting.CfgProvider
  158. if rootCfg.Section("").Key("WORK_PATH").String() == "" {
  159. saveCfg, err := rootCfg.PrepareSaving()
  160. if err != nil {
  161. log.Error("Unable to prepare saving WORK_PATH=%s to config %q: %v\nYou should set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err)
  162. } else {
  163. rootCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
  164. saveCfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
  165. if err = saveCfg.Save(); err != nil {
  166. log.Error("Unable to update WORK_PATH=%s to config %q: %v\nYou should set it manually, otherwise there might be bugs when accessing the git repositories.", setting.AppWorkPath, setting.CustomConf, err)
  167. }
  168. }
  169. }
  170. // in old versions, user's custom web files are placed in "custom/public", and they were served as "http://domain.com/assets/xxx"
  171. // now, Gitea only serves pre-defined files in the "custom/public" folder basing on the web root, the user should move their custom files to "custom/public/assets"
  172. publicFiles, _ := public.AssetFS().ListFiles(".")
  173. publicFilesSet := container.SetOf(publicFiles...)
  174. publicFilesSet.Remove(".well-known")
  175. publicFilesSet.Remove("assets")
  176. publicFilesSet.Remove("robots.txt")
  177. for _, fn := range publicFilesSet.Values() {
  178. log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn)
  179. }
  180. if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil {
  181. log.Error(`Found legacy public asset "robots.txt" in CustomPath. Please move it to %s/public/robots.txt`, setting.CustomPath)
  182. }
  183. routers.InitWebInstalled(graceful.GetManager().HammerContext())
  184. // We check that AppDataPath exists here (it should have been created during installation)
  185. // We can't check it in `InitWebInstalled`, because some integration tests
  186. // use cmd -> InitWebInstalled, but the AppDataPath doesn't exist during those tests.
  187. if _, err := os.Stat(setting.AppDataPath); err != nil {
  188. log.Fatal("Can not find APP_DATA_PATH %q", setting.AppDataPath)
  189. }
  190. // the AppDataTempDir is fully managed by us with a safe sub-path
  191. // so it's safe to automatically remove the outdated files
  192. setting.AppDataTempDir("").RemoveOutdated(3 * 24 * time.Hour)
  193. // Override the provided port number within the configuration
  194. if c.IsSet("port") {
  195. if err := setPort(c.String("port")); err != nil {
  196. return err
  197. }
  198. }
  199. gtprof.EnableBuiltinTracer(util.Iif(setting.IsProd, 2000*time.Millisecond, 100*time.Millisecond))
  200. // Set up Chi routes
  201. webRoutes := routers.NormalRoutes()
  202. err := listen(webRoutes, true)
  203. <-graceful.GetManager().Done()
  204. log.Info("PID: %d Gitea Web Finished", os.Getpid())
  205. log.GetManager().Close()
  206. return err
  207. }
  208. func servePprof() {
  209. // FIXME: it shouldn't use the global DefaultServeMux, and it should use a proper context
  210. http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
  211. _, _, finished := process.GetManager().AddTypedContext(context.TODO(), "Web: PProf Server", process.SystemProcessType, true)
  212. // The pprof server is for debug purpose only, it shouldn't be exposed on public network. At the moment, it's not worth introducing a configurable option for it.
  213. log.Info("Starting pprof server on localhost:6060")
  214. log.Info("Stopped pprof server: %v", http.ListenAndServe("localhost:6060", nil))
  215. finished()
  216. }
  217. func runWeb(ctx context.Context, cmd *cli.Command) error {
  218. defer func() {
  219. if panicked := recover(); panicked != nil {
  220. log.Fatal("PANIC: %v\n%s", panicked, log.Stack(2))
  221. }
  222. }()
  223. if subCmdName, valid := isValidDefaultSubCommand(cmd); !valid {
  224. return fmt.Errorf("unknown command: %s", subCmdName)
  225. }
  226. managerCtx, cancel := context.WithCancel(ctx)
  227. graceful.InitManager(managerCtx)
  228. defer cancel()
  229. if os.Getppid() > 1 && len(os.Getenv("LISTEN_FDS")) > 0 {
  230. log.Info("Restarting Gitea on PID: %d from parent PID: %d", os.Getpid(), os.Getppid())
  231. } else {
  232. log.Info("Starting Gitea on PID: %d", os.Getpid())
  233. }
  234. // Set pid file setting
  235. if cmd.IsSet("pid") {
  236. createPIDFile(cmd.String("pid"))
  237. }
  238. if !setting.InstallLock {
  239. if err := serveInstall(cmd); err != nil {
  240. return err
  241. }
  242. } else {
  243. NoInstallListener()
  244. }
  245. if setting.EnablePprof {
  246. go servePprof()
  247. }
  248. return serveInstalled(cmd)
  249. }
  250. func setPort(port string) error {
  251. setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1)
  252. setting.HTTPPort = port
  253. switch setting.Protocol {
  254. case setting.HTTPUnix:
  255. case setting.FCGI:
  256. case setting.FCGIUnix:
  257. default:
  258. defaultLocalURL := string(setting.Protocol) + "://"
  259. if setting.HTTPAddr == "0.0.0.0" {
  260. defaultLocalURL += "localhost"
  261. } else {
  262. defaultLocalURL += setting.HTTPAddr
  263. }
  264. defaultLocalURL += ":" + setting.HTTPPort + "/"
  265. // Save LOCAL_ROOT_URL if port changed
  266. rootCfg := setting.CfgProvider
  267. saveCfg, err := rootCfg.PrepareSaving()
  268. if err != nil {
  269. return fmt.Errorf("failed to save config file: %v", err)
  270. }
  271. rootCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
  272. saveCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
  273. if err = saveCfg.Save(); err != nil {
  274. return fmt.Errorf("failed to save config file: %v", err)
  275. }
  276. }
  277. return nil
  278. }
  279. func listen(m http.Handler, handleRedirector bool) error {
  280. listenAddr := setting.HTTPAddr
  281. if setting.Protocol != setting.HTTPUnix && setting.Protocol != setting.FCGIUnix {
  282. listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort)
  283. }
  284. _, _, finished := process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), "Web: Gitea Server", process.SystemProcessType, true)
  285. defer finished()
  286. log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubURL)
  287. // This can be useful for users, many users do wrong to their config and get strange behaviors behind a reverse-proxy.
  288. // A user may fix the configuration mistake when he sees this log.
  289. // And this is also very helpful to maintainers to provide help to users to resolve their configuration problems.
  290. log.Info("AppURL(ROOT_URL): %s", setting.AppURL)
  291. if setting.LFS.StartServer {
  292. log.Info("LFS server enabled")
  293. }
  294. var err error
  295. switch setting.Protocol {
  296. case setting.HTTP:
  297. if handleRedirector {
  298. NoHTTPRedirector()
  299. }
  300. err = runHTTP("tcp", listenAddr, "Web", m, setting.UseProxyProtocol)
  301. case setting.HTTPS:
  302. if setting.EnableAcme {
  303. err = runACME(listenAddr, m)
  304. break
  305. }
  306. if handleRedirector {
  307. if setting.RedirectOtherPort {
  308. go runHTTPRedirector()
  309. } else {
  310. NoHTTPRedirector()
  311. }
  312. }
  313. err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging)
  314. case setting.FCGI:
  315. if handleRedirector {
  316. NoHTTPRedirector()
  317. }
  318. err = runFCGI("tcp", listenAddr, "FCGI Web", m, setting.UseProxyProtocol)
  319. case setting.HTTPUnix:
  320. if handleRedirector {
  321. NoHTTPRedirector()
  322. }
  323. err = runHTTP("unix", listenAddr, "Web", m, setting.UseProxyProtocol)
  324. case setting.FCGIUnix:
  325. if handleRedirector {
  326. NoHTTPRedirector()
  327. }
  328. err = runFCGI("unix", listenAddr, "Web", m, setting.UseProxyProtocol)
  329. default:
  330. log.Fatal("Invalid protocol: %s", setting.Protocol)
  331. }
  332. if err != nil {
  333. log.Critical("Failed to start server: %v", err)
  334. }
  335. log.Info("HTTP Listener: %s Closed", listenAddr)
  336. return err
  337. }