forked from NYANDEV/forgejo
bd820aa9c5
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.
587 lines
21 KiB
Go
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)
|
|
}
|
|
}()
|
|
}
|