diff --git a/cmd/web.go b/cmd/web.go index 92d4b11b6..e16d1afb5 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -19,6 +19,8 @@ import ( "code.gitea.io/gitea/routers" "code.gitea.io/gitea/routers/routes" + "gitea.com/macaron/macaron" + context2 "github.com/gorilla/context" "github.com/unknwon/com" "github.com/urfave/cli" @@ -114,6 +116,39 @@ func runWeb(ctx *cli.Context) error { setting.WritePIDFile = true } + // Flag for port number in case first time run conflict. + if ctx.IsSet("port") { + if err := setPort(ctx.String("port")); err != nil { + return err + } + } + + // Perform pre-initialization + needsInstall := routers.PreInstallInit(graceful.GetManager().HammerContext()) + if needsInstall { + m := routes.NewMacaron() + routes.RegisterInstallRoute(m) + err := listen(m, false) + select { + case <-graceful.GetManager().IsShutdown(): + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.Close() + return err + default: + } + } else { + NoInstallListener() + } + + if setting.EnablePprof { + go func() { + log.Info("Starting pprof server on localhost:6060") + log.Info("%v", http.ListenAndServe("localhost:6060", nil)) + }() + } + + log.Info("Global init") // Perform global initialization routers.GlobalInit(graceful.GetManager().HammerContext()) @@ -121,41 +156,49 @@ func runWeb(ctx *cli.Context) error { m := routes.NewMacaron() routes.RegisterRoutes(m) - // Flag for port number in case first time run conflict. - if ctx.IsSet("port") { - setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, ctx.String("port"), 1) - setting.HTTPPort = ctx.String("port") + err := listen(m, true) + <-graceful.GetManager().Done() + log.Info("PID: %d Gitea Web Finished", os.Getpid()) + log.Close() + return err +} - switch setting.Protocol { - case setting.UnixSocket: - case setting.FCGI: - case setting.FCGIUnix: - default: - // Save LOCAL_ROOT_URL if port changed - cfg := ini.Empty() - if com.IsFile(setting.CustomConf) { - // Keeps custom settings if there is already something. - if err := cfg.Append(setting.CustomConf); err != nil { - return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err) - } - } +func setPort(port string) error { + setting.AppURL = strings.Replace(setting.AppURL, setting.HTTPPort, port, 1) + setting.HTTPPort = port - defaultLocalURL := string(setting.Protocol) + "://" - if setting.HTTPAddr == "0.0.0.0" { - defaultLocalURL += "localhost" - } else { - defaultLocalURL += setting.HTTPAddr - } - defaultLocalURL += ":" + setting.HTTPPort + "/" - - cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) - - if err := cfg.SaveTo(setting.CustomConf); err != nil { - return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err) + switch setting.Protocol { + case setting.UnixSocket: + case setting.FCGI: + case setting.FCGIUnix: + default: + // Save LOCAL_ROOT_URL if port changed + cfg := ini.Empty() + if com.IsFile(setting.CustomConf) { + // Keeps custom settings if there is already something. + if err := cfg.Append(setting.CustomConf); err != nil { + return fmt.Errorf("Failed to load custom conf '%s': %v", setting.CustomConf, err) } } - } + defaultLocalURL := string(setting.Protocol) + "://" + if setting.HTTPAddr == "0.0.0.0" { + defaultLocalURL += "localhost" + } else { + defaultLocalURL += setting.HTTPAddr + } + defaultLocalURL += ":" + setting.HTTPPort + "/" + + cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL) + + if err := cfg.SaveTo(setting.CustomConf); err != nil { + return fmt.Errorf("Error saving generated JWT Secret to custom config: %v", err) + } + } + return nil +} + +func listen(m *macaron.Macaron, handleRedirector bool) error { listenAddr := setting.HTTPAddr if setting.Protocol != setting.UnixSocket && setting.Protocol != setting.FCGIUnix { listenAddr = net.JoinHostPort(listenAddr, setting.HTTPPort) @@ -166,37 +209,40 @@ func runWeb(ctx *cli.Context) error { log.Info("LFS server enabled") } - if setting.EnablePprof { - go func() { - log.Info("Starting pprof server on localhost:6060") - log.Info("%v", http.ListenAndServe("localhost:6060", nil)) - }() - } - var err error switch setting.Protocol { case setting.HTTP: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runHTTP("tcp", listenAddr, context2.ClearHandler(m)) case setting.HTTPS: if setting.EnableLetsEncrypt { err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m)) break } - if setting.RedirectOtherPort { - go runHTTPRedirector() - } else { - NoHTTPRedirector() + if handleRedirector { + if setting.RedirectOtherPort { + go runHTTPRedirector() + } else { + NoHTTPRedirector() + } } err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) case setting.FCGI: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runFCGI("tcp", listenAddr, context2.ClearHandler(m)) case setting.UnixSocket: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runHTTP("unix", listenAddr, context2.ClearHandler(m)) case setting.FCGIUnix: - NoHTTPRedirector() + if handleRedirector { + NoHTTPRedirector() + } err = runFCGI("unix", listenAddr, context2.ClearHandler(m)) default: log.Fatal("Invalid protocol: %s", setting.Protocol) @@ -206,8 +252,5 @@ func runWeb(ctx *cli.Context) error { log.Critical("Failed to start server: %v", err) } log.Info("HTTP Listener: %s Closed", listenAddr) - <-graceful.GetManager().Done() - log.Info("PID: %d Gitea Web Finished", os.Getpid()) - log.Close() - return nil + return err } diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index f3c41766a..9e039de69 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -37,6 +37,12 @@ func NoMainListener() { graceful.GetManager().InformCleanup() } +// NoInstallListener tells our cleanup routine that we will not be using a possibly provided listener +// for our install HTTP/HTTPS service +func NoInstallListener() { + graceful.GetManager().InformCleanup() +} + func runFCGI(network, listenAddr string, m http.Handler) error { // This needs to handle stdin as fcgi point fcgiServer := graceful.NewServer(network, listenAddr) diff --git a/modules/context/auth.go b/modules/context/auth.go index 00a7032e2..02248384e 100644 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -26,12 +26,6 @@ type ToggleOptions struct { // Toggle returns toggle options as middleware func Toggle(options *ToggleOptions) macaron.Handler { return func(ctx *Context) { - // Cannot view any page before installation. - if !setting.InstallLock { - ctx.Redirect(setting.AppSubURL + "/install") - return - } - isAPIPath := auth.IsAPIPath(ctx.Req.URL.Path) // Check prohibit login users. diff --git a/modules/graceful/manager.go b/modules/graceful/manager.go index 6b134e7d0..903d05ed2 100644 --- a/modules/graceful/manager.go +++ b/modules/graceful/manager.go @@ -31,7 +31,7 @@ const ( // // If you add an additional place you must increment this number // and add a function to call manager.InformCleanup if it's not going to be used -const numberOfServersToCreate = 3 +const numberOfServersToCreate = 4 // Manager represents the graceful server manager interface var manager *Manager diff --git a/modules/graceful/server.go b/modules/graceful/server.go index db73174ac..e7394f349 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -162,7 +162,7 @@ func (srv *Server) Serve(serve ServeFunction) error { srv.setState(stateTerminate) GetManager().ServerDone() // use of closed means that the listeners are closed - i.e. we should be shutting down - return nil - if err != nil && strings.Contains(err.Error(), "use of closed") { + if err == nil || strings.Contains(err.Error(), "use of closed") || strings.Contains(err.Error(), "http: Server closed") { return nil } return err diff --git a/routers/init.go b/routers/init.go index 793033f4a..702acb726 100644 --- a/routers/init.go +++ b/routers/init.go @@ -117,9 +117,46 @@ func InitLocales() { }) } +// PreInstallInit preloads the configuration to check if we need to run install +func PreInstallInit(ctx context.Context) bool { + setting.NewContext() + if !setting.InstallLock { + log.Trace("AppPath: %s", setting.AppPath) + log.Trace("AppWorkPath: %s", setting.AppWorkPath) + log.Trace("Custom path: %s", setting.CustomPath) + log.Trace("Log path: %s", setting.LogRootPath) + log.Trace("Preparing to run install page") + InitLocales() + if setting.EnableSQLite3 { + log.Info("SQLite3 Supported") + } + setting.InitDBConfig() + svg.Init() + } + + return !setting.InstallLock +} + +// PostInstallInit rereads the settings and starts up the database +func PostInstallInit(ctx context.Context) { + setting.NewContext() + setting.InitDBConfig() + if setting.InstallLock { + if err := initDBEngine(ctx); err == nil { + log.Info("ORM engine initialization successful!") + } else { + log.Fatal("ORM engine initialization failed: %v", err) + } + svg.Init() + } +} + // GlobalInit is for global configuration reload-able. func GlobalInit(ctx context.Context) { setting.NewContext() + if !setting.InstallLock { + log.Fatal("Gitea is not installed") + } if err := git.Init(ctx); err != nil { log.Fatal("Git module init failed: %v", err) } @@ -134,59 +171,50 @@ func GlobalInit(ctx context.Context) { NewServices() - if setting.InstallLock { - highlight.NewContext() - external.RegisterParsers() - markup.Init() - if err := initDBEngine(ctx); err == nil { - log.Info("ORM engine initialization successful!") - } else { - log.Fatal("ORM engine initialization failed: %v", err) - } - - if err := models.InitOAuth2(); err != nil { - log.Fatal("Failed to initialize OAuth2 support: %v", err) - } - - models.NewRepoContext() - - // Booting long running goroutines. - cron.NewContext() - issue_indexer.InitIssueIndexer(false) - code_indexer.Init() - if err := stats_indexer.Init(); err != nil { - log.Fatal("Failed to initialize repository stats indexer queue: %v", err) - } - mirror_service.InitSyncMirrors() - webhook.InitDeliverHooks() - if err := pull_service.Init(); err != nil { - log.Fatal("Failed to initialize test pull requests queue: %v", err) - } - if err := task.Init(); err != nil { - log.Fatal("Failed to initialize task scheduler: %v", err) - } - eventsource.GetManager().Init() + highlight.NewContext() + external.RegisterParsers() + markup.Init() + if err := initDBEngine(ctx); err == nil { + log.Info("ORM engine initialization successful!") + } else { + log.Fatal("ORM engine initialization failed: %v", err) } + + if err := models.InitOAuth2(); err != nil { + log.Fatal("Failed to initialize OAuth2 support: %v", err) + } + + models.NewRepoContext() + + // Booting long running goroutines. + cron.NewContext() + issue_indexer.InitIssueIndexer(false) + code_indexer.Init() + if err := stats_indexer.Init(); err != nil { + log.Fatal("Failed to initialize repository stats indexer queue: %v", err) + } + mirror_service.InitSyncMirrors() + webhook.InitDeliverHooks() + if err := pull_service.Init(); err != nil { + log.Fatal("Failed to initialize test pull requests queue: %v", err) + } + if err := task.Init(); err != nil { + log.Fatal("Failed to initialize task scheduler: %v", err) + } + eventsource.GetManager().Init() + if setting.EnableSQLite3 { log.Info("SQLite3 Supported") } checkRunMode() - // Now because Install will re-run GlobalInit once it has set InstallLock - // we can't tell if the ssh port will remain unused until that's done. - // However, see FIXME comment in install.go - if setting.InstallLock { - if setting.SSH.StartBuiltinServer { - ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) - } else { - ssh.Unused() - } - } - - if setting.InstallLock { - sso.Init() + if setting.SSH.StartBuiltinServer { + ssh.Listen(setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + log.Info("SSH server started on %s:%d. Cipher list (%v), key exchange algorithms (%v), MACs (%v)", setting.SSH.ListenHost, setting.SSH.ListenPort, setting.SSH.ServerCiphers, setting.SSH.ServerKeyExchanges, setting.SSH.ServerMACs) + } else { + ssh.Unused() } + sso.Init() svg.Init() } diff --git a/routers/install.go b/routers/install.go index a7fe55774..5d0d089dc 100644 --- a/routers/install.go +++ b/routers/install.go @@ -5,7 +5,7 @@ package routers import ( - "errors" + "net/http" "os" "os/exec" "path/filepath" @@ -27,13 +27,15 @@ import ( const ( // tplInstall template for installation page - tplInstall base.TplName = "install" + tplInstall base.TplName = "install" + tplPostInstall base.TplName = "post-install" ) // InstallInit prepare for rendering installation page func InstallInit(ctx *context.Context) { if setting.InstallLock { - ctx.NotFound("Install", errors.New("Installation is prohibited")) + ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login") + ctx.HTML(200, tplPostInstall) return } @@ -357,7 +359,8 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { return } - GlobalInit(graceful.GetManager().HammerContext()) + // Re-read settings + PostInstallInit(ctx.Req.Context()) // Create admin account if len(form.AdminName) > 0 { @@ -380,6 +383,11 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { u, _ = models.GetUserByName(u.Name) } + days := 86400 * setting.LogInRememberDays + ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true) + ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), + setting.CookieRememberName, u.Name, days, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true) + // Auto-login for admin if err = ctx.Session.Set("uid", u.ID); err != nil { ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) @@ -397,12 +405,18 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) { } log.Info("First-time run install finished!") - // FIXME: This isn't really enough to completely take account of new configuration - // We should really be restarting: - // - On windows this is probably just a simple restart - // - On linux we can't just use graceful.RestartProcess() everything that was passed in on LISTEN_FDS - // (active or not) needs to be passed out and everything new passed out too. - // This means we need to prevent the cleanup goroutine from running prior to the second GlobalInit + ctx.Flash.Success(ctx.Tr("install.install_success")) - ctx.Redirect(form.AppURL + "user/login") + + ctx.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login") + ctx.HTML(200, tplPostInstall) + + // Now get the http.Server from this request and shut it down + // NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown + srv := ctx.Req.Context().Value(http.ServerContextKey).(*http.Server) + go func() { + if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil { + log.Error("Unable to shutdown the install server! Error: %v", err) + } + }() } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 9f7ff277c..7f43b3b2b 100644 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -301,6 +301,15 @@ func NewMacaron() *macaron.Macaron { return m } +// RegisterInstallRoute registers the install routes +func RegisterInstallRoute(m *macaron.Macaron) { + m.Combo("/", routers.InstallInit).Get(routers.Install). + Post(binding.BindIgnErr(auth.InstallForm{}), routers.InstallPost) + m.NotFound(func(ctx *context.Context) { + ctx.Redirect(setting.AppURL, 302) + }) +} + // RegisterRoutes routes routes to Macaron func RegisterRoutes(m *macaron.Macaron) { reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true}) diff --git a/templates/install.tmpl b/templates/install.tmpl index 27cf1034c..f0e4680c3 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -10,7 +10,7 @@
{{.i18n.Tr "install.docker_helper" "https://docs.gitea.io/en-us/install-with-docker/" | Safe}}
-