code.gitea.io/gitea@v1.21.7/cmd/web.go (about)

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