forgejo-federation/routers/install/install.go
Lunny Xiao bd820aa9c5
Add context cache as a request level cache (#22294)
To avoid duplicated load of the same data in an HTTP request, we can set
a context cache to do that. i.e. Some pages may load a user from a
database with the same id in different areas on the same page. But the
code is hidden in two different deep logic. How should we share the
user? As a result of this PR, now if both entry functions accept
`context.Context` as the first parameter and we just need to refactor
`GetUserByID` to reuse the user from the context cache. Then it will not
be loaded twice on an HTTP request.

But of course, sometimes we would like to reload an object from the
database, that's why `RemoveContextData` is also exposed.

The core context cache is here. It defines a new context
```go
type cacheContext struct {
	ctx  context.Context
	data map[any]map[any]any
        lock sync.RWMutex
}

var cacheContextKey = struct{}{}

func WithCacheContext(ctx context.Context) context.Context {
	return context.WithValue(ctx, cacheContextKey, &cacheContext{
		ctx:  ctx,
		data: make(map[any]map[any]any),
	})
}
```

Then you can use the below 4 methods to read/write/del the data within
the same context.

```go
func GetContextData(ctx context.Context, tp, key any) any
func SetContextData(ctx context.Context, tp, key, value any)
func RemoveContextData(ctx context.Context, tp, key any)
func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error)
```

Then let's take a look at how `system.GetString` implement it.

```go
func GetSetting(ctx context.Context, key string) (string, error) {
	return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
		return cache.GetString(genSettingCacheKey(key), func() (string, error) {
			res, err := GetSettingNoCache(ctx, key)
			if err != nil {
				return "", err
			}
			return res.SettingValue, nil
		})
	})
}
```

First, it will check if context data include the setting object with the
key. If not, it will query from the global cache which may be memory or
a Redis cache. If not, it will get the object from the database. In the
end, if the object gets from the global cache or database, it will be
set into the context cache.

An object stored in the context cache will only be destroyed after the
context disappeared.
2023-02-15 21:37:34 +08:00

587 lines
21 KiB
Go

// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package install
import (
goctx "context"
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/models/db"
db_install "code.gitea.io/gitea/models/db/install"
"code.gitea.io/gitea/models/migrations"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/user"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/forms"
"gitea.com/go-chi/session"
"gopkg.in/ini.v1"
)
const (
// tplInstall template for installation page
tplInstall base.TplName = "install"
tplPostInstall base.TplName = "post-install"
)
// getSupportedDbTypeNames returns a slice for supported database types and names. The slice is used to keep the order
func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
for _, t := range setting.SupportedDatabaseTypes {
dbTypeNames = append(dbTypeNames, map[string]string{"type": t, "name": setting.DatabaseTypeNames[t]})
}
return dbTypeNames
}
// Init prepare for rendering installation page
func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
_, rnd := templates.HTMLRenderer(ctx)
dbTypeNames := getSupportedDbTypeNames()
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
if setting.InstallLock {
resp.Header().Add("Refresh", "1; url="+setting.AppURL+"user/login")
_ = rnd.HTML(resp, http.StatusOK, string(tplPostInstall), nil)
return
}
locale := middleware.Locale(resp, req)
startTime := time.Now()
ctx := context.Context{
Resp: context.NewResponse(resp),
Flash: &middleware.Flash{},
Locale: locale,
Render: rnd,
Session: session.GetSession(req),
Data: map[string]interface{}{
"locale": locale,
"Title": locale.Tr("install.install"),
"PageIsInstall": true,
"DbTypeNames": dbTypeNames,
"AllLangs": translation.AllLangs(),
"PageStartTime": startTime,
"PasswordHashAlgorithms": user_model.AvailableHashAlgorithms,
},
}
defer ctx.Close()
ctx.Req = context.WithContext(req, &ctx)
next.ServeHTTP(resp, ctx.Req)
})
}
}
// Install render installation page
func Install(ctx *context.Context) {
form := forms.InstallForm{}
// Database settings
form.DbHost = setting.Database.Host
form.DbUser = setting.Database.User
form.DbPasswd = setting.Database.Passwd
form.DbName = setting.Database.Name
form.DbPath = setting.Database.Path
form.DbSchema = setting.Database.Schema
form.Charset = setting.Database.Charset
curDBType := setting.Database.Type
var isCurDBTypeSupported bool
for _, dbType := range setting.SupportedDatabaseTypes {
if dbType == curDBType {
isCurDBTypeSupported = true
break
}
}
if !isCurDBTypeSupported {
curDBType = "mysql"
}
ctx.Data["CurDbType"] = curDBType
// Application general settings
form.AppName = setting.AppName
form.RepoRootPath = setting.RepoRootPath
form.LFSRootPath = setting.LFS.Path
// Note(unknown): it's hard for Windows users change a running user,
// so just use current one if config says default.
if setting.IsWindows && setting.RunUser == "git" {
form.RunUser = user.CurrentUsername()
} else {
form.RunUser = setting.RunUser
}
form.Domain = setting.Domain
form.SSHPort = setting.SSH.Port
form.HTTPPort = setting.HTTPPort
form.AppURL = setting.AppURL
form.LogRootPath = setting.LogRootPath
// E-mail service settings
if setting.MailService != nil {
form.SMTPAddr = setting.MailService.SMTPAddr
form.SMTPPort = setting.MailService.SMTPPort
form.SMTPFrom = setting.MailService.From
form.SMTPUser = setting.MailService.User
form.SMTPPasswd = setting.MailService.Passwd
}
form.RegisterConfirm = setting.Service.RegisterEmailConfirm
form.MailNotify = setting.Service.EnableNotifyMail
// Server and other services settings
form.OfflineMode = setting.OfflineMode
form.DisableGravatar = setting.DisableGravatar // when installing, there is no database connection so that given a default value
form.EnableFederatedAvatar = setting.EnableFederatedAvatar // when installing, there is no database connection so that given a default value
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
form.DisableRegistration = setting.Service.DisableRegistration
form.AllowOnlyExternalRegistration = setting.Service.AllowOnlyExternalRegistration
form.EnableCaptcha = setting.Service.EnableCaptcha
form.RequireSignInView = setting.Service.RequireSignInView
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
form.NoReplyAddress = setting.Service.NoReplyAddress
form.PasswordAlgorithm = setting.PasswordHashAlgo
middleware.AssignForm(form, ctx.Data)
ctx.HTML(http.StatusOK, tplInstall)
}
func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
var err error
if (setting.Database.Type == "sqlite3") &&
len(setting.Database.Path) == 0 {
ctx.Data["Err_DbPath"] = true
ctx.RenderWithErr(ctx.Tr("install.err_empty_db_path"), tplInstall, form)
return false
}
// Check if the user is trying to re-install in an installed database
db.UnsetDefaultEngine()
defer db.UnsetDefaultEngine()
if err = db.InitEngine(ctx); err != nil {
if strings.Contains(err.Error(), `Unknown database type: sqlite3`) {
ctx.Data["Err_DbType"] = true
ctx.RenderWithErr(ctx.Tr("install.sqlite3_not_available", "https://docs.gitea.io/en-us/install-from-binary/"), tplInstall, form)
} else {
ctx.Data["Err_DbSetting"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, form)
}
return false
}
err = db_install.CheckDatabaseConnection()
if err != nil {
ctx.Data["Err_DbSetting"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, form)
return false
}
hasPostInstallationUser, err := db_install.HasPostInstallationUsers()
if err != nil {
ctx.Data["Err_DbSetting"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_db_table", "user", err), tplInstall, form)
return false
}
dbMigrationVersion, err := db_install.GetMigrationVersion()
if err != nil {
ctx.Data["Err_DbSetting"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_db_table", "version", err), tplInstall, form)
return false
}
if hasPostInstallationUser && dbMigrationVersion > 0 {
log.Error("The database is likely to have been used by Gitea before, database migration version=%d", dbMigrationVersion)
confirmed := form.ReinstallConfirmFirst && form.ReinstallConfirmSecond && form.ReinstallConfirmThird
if !confirmed {
ctx.Data["Err_DbInstalledBefore"] = true
ctx.RenderWithErr(ctx.Tr("install.reinstall_error"), tplInstall, form)
return false
}
log.Info("User confirmed reinstallation of Gitea into a pre-existing database")
}
if hasPostInstallationUser || dbMigrationVersion > 0 {
log.Info("Gitea will be installed in a database with: hasPostInstallationUser=%v, dbMigrationVersion=%v", hasPostInstallationUser, dbMigrationVersion)
}
return true
}
// SubmitInstall response for submit install items
func SubmitInstall(ctx *context.Context) {
var err error
form := *web.GetForm(ctx).(*forms.InstallForm)
// fix form values
if form.AppURL != "" && form.AppURL[len(form.AppURL)-1] != '/' {
form.AppURL += "/"
}
ctx.Data["CurDbType"] = form.DbType
if ctx.HasError() {
if ctx.HasValue("Err_SMTPUser") {
ctx.Data["Err_SMTP"] = true
}
if ctx.HasValue("Err_AdminName") ||
ctx.HasValue("Err_AdminPasswd") ||
ctx.HasValue("Err_AdminEmail") {
ctx.Data["Err_Admin"] = true
}
ctx.HTML(http.StatusOK, tplInstall)
return
}
if _, err = exec.LookPath("git"); err != nil {
ctx.RenderWithErr(ctx.Tr("install.test_git_failed", err), tplInstall, &form)
return
}
// ---- Basic checks are passed, now test configuration.
// Test database setting.
setting.Database.Type = form.DbType
setting.Database.Host = form.DbHost
setting.Database.User = form.DbUser
setting.Database.Passwd = form.DbPasswd
setting.Database.Name = form.DbName
setting.Database.Schema = form.DbSchema
setting.Database.SSLMode = form.SSLMode
setting.Database.Charset = form.Charset
setting.Database.Path = form.DbPath
setting.Database.LogSQL = !setting.IsProd
setting.PasswordHashAlgo = form.PasswordAlgorithm
if !checkDatabase(ctx, &form) {
return
}
// Prepare AppDataPath, it is very important for Gitea
if err = setting.PrepareAppDataPath(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.invalid_app_data_path", err), tplInstall, &form)
return
}
// Test repository root path.
form.RepoRootPath = strings.ReplaceAll(form.RepoRootPath, "\\", "/")
if err = os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil {
ctx.Data["Err_RepoRootPath"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_repo_path", err), tplInstall, &form)
return
}
// Test LFS root path if not empty, empty meaning disable LFS
if form.LFSRootPath != "" {
form.LFSRootPath = strings.ReplaceAll(form.LFSRootPath, "\\", "/")
if err := os.MkdirAll(form.LFSRootPath, os.ModePerm); err != nil {
ctx.Data["Err_LFSRootPath"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form)
return
}
}
// Test log root path.
form.LogRootPath = strings.ReplaceAll(form.LogRootPath, "\\", "/")
if err = os.MkdirAll(form.LogRootPath, os.ModePerm); err != nil {
ctx.Data["Err_LogRootPath"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_log_root_path", err), tplInstall, &form)
return
}
currentUser, match := setting.IsRunUserMatchCurrentUser(form.RunUser)
if !match {
ctx.Data["Err_RunUser"] = true
ctx.RenderWithErr(ctx.Tr("install.run_user_not_match", form.RunUser, currentUser), tplInstall, &form)
return
}
// Check logic loophole between disable self-registration and no admin account.
if form.DisableRegistration && len(form.AdminName) == 0 {
ctx.Data["Err_Services"] = true
ctx.Data["Err_Admin"] = true
ctx.RenderWithErr(ctx.Tr("install.no_admin_and_disable_registration"), tplInstall, form)
return
}
// Check admin user creation
if len(form.AdminName) > 0 {
// Ensure AdminName is valid
if err := user_model.IsUsableUsername(form.AdminName); err != nil {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminName"] = true
if db.IsErrNameReserved(err) {
ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_reserved"), tplInstall, form)
return
} else if db.IsErrNamePatternNotAllowed(err) {
ctx.RenderWithErr(ctx.Tr("install.err_admin_name_pattern_not_allowed"), tplInstall, form)
return
}
ctx.RenderWithErr(ctx.Tr("install.err_admin_name_is_invalid"), tplInstall, form)
return
}
// Check Admin email
if len(form.AdminEmail) == 0 {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminEmail"] = true
ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_email"), tplInstall, form)
return
}
// Check admin password.
if len(form.AdminPasswd) == 0 {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("install.err_empty_admin_password"), tplInstall, form)
return
}
if form.AdminPasswd != form.AdminConfirmPasswd {
ctx.Data["Err_Admin"] = true
ctx.Data["Err_AdminPasswd"] = true
ctx.RenderWithErr(ctx.Tr("form.password_not_match"), tplInstall, form)
return
}
}
// Init the engine with migration
if err = db.InitEngineWithMigration(ctx, migrations.Migrate); err != nil {
db.UnsetDefaultEngine()
ctx.Data["Err_DbSetting"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_db_setting", err), tplInstall, &form)
return
}
// Save settings.
cfg := ini.Empty()
isFile, err := util.IsFile(setting.CustomConf)
if err != nil {
log.Error("Unable to check if %s is a file. Error: %v", setting.CustomConf, err)
}
if isFile {
// Keeps custom settings if there is already something.
if err = cfg.Append(setting.CustomConf); err != nil {
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
}
}
cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type)
cfg.Section("database").Key("HOST").SetValue(setting.Database.Host)
cfg.Section("database").Key("NAME").SetValue(setting.Database.Name)
cfg.Section("database").Key("USER").SetValue(setting.Database.User)
cfg.Section("database").Key("PASSWD").SetValue(setting.Database.Passwd)
cfg.Section("database").Key("SCHEMA").SetValue(setting.Database.Schema)
cfg.Section("database").Key("SSL_MODE").SetValue(setting.Database.SSLMode)
cfg.Section("database").Key("CHARSET").SetValue(setting.Database.Charset)
cfg.Section("database").Key("PATH").SetValue(setting.Database.Path)
cfg.Section("database").Key("LOG_SQL").SetValue("false") // LOG_SQL is rarely helpful
cfg.Section("").Key("APP_NAME").SetValue(form.AppName)
cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath)
cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
cfg.Section("server").Key("SSH_DOMAIN").SetValue(form.Domain)
cfg.Section("server").Key("DOMAIN").SetValue(form.Domain)
cfg.Section("server").Key("HTTP_PORT").SetValue(form.HTTPPort)
cfg.Section("server").Key("ROOT_URL").SetValue(form.AppURL)
if form.SSHPort == 0 {
cfg.Section("server").Key("DISABLE_SSH").SetValue("true")
} else {
cfg.Section("server").Key("DISABLE_SSH").SetValue("false")
cfg.Section("server").Key("SSH_PORT").SetValue(fmt.Sprint(form.SSHPort))
}
if form.LFSRootPath != "" {
cfg.Section("server").Key("LFS_START_SERVER").SetValue("true")
cfg.Section("lfs").Key("PATH").SetValue(form.LFSRootPath)
var lfsJwtSecret string
if lfsJwtSecret, err = generate.NewJwtSecretBase64(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.lfs_jwt_secret_failed", err), tplInstall, &form)
return
}
cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(lfsJwtSecret)
} else {
cfg.Section("server").Key("LFS_START_SERVER").SetValue("false")
}
if len(strings.TrimSpace(form.SMTPAddr)) > 0 {
cfg.Section("mailer").Key("ENABLED").SetValue("true")
cfg.Section("mailer").Key("SMTP_ADDR").SetValue(form.SMTPAddr)
cfg.Section("mailer").Key("SMTP_PORT").SetValue(form.SMTPPort)
cfg.Section("mailer").Key("FROM").SetValue(form.SMTPFrom)
cfg.Section("mailer").Key("USER").SetValue(form.SMTPUser)
cfg.Section("mailer").Key("PASSWD").SetValue(form.SMTPPasswd)
} else {
cfg.Section("mailer").Key("ENABLED").SetValue("false")
}
cfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").SetValue(fmt.Sprint(form.RegisterConfirm))
cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(fmt.Sprint(form.MailNotify))
cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode))
// if you are reinstalling, this maybe not right because of missing version
if err := system_model.SetSettingNoVersion(ctx, system_model.KeyPictureDisableGravatar, strconv.FormatBool(form.DisableGravatar)); err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return
}
if err := system_model.SetSettingNoVersion(ctx, system_model.KeyPictureEnableFederatedAvatar, strconv.FormatBool(form.EnableFederatedAvatar)); err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return
}
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
cfg.Section("service").Key("DISABLE_REGISTRATION").SetValue(fmt.Sprint(form.DisableRegistration))
cfg.Section("service").Key("ALLOW_ONLY_EXTERNAL_REGISTRATION").SetValue(fmt.Sprint(form.AllowOnlyExternalRegistration))
cfg.Section("service").Key("ENABLE_CAPTCHA").SetValue(fmt.Sprint(form.EnableCaptcha))
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(fmt.Sprint(form.RequireSignInView))
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(fmt.Sprint(form.DefaultKeepEmailPrivate))
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(fmt.Sprint(form.DefaultAllowCreateOrganization))
cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(fmt.Sprint(form.DefaultEnableTimetracking))
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(fmt.Sprint(form.NoReplyAddress))
cfg.Section("cron.update_checker").Key("ENABLED").SetValue(fmt.Sprint(form.EnableUpdateChecker))
cfg.Section("").Key("RUN_MODE").SetValue("prod")
cfg.Section("session").Key("PROVIDER").SetValue("file")
cfg.Section("log").Key("MODE").SetValue("console")
cfg.Section("log").Key("LEVEL").SetValue(setting.LogLevel.String())
cfg.Section("log").Key("ROOT_PATH").SetValue(form.LogRootPath)
cfg.Section("log").Key("ROUTER").SetValue("console")
cfg.Section("repository.pull-request").Key("DEFAULT_MERGE_STYLE").SetValue("merge")
cfg.Section("repository.signing").Key("DEFAULT_TRUST_MODEL").SetValue("committer")
cfg.Section("security").Key("INSTALL_LOCK").SetValue("true")
// the internal token could be read from INTERNAL_TOKEN or INTERNAL_TOKEN_URI (the file is guaranteed to be non-empty)
// if there is no InternalToken, generate one and save to security.INTERNAL_TOKEN
if setting.InternalToken == "" {
var internalToken string
if internalToken, err = generate.NewInternalToken(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.internal_token_failed", err), tplInstall, &form)
return
}
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken)
}
// if there is already a SECRET_KEY, we should not overwrite it, otherwise the encrypted data will not be able to be decrypted
if setting.SecretKey == "" {
var secretKey string
if secretKey, err = generate.NewSecretKey(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form)
return
}
cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey)
}
if len(form.PasswordAlgorithm) > 0 {
cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm)
}
log.Info("Save settings to custom config file %s", setting.CustomConf)
err = os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm)
if err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return
}
if err = cfg.SaveTo(setting.CustomConf); err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return
}
// unset default engine before reload database setting
db.UnsetDefaultEngine()
// ---- All checks are passed
// Reload settings (and re-initialize database connection)
reloadSettings(ctx)
// Create admin account
if len(form.AdminName) > 0 {
u := &user_model.User{
Name: form.AdminName,
Email: form.AdminEmail,
Passwd: form.AdminPasswd,
IsAdmin: true,
}
overwriteDefault := &user_model.CreateUserOverwriteOptions{
IsRestricted: util.OptionalBoolFalse,
IsActive: util.OptionalBoolTrue,
}
if err = user_model.CreateUser(u, overwriteDefault); err != nil {
if !user_model.IsErrUserAlreadyExist(err) {
setting.InstallLock = false
ctx.Data["Err_AdminName"] = true
ctx.Data["Err_AdminEmail"] = true
ctx.RenderWithErr(ctx.Tr("install.invalid_admin_setting", err), tplInstall, &form)
return
}
log.Info("Admin account already exist")
u, _ = user_model.GetUserByName(ctx, u.Name)
}
days := 86400 * setting.LogInRememberDays
ctx.SetCookie(setting.CookieUserName, u.Name, days)
ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
setting.CookieRememberName, u.Name, days)
// 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)
return
}
if err = ctx.Session.Set("uname", u.Name); err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return
}
if err = ctx.Session.Release(); err != nil {
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
return
}
}
log.Info("First-time run install finished!")
ctx.Flash.Success(ctx.Tr("install.install_success"))
ctx.RespHeader().Add("Refresh", "1; url="+setting.AppURL+"user/login")
ctx.HTML(http.StatusOK, 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.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)
}
}()
}