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.

web.go 10KB

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