forked from NYANDEV/forgejo
736dfab3ae
- Resolves #271 - Ensure that the adminstrator password is at least `MIN_PASSWORD_LENGTH`. (cherry picked from commit 28cb04c3f5040980e716ce66cd5906f324257e02) (cherry picked from commit 95371ebd92cd005e2d50a4754e60525cf6135b86) (cherry picked from commit a134288ab6b0291082d913c4e22456b31af58af9) (cherry picked from commit 4202f052cb32aec71a61dd2afd814035a9d85eea) (cherry picked from commit 510b7467d3ee0bf346ad1843775affe1df0675ae) (cherry picked from commit f3a6e1f121e89aaf608fd9890eaf06ed939d1006) (cherry picked from commit f340508819866f355feec6d01b349fa7df29ace9) (cherry picked from commit b891bb176d48c3855cc5b6e4573e7a337af9d382) (cherry picked from commit 1a1bfc38cc7863f5cb3022560cacb2006d08f113) (cherry picked from commit 083d5aefed10e54814c4438eabcd01973d305502) (cherry picked from commit 4586096be9b6214058245da3227541866ea4312f) (cherry picked from commit 039fa20cc8a5b50d5cc37de4503e8a9a80042bcc) (cherry picked from commit 3ec9cb5f5915cd0bd46ca0d20d0ab798dc7bd135) (cherry picked from commit 00be0eee3727130966c34a3b95b10f2af06ea2ec) (cherry picked from commit a1566030025df8cc83d20cbe2b6fb0f87304a1a5) (cherry picked from commit 4d305e77742c181f68cd24724dfc685723a41b31) (cherry picked from commit 51e8f21202ea766d69a4b3c26f44c6db07f47844) (cherry picked from commit 58e354c98e6b361f6d651ffdca3d5cb459adbf2f) (cherry picked from commit 20405564f56775ba0f29a54c9a6eca8136d8ac99) (cherry picked from commit1d7f495683
) (cherry picked from commitd457b9c911
) (cherry picked from commit 72b54bc4cce030540310e50acc41ea789a1e5221) (cherry picked from commit d7ce723e350d21ef42eba7b7013543e2ba6e0e17) (cherry picked from commit ce5f863d5d3eff77b9736db453f0f9a65241c9bb) (cherry picked from commit 324b9318acbf5e12be922ee7f8fc0f0fece1743a) (cherry picked from commit fff11fc535c1a1122914170363bfc23aeb52e02c) (cherry picked from commit d3fa04aa699883df9b227382190f57726c591cb8) (cherry picked from commit d3b24691f389d863be834ccc8b2c8910b1614f30)
597 lines
22 KiB
Go
597 lines
22 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 (
|
|
"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/auth/password/hash"
|
|
"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/routers/common"
|
|
"code.gitea.io/gitea/services/forms"
|
|
|
|
"gitea.com/go-chi/session"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// Contexter prepare for rendering installation page
|
|
func Contexter() func(next http.Handler) http.Handler {
|
|
rnd := templates.HTMLRenderer()
|
|
dbTypeNames := getSupportedDbTypeNames()
|
|
envConfigKeys := setting.CollectEnvConfigKeys()
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
base, baseCleanUp := context.NewBaseContext(resp, req)
|
|
defer baseCleanUp()
|
|
|
|
ctx := context.NewWebContext(base, rnd, session.GetSession(req))
|
|
ctx.AppendContextValue(context.WebContextKey, ctx)
|
|
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
|
ctx.Data.MergeFrom(middleware.ContextData{
|
|
"Context": ctx, // TODO: use "ctx" in template and remove this
|
|
"locale": ctx.Locale,
|
|
"Title": ctx.Locale.Tr("install.install"),
|
|
"PageIsInstall": true,
|
|
"DbTypeNames": dbTypeNames,
|
|
"EnvConfigKeys": envConfigKeys,
|
|
"CustomConfFile": setting.CustomConf,
|
|
"AllLangs": translation.AllLangs(),
|
|
|
|
"PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
|
|
})
|
|
next.ServeHTTP(resp, ctx.Req)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Install render installation page
|
|
func Install(ctx *context.Context) {
|
|
if setting.InstallLock {
|
|
InstallDone(ctx)
|
|
return
|
|
}
|
|
|
|
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.SSLMode = setting.Database.SSLMode
|
|
|
|
curDBType := setting.Database.Type.String()
|
|
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.Storage.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.Log.RootPath
|
|
|
|
// 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 = hash.ConfigHashAlgorithm(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.com/installation/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 re-installation 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) {
|
|
if setting.InstallLock {
|
|
InstallDone(ctx)
|
|
return
|
|
}
|
|
|
|
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() {
|
|
ctx.Data["Err_SMTP"] = ctx.Data["Err_SMTPUser"] != nil
|
|
ctx.Data["Err_Admin"] = ctx.Data["Err_AdminName"] != nil || ctx.Data["Err_AdminPasswd"] != nil || ctx.Data["Err_AdminEmail"] != nil
|
|
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 = setting.DatabaseType(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.Path = form.DbPath
|
|
setting.Database.LogSQL = !setting.IsProd
|
|
|
|
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
|
|
}
|
|
if len(form.AdminPasswd) < setting.MinPasswordLength {
|
|
ctx.Data["Err_Admin"] = true
|
|
ctx.Data["Err_AdminPasswd"] = true
|
|
ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), 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, err := setting.NewConfigProviderFromFile(setting.CustomConf)
|
|
if err != nil {
|
|
log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err)
|
|
}
|
|
|
|
cfg.Section("").Key("APP_NAME").SetValue(form.AppName)
|
|
cfg.Section("").Key("RUN_USER").SetValue(form.RunUser)
|
|
cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath)
|
|
cfg.Section("").Key("RUN_MODE").SetValue("prod")
|
|
|
|
cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type.String())
|
|
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("PATH").SetValue(setting.Database.Path)
|
|
cfg.Section("database").Key("LOG_SQL").SetValue("false") // LOG_SQL is rarely helpful
|
|
|
|
cfg.Section("repository").Key("ROOT").SetValue(form.RepoRootPath)
|
|
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)
|
|
cfg.Section("server").Key("APP_DATA_PATH").SetValue(setting.AppDataPath)
|
|
|
|
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 err := system_model.SetSettings(ctx, map[string]string{
|
|
setting.Config().Picture.DisableGravatar.DynKey(): strconv.FormatBool(form.DisableGravatar),
|
|
setting.Config().Picture.EnableFederatedAvatar.DynKey(): 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("session").Key("PROVIDER").SetValue("file")
|
|
|
|
cfg.Section("log").Key("MODE").MustString("console")
|
|
cfg.Section("log").Key("LEVEL").SetValue(setting.Log.Level.String())
|
|
cfg.Section("log").Key("ROOT_PATH").SetValue(form.LogRootPath)
|
|
|
|
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 {
|
|
var algorithm *hash.PasswordHashAlgorithm
|
|
setting.PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(form.PasswordAlgorithm)
|
|
if algorithm == nil {
|
|
ctx.RenderWithErr(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form)
|
|
return
|
|
}
|
|
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
|
|
}
|
|
|
|
setting.EnvironmentToConfig(cfg, os.Environ())
|
|
|
|
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)
|
|
setting.InitCfgProvider(setting.CustomConf)
|
|
setting.LoadCommonSettings()
|
|
setting.MustInstalled()
|
|
setting.LoadDBSetting()
|
|
if err := common.InitDBEngine(ctx); err != nil {
|
|
log.Fatal("ORM engine initialization failed: %v", err)
|
|
}
|
|
|
|
// 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(ctx, 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)
|
|
}
|
|
|
|
if err := ctx.SetLTACookie(u); err != nil {
|
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
|
return
|
|
}
|
|
|
|
// 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.Release(); err != nil {
|
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
|
return
|
|
}
|
|
}
|
|
|
|
setting.ClearEnvConfigKeys()
|
|
log.Info("First-time run install finished!")
|
|
InstallDone(ctx)
|
|
|
|
go func() {
|
|
// Sleep for a while to make sure the user's browser has loaded the post-install page and its assets (images, css, js)
|
|
// What if this duration is not long enough? That's impossible -- if the user can't load the simple page in time, how could they install or use Gitea in the future ....
|
|
time.Sleep(3 * time.Second)
|
|
|
|
// 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)
|
|
if err := srv.Shutdown(graceful.GetManager().HammerContext()); err != nil {
|
|
log.Error("Unable to shutdown the install server! Error: %v", err)
|
|
}
|
|
|
|
// After the HTTP server for "install" shuts down, the `runWeb()` will continue to run the "normal" server
|
|
}()
|
|
}
|
|
|
|
// InstallDone shows the "post-install" page, makes it easier to develop the page.
|
|
// The name is not called as "PostInstall" to avoid misinterpretation as a handler for "POST /install"
|
|
func InstallDone(ctx *context.Context) { //nolint
|
|
ctx.HTML(http.StatusOK, tplPostInstall)
|
|
}
|