2014-03-25 04:51:42 -04:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2021-06-08 19:33:54 -04:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2014-03-25 04:51:42 -04:00
2021-06-08 19:33:54 -04:00
package install
2014-03-25 04:51:42 -04:00
2014-03-28 07:26:22 -04:00
import (
2020-12-25 04:59:32 -05:00
"fmt"
2020-10-19 17:03:08 -04:00
"net/http"
2014-03-29 17:50:51 -04:00
"os"
2014-04-08 15:27:35 -04:00
"os/exec"
2015-02-05 05:12:37 -05:00
"path/filepath"
2022-10-16 19:29:26 -04:00
"strconv"
2014-03-29 17:50:51 -04:00
"strings"
2021-01-26 10:36:53 -05:00
"time"
2014-03-29 17:50:51 -04:00
2021-09-19 07:49:59 -04:00
"code.gitea.io/gitea/models/db"
2021-12-01 02:50:01 -05:00
db_install "code.gitea.io/gitea/models/db/install"
2021-10-29 04:23:10 -04:00
"code.gitea.io/gitea/models/migrations"
2022-10-16 19:29:26 -04:00
system_model "code.gitea.io/gitea/models/system"
2021-11-24 04:49:20 -05:00
user_model "code.gitea.io/gitea/models/user"
2023-02-19 02:35:20 -05:00
"code.gitea.io/gitea/modules/auth/password/hash"
2016-11-10 11:24:48 -05:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
2018-02-18 13:14:37 -05:00
"code.gitea.io/gitea/modules/generate"
2019-12-15 04:51:28 -05:00
"code.gitea.io/gitea/modules/graceful"
2016-11-10 11:24:48 -05:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
2021-01-26 10:36:53 -05:00
"code.gitea.io/gitea/modules/templates"
2021-06-01 15:12:50 -04:00
"code.gitea.io/gitea/modules/translation"
2016-11-10 11:24:48 -05:00
"code.gitea.io/gitea/modules/user"
2020-11-27 21:42:08 -05:00
"code.gitea.io/gitea/modules/util"
2021-01-26 10:36:53 -05:00
"code.gitea.io/gitea/modules/web"
2021-01-30 03:55:53 -05:00
"code.gitea.io/gitea/modules/web/middleware"
2021-04-06 15:44:05 -04:00
"code.gitea.io/gitea/services/forms"
2019-08-23 12:40:30 -04:00
2021-01-26 10:36:53 -05:00
"gitea.com/go-chi/session"
2019-08-23 12:40:30 -04:00
"gopkg.in/ini.v1"
2014-03-28 07:26:22 -04:00
)
2014-06-22 13:14:03 -04:00
const (
2016-11-17 22:03:03 -05:00
// tplInstall template for installation page
2020-10-19 17:03:08 -04:00
tplInstall base . TplName = "install"
tplPostInstall base . TplName = "post-install"
2014-06-22 13:14:03 -04:00
)
2022-04-01 04:00:26 -04:00
// 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 ] } )
2021-12-07 00:44:08 -05:00
}
2022-04-01 04:00:26 -04:00
return dbTypeNames
2021-12-07 00:44:08 -05:00
}
2023-05-04 02:36:34 -04:00
// Contexter prepare for rendering installation page
func Contexter ( ) func ( next http . Handler ) http . Handler {
2023-04-30 08:22:23 -04:00
rnd := templates . HTMLRenderer ( )
2022-04-01 04:00:26 -04:00
dbTypeNames := getSupportedDbTypeNames ( )
2022-08-28 05:43:25 -04:00
return func ( next http . Handler ) http . Handler {
return http . HandlerFunc ( func ( resp http . ResponseWriter , req * http . Request ) {
2023-05-20 21:50:53 -04:00
base , baseCleanUp := context . NewBaseContext ( resp , req )
2023-05-22 21:29:15 -04:00
ctx := & context . Context {
2023-05-20 21:50:53 -04:00
Base : base ,
2022-08-28 05:43:25 -04:00
Flash : & middleware . Flash { } ,
Render : rnd ,
Session : session . GetSession ( req ) ,
}
2023-05-20 21:50:53 -04:00
defer baseCleanUp ( )
2022-05-05 10:13:23 -04:00
2023-05-22 21:29:15 -04:00
ctx . AppendContextValue ( context . WebContextKey , ctx )
2023-05-04 02:36:34 -04:00
ctx . Data . MergeFrom ( middleware . CommonTemplateContextData ( ) )
ctx . Data . MergeFrom ( middleware . ContextData {
"locale" : ctx . Locale ,
"Title" : ctx . Locale . Tr ( "install.install" ) ,
"PageIsInstall" : true ,
"DbTypeNames" : dbTypeNames ,
"AllLangs" : translation . AllLangs ( ) ,
"PasswordHashAlgorithms" : hash . RecommendedHashAlgorithms ,
} )
2022-08-28 05:43:25 -04:00
next . ServeHTTP ( resp , ctx . Req )
} )
}
2015-02-01 12:41:03 -05:00
}
2014-03-29 17:50:51 -04:00
2016-11-17 22:03:03 -05:00
// Install render installation page
2016-03-11 11:56:52 -05:00
func Install ( ctx * context . Context ) {
2023-03-03 21:12:02 -05:00
if setting . InstallLock {
InstallDone ( ctx )
return
}
2021-04-06 15:44:05 -04:00
form := forms . InstallForm { }
2015-02-01 12:41:03 -05:00
2015-07-09 01:17:48 -04:00
// Database settings
2019-08-24 05:24:45 -04:00
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
2020-01-20 10:45:14 -05:00
form . DbSchema = setting . Database . Schema
2019-08-24 05:24:45 -04:00
form . Charset = setting . Database . Charset
2015-02-01 12:41:03 -05:00
2023-03-07 05:51:06 -05:00
curDBType := setting . Database . Type . String ( )
2021-12-07 00:44:08 -05:00
var isCurDBTypeSupported bool
for _ , dbType := range setting . SupportedDatabaseTypes {
if dbType == curDBType {
isCurDBTypeSupported = true
break
2015-09-12 15:31:36 -04:00
}
2015-07-09 01:17:48 -04:00
}
2021-12-07 00:44:08 -05:00
if ! isCurDBTypeSupported {
curDBType = "mysql"
}
ctx . Data [ "CurDbType" ] = curDBType
2020-11-16 02:33:41 -05:00
2015-07-09 01:17:48 -04:00
// Application general settings
form . AppName = setting . AppName
2015-02-01 12:41:03 -05:00
form . RepoRootPath = setting . RepoRootPath
2020-09-29 05:05:13 -04:00
form . LFSRootPath = setting . LFS . Path
2015-02-01 12:41:03 -05:00
2017-06-17 20:30:04 -04:00
// Note(unknown): it's hard for Windows users change a running user,
2015-02-01 12:41:03 -05:00
// so just use current one if config says default.
if setting . IsWindows && setting . RunUser == "git" {
2015-07-31 02:50:11 -04:00
form . RunUser = user . CurrentUsername ( )
2015-02-01 12:41:03 -05:00
} else {
form . RunUser = setting . RunUser
2014-04-10 14:37:43 -04:00
}
2014-03-29 17:50:51 -04:00
2015-02-01 12:41:03 -05:00
form . Domain = setting . Domain
2016-02-27 20:48:39 -05:00
form . SSHPort = setting . SSH . Port
2016-08-11 17:55:10 -04:00
form . HTTPPort = setting . HTTPPort
2016-11-27 01:03:59 -05:00
form . AppURL = setting . AppURL
2023-02-19 11:12:01 -05:00
form . LogRootPath = setting . Log . RootPath
2015-02-01 12:41:03 -05:00
2015-07-09 01:17:48 -04:00
// E-mail service settings
if setting . MailService != nil {
Rework mailer settings (#18982)
* `PROTOCOL`: can be smtp, smtps, smtp+startls, smtp+unix, sendmail, dummy
* `SMTP_ADDR`: domain for SMTP, or path to unix socket
* `SMTP_PORT`: port for SMTP; defaults to 25 for `smtp`, 465 for `smtps`, and 587 for `smtp+startls`
* `ENABLE_HELO`, `HELO_HOSTNAME`: reverse `DISABLE_HELO` to `ENABLE_HELO`; default to false + system hostname
* `FORCE_TRUST_SERVER_CERT`: replace the unclear `SKIP_VERIFY`
* `CLIENT_CERT_FILE`, `CLIENT_KEY_FILE`, `USE_CLIENT_CERT`: clarify client certificates here
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-08-02 01:24:18 -04:00
form . SMTPAddr = setting . MailService . SMTPAddr
form . SMTPPort = setting . MailService . SMTPPort
2015-07-09 04:10:31 -04:00
form . SMTPFrom = setting . MailService . From
2017-02-23 20:37:13 -05:00
form . SMTPUser = setting . MailService . User
2022-05-02 04:45:23 -04:00
form . SMTPPasswd = setting . MailService . Passwd
2014-04-27 00:34:48 -04:00
}
2015-07-09 01:17:48 -04:00
form . RegisterConfirm = setting . Service . RegisterEmailConfirm
form . MailNotify = setting . Service . EnableNotifyMail
// Server and other services settings
form . OfflineMode = setting . OfflineMode
2023-01-03 15:33:41 -05:00
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
2022-10-16 19:29:26 -04:00
2017-11-29 07:47:42 -05:00
form . EnableOpenIDSignIn = setting . Service . EnableOpenIDSignIn
form . EnableOpenIDSignUp = setting . Service . EnableOpenIDSignUp
2015-07-09 01:17:48 -04:00
form . DisableRegistration = setting . Service . DisableRegistration
2018-05-13 03:51:16 -04:00
form . AllowOnlyExternalRegistration = setting . Service . AllowOnlyExternalRegistration
2015-09-13 12:14:32 -04:00
form . EnableCaptcha = setting . Service . EnableCaptcha
2015-07-09 01:17:48 -04:00
form . RequireSignInView = setting . Service . RequireSignInView
2017-01-07 22:12:03 -05:00
form . DefaultKeepEmailPrivate = setting . Service . DefaultKeepEmailPrivate
2017-05-08 15:51:53 -04:00
form . DefaultAllowCreateOrganization = setting . Service . DefaultAllowCreateOrganization
2017-09-12 02:48:13 -04:00
form . DefaultEnableTimetracking = setting . Service . DefaultEnableTimetracking
2017-01-07 22:12:03 -05:00
form . NoReplyAddress = setting . Service . NoReplyAddress
2023-03-03 21:12:02 -05:00
form . PasswordAlgorithm = hash . ConfigHashAlgorithm ( setting . PasswordHashAlgo )
2014-04-27 00:34:48 -04:00
2021-01-30 03:55:53 -05:00
middleware . AssignForm ( form , ctx . Data )
2021-04-05 11:30:52 -04:00
ctx . HTML ( http . StatusOK , tplInstall )
2014-04-10 14:37:43 -04:00
}
2021-12-01 02:50:01 -05:00
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
}
2021-06-08 19:33:54 -04:00
// SubmitInstall response for submit install items
func SubmitInstall ( ctx * context . Context ) {
2023-03-03 21:12:02 -05:00
if setting . InstallLock {
InstallDone ( ctx )
return
}
2016-12-20 07:32:02 -05:00
var err error
2021-12-01 02:50:01 -05:00
form := * web . GetForm ( ctx ) . ( * forms . InstallForm )
// fix form values
if form . AppURL != "" && form . AppURL [ len ( form . AppURL ) - 1 ] != '/' {
form . AppURL += "/"
}
2021-12-07 00:44:08 -05:00
ctx . Data [ "CurDbType" ] = form . DbType
2014-04-27 00:34:48 -04:00
2014-03-29 17:50:51 -04:00
if ctx . HasError ( ) {
2023-05-20 21:50:53 -04:00
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
2021-04-05 11:30:52 -04:00
ctx . HTML ( http . StatusOK , tplInstall )
2014-03-29 17:50:51 -04:00
return
}
2016-12-20 07:32:02 -05:00
if _ , err = exec . LookPath ( "git" ) ; err != nil {
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.test_git_failed" , err ) , tplInstall , & form )
2014-04-08 15:27:35 -04:00
return
}
2021-12-01 02:50:01 -05:00
// ---- Basic checks are passed, now test configuration.
2019-08-24 05:24:45 -04:00
2021-12-01 02:50:01 -05:00
// Test database setting.
2023-03-07 05:51:06 -05:00
setting . Database . Type = setting . DatabaseType ( form . DbType )
2019-08-24 05:24:45 -04:00
setting . Database . Host = form . DbHost
setting . Database . User = form . DbUser
setting . Database . Passwd = form . DbPasswd
setting . Database . Name = form . DbName
2020-01-20 10:45:14 -05:00
setting . Database . Schema = form . DbSchema
2019-08-24 05:24:45 -04:00
setting . Database . SSLMode = form . SSLMode
setting . Database . Charset = form . Charset
setting . Database . Path = form . DbPath
2021-11-06 23:11:27 -04:00
setting . Database . LogSQL = ! setting . IsProd
2021-02-16 17:37:20 -05:00
2021-12-01 02:50:01 -05:00
if ! checkDatabase ( ctx , & form ) {
2015-09-12 15:31:36 -04:00
return
2015-07-08 07:47:56 -04:00
}
2021-12-01 02:50:01 -05:00
// 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 )
2014-03-29 17:50:51 -04:00
return
}
// Test repository root path.
2020-10-11 16:27:20 -04:00
form . RepoRootPath = strings . ReplaceAll ( form . RepoRootPath , "\\" , "/" )
2016-12-20 07:32:02 -05:00
if err = os . MkdirAll ( form . RepoRootPath , os . ModePerm ) ; err != nil {
2014-09-14 19:22:52 -04:00
ctx . Data [ "Err_RepoRootPath" ] = true
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.invalid_repo_path" , err ) , tplInstall , & form )
2014-03-29 17:50:51 -04:00
return
}
2016-12-25 20:16:37 -05:00
// Test LFS root path if not empty, empty meaning disable LFS
if form . LFSRootPath != "" {
2020-10-11 16:27:20 -04:00
form . LFSRootPath = strings . ReplaceAll ( form . LFSRootPath , "\\" , "/" )
2016-12-25 20:16:37 -05:00
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
}
}
2016-02-12 09:19:45 -05:00
// Test log root path.
2020-10-11 16:27:20 -04:00
form . LogRootPath = strings . ReplaceAll ( form . LogRootPath , "\\" , "/" )
2016-12-20 07:32:02 -05:00
if err = os . MkdirAll ( form . LogRootPath , os . ModePerm ) ; err != nil {
2016-02-12 09:19:45 -05:00
ctx . Data [ "Err_LogRootPath" ] = true
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.invalid_log_root_path" , err ) , tplInstall , & form )
2016-02-12 09:19:45 -05:00
return
}
2016-08-09 20:41:18 -04:00
currentUser , match := setting . IsRunUserMatchCurrentUser ( form . RunUser )
if ! match {
2014-09-14 19:22:52 -04:00
ctx . Data [ "Err_RunUser" ] = true
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.run_user_not_match" , form . RunUser , currentUser ) , tplInstall , & form )
2014-09-07 19:02:58 -04:00
return
}
2015-09-12 15:31:36 -04:00
// 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
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.no_admin_and_disable_registration" ) , tplInstall , form )
2015-09-12 15:31:36 -04:00
return
}
2019-05-28 02:18:40 -04:00
// Check admin user creation
if len ( form . AdminName ) > 0 {
// Ensure AdminName is valid
2021-11-24 04:49:20 -05:00
if err := user_model . IsUsableUsername ( form . AdminName ) ; err != nil {
2019-05-28 02:18:40 -04:00
ctx . Data [ "Err_Admin" ] = true
ctx . Data [ "Err_AdminName" ] = true
2021-11-24 04:49:20 -05:00
if db . IsErrNameReserved ( err ) {
2019-05-28 02:18:40 -04:00
ctx . RenderWithErr ( ctx . Tr ( "install.err_admin_name_is_reserved" ) , tplInstall , form )
return
2021-11-24 04:49:20 -05:00
} else if db . IsErrNamePatternNotAllowed ( err ) {
2019-05-28 02:18:40 -04:00
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
}
2014-03-30 11:58:21 -04:00
}
2021-12-01 02:50:01 -05:00
// 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
2015-02-01 12:41:03 -05:00
}
2014-03-29 17:50:51 -04:00
// Save settings.
2015-02-01 14:39:58 -05:00
cfg := ini . Empty ( )
2020-11-27 21:42:08 -05:00
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 {
2015-02-13 16:48:23 -05:00
// Keeps custom settings if there is already something.
2016-12-20 07:32:02 -05:00
if err = cfg . Append ( setting . CustomConf ) ; err != nil {
2019-04-02 03:48:31 -04:00
log . Error ( "Failed to load custom conf '%s': %v" , setting . CustomConf , err )
2015-02-13 16:48:23 -05:00
}
}
2023-03-07 05:51:06 -05:00
cfg . Section ( "database" ) . Key ( "DB_TYPE" ) . SetValue ( setting . Database . Type . String ( ) )
2019-08-24 05:24:45 -04:00
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 )
2020-01-20 10:45:14 -05:00
cfg . Section ( "database" ) . Key ( "SCHEMA" ) . SetValue ( setting . Database . Schema )
2019-08-24 05:24:45 -04:00
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 )
2020-10-10 11:19:50 -04:00
cfg . Section ( "database" ) . Key ( "LOG_SQL" ) . SetValue ( "false" ) // LOG_SQL is rarely helpful
2015-02-01 14:39:58 -05:00
2015-07-09 01:17:48 -04:00
cfg . Section ( "" ) . Key ( "APP_NAME" ) . SetValue ( form . AppName )
2015-02-01 14:39:58 -05:00
cfg . Section ( "repository" ) . Key ( "ROOT" ) . SetValue ( form . RepoRootPath )
cfg . Section ( "" ) . Key ( "RUN_USER" ) . SetValue ( form . RunUser )
2016-12-27 02:34:34 -05:00
cfg . Section ( "server" ) . Key ( "SSH_DOMAIN" ) . SetValue ( form . Domain )
2017-04-20 22:43:29 -04:00
cfg . Section ( "server" ) . Key ( "DOMAIN" ) . SetValue ( form . Domain )
2015-02-01 14:39:58 -05:00
cfg . Section ( "server" ) . Key ( "HTTP_PORT" ) . SetValue ( form . HTTPPort )
2016-11-27 01:03:59 -05:00
cfg . Section ( "server" ) . Key ( "ROOT_URL" ) . SetValue ( form . AppURL )
2014-03-29 17:50:51 -04:00
2015-08-19 08:36:19 -04:00
if form . SSHPort == 0 {
cfg . Section ( "server" ) . Key ( "DISABLE_SSH" ) . SetValue ( "true" )
} else {
cfg . Section ( "server" ) . Key ( "DISABLE_SSH" ) . SetValue ( "false" )
2020-12-25 04:59:32 -05:00
cfg . Section ( "server" ) . Key ( "SSH_PORT" ) . SetValue ( fmt . Sprint ( form . SSHPort ) )
2015-08-19 08:36:19 -04:00
}
2016-12-25 20:16:37 -05:00
if form . LFSRootPath != "" {
cfg . Section ( "server" ) . Key ( "LFS_START_SERVER" ) . SetValue ( "true" )
2022-01-23 14:02:29 -05:00
cfg . Section ( "lfs" ) . Key ( "PATH" ) . SetValue ( form . LFSRootPath )
2021-12-01 02:50:01 -05:00
var lfsJwtSecret string
if lfsJwtSecret , err = generate . NewJwtSecretBase64 ( ) ; err != nil {
2018-02-18 13:14:37 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.lfs_jwt_secret_failed" , err ) , tplInstall , & form )
return
}
2021-12-01 02:50:01 -05:00
cfg . Section ( "server" ) . Key ( "LFS_JWT_SECRET" ) . SetValue ( lfsJwtSecret )
2016-12-25 20:16:37 -05:00
} else {
cfg . Section ( "server" ) . Key ( "LFS_START_SERVER" ) . SetValue ( "false" )
}
Rework mailer settings (#18982)
* `PROTOCOL`: can be smtp, smtps, smtp+startls, smtp+unix, sendmail, dummy
* `SMTP_ADDR`: domain for SMTP, or path to unix socket
* `SMTP_PORT`: port for SMTP; defaults to 25 for `smtp`, 465 for `smtps`, and 587 for `smtp+startls`
* `ENABLE_HELO`, `HELO_HOSTNAME`: reverse `DISABLE_HELO` to `ENABLE_HELO`; default to false + system hostname
* `FORCE_TRUST_SERVER_CERT`: replace the unclear `SKIP_VERIFY`
* `CLIENT_CERT_FILE`, `CLIENT_KEY_FILE`, `USE_CLIENT_CERT`: clarify client certificates here
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-08-02 01:24:18 -04:00
if len ( strings . TrimSpace ( form . SMTPAddr ) ) > 0 {
2015-02-01 14:39:58 -05:00
cfg . Section ( "mailer" ) . Key ( "ENABLED" ) . SetValue ( "true" )
Rework mailer settings (#18982)
* `PROTOCOL`: can be smtp, smtps, smtp+startls, smtp+unix, sendmail, dummy
* `SMTP_ADDR`: domain for SMTP, or path to unix socket
* `SMTP_PORT`: port for SMTP; defaults to 25 for `smtp`, 465 for `smtps`, and 587 for `smtp+startls`
* `ENABLE_HELO`, `HELO_HOSTNAME`: reverse `DISABLE_HELO` to `ENABLE_HELO`; default to false + system hostname
* `FORCE_TRUST_SERVER_CERT`: replace the unclear `SKIP_VERIFY`
* `CLIENT_CERT_FILE`, `CLIENT_KEY_FILE`, `USE_CLIENT_CERT`: clarify client certificates here
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2022-08-02 01:24:18 -04:00
cfg . Section ( "mailer" ) . Key ( "SMTP_ADDR" ) . SetValue ( form . SMTPAddr )
cfg . Section ( "mailer" ) . Key ( "SMTP_PORT" ) . SetValue ( form . SMTPPort )
2015-07-09 04:10:31 -04:00
cfg . Section ( "mailer" ) . Key ( "FROM" ) . SetValue ( form . SMTPFrom )
2017-02-23 20:37:13 -05:00
cfg . Section ( "mailer" ) . Key ( "USER" ) . SetValue ( form . SMTPUser )
2015-02-01 14:39:58 -05:00
cfg . Section ( "mailer" ) . Key ( "PASSWD" ) . SetValue ( form . SMTPPasswd )
2015-07-09 01:17:48 -04:00
} else {
cfg . Section ( "mailer" ) . Key ( "ENABLED" ) . SetValue ( "false" )
2014-03-29 17:50:51 -04:00
}
2020-12-25 04:59:32 -05:00
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 ) )
2022-10-16 19:29:26 -04:00
// if you are reinstalling, this maybe not right because of missing version
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 08:37:34 -05:00
if err := system_model . SetSettingNoVersion ( ctx , system_model . KeyPictureDisableGravatar , strconv . FormatBool ( form . DisableGravatar ) ) ; err != nil {
2023-01-03 15:33:41 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
return
}
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 08:37:34 -05:00
if err := system_model . SetSettingNoVersion ( ctx , system_model . KeyPictureEnableFederatedAvatar , strconv . FormatBool ( form . EnableFederatedAvatar ) ) ; err != nil {
2023-01-03 15:33:41 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
2022-10-16 19:29:26 -04:00
return
}
2020-12-25 04:59:32 -05:00
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 ) )
2022-11-01 15:23:56 -04:00
cfg . Section ( "cron.update_checker" ) . Key ( "ENABLED" ) . SetValue ( fmt . Sprint ( form . EnableUpdateChecker ) )
2014-03-29 17:50:51 -04:00
2015-02-01 14:39:58 -05:00
cfg . Section ( "" ) . Key ( "RUN_MODE" ) . SetValue ( "prod" )
2014-03-30 11:58:21 -04:00
2015-02-01 14:39:58 -05:00
cfg . Section ( "session" ) . Key ( "PROVIDER" ) . SetValue ( "file" )
2014-12-20 22:51:16 -05:00
2020-10-10 11:19:50 -04:00
cfg . Section ( "log" ) . Key ( "MODE" ) . SetValue ( "console" )
2023-02-19 11:12:01 -05:00
cfg . Section ( "log" ) . Key ( "LEVEL" ) . SetValue ( setting . Log . Level . String ( ) )
2016-02-12 09:19:45 -05:00
cfg . Section ( "log" ) . Key ( "ROOT_PATH" ) . SetValue ( form . LogRootPath )
2020-10-10 11:19:50 -04:00
cfg . Section ( "log" ) . Key ( "ROUTER" ) . SetValue ( "console" )
2014-08-27 04:39:36 -04:00
2022-06-02 23:45:54 -04:00
cfg . Section ( "repository.pull-request" ) . Key ( "DEFAULT_MERGE_STYLE" ) . SetValue ( "merge" )
2022-01-19 21:41:59 -05:00
cfg . Section ( "repository.signing" ) . Key ( "DEFAULT_TRUST_MODEL" ) . SetValue ( "committer" )
2015-02-01 14:39:58 -05:00
cfg . Section ( "security" ) . Key ( "INSTALL_LOCK" ) . SetValue ( "true" )
2021-12-01 02:50:01 -05:00
2022-11-03 16:55:09 -04:00
// 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 )
2016-12-20 07:32:02 -05:00
}
2021-12-01 02:50:01 -05:00
// 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 )
}
2021-02-16 17:37:20 -05:00
if len ( form . PasswordAlgorithm ) > 0 {
2023-03-03 21:12:02 -05:00
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
}
2021-02-16 17:37:20 -05:00
cfg . Section ( "security" ) . Key ( "PASSWORD_HASH_ALGO" ) . SetValue ( form . PasswordAlgorithm )
}
2014-03-29 17:50:51 -04:00
2021-12-01 02:50:01 -05:00
log . Info ( "Save settings to custom config file %s" , setting . CustomConf )
2016-12-20 07:32:02 -05:00
err = os . MkdirAll ( filepath . Dir ( setting . CustomConf ) , os . ModePerm )
2016-11-10 05:02:01 -05:00
if err != nil {
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
2016-11-10 05:02:01 -05:00
return
}
2016-12-20 07:32:02 -05:00
if err = cfg . SaveTo ( setting . CustomConf ) ; err != nil {
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
2014-03-29 17:50:51 -04:00
return
}
2022-10-18 11:16:58 -04:00
// unset default engine before reload database setting
db . UnsetDefaultEngine ( )
2021-12-01 02:50:01 -05:00
// ---- All checks are passed
// Reload settings (and re-initialize database connection)
reloadSettings ( ctx )
2014-03-29 17:50:51 -04:00
2015-12-08 00:59:14 -05:00
// Create admin account
2015-07-08 07:47:56 -04:00
if len ( form . AdminName ) > 0 {
2021-11-24 04:49:20 -05:00
u := & user_model . User {
2022-04-29 15:38:11 -04:00
Name : form . AdminName ,
Email : form . AdminEmail ,
Passwd : form . AdminPasswd ,
IsAdmin : true ,
2015-12-08 00:59:14 -05:00
}
2022-04-29 15:38:11 -04:00
overwriteDefault := & user_model . CreateUserOverwriteOptions {
IsRestricted : util . OptionalBoolFalse ,
IsActive : util . OptionalBoolTrue ,
}
if err = user_model . CreateUser ( u , overwriteDefault ) ; err != nil {
2021-11-24 04:49:20 -05:00
if ! user_model . IsErrUserAlreadyExist ( err ) {
2015-07-08 07:47:56 -04:00
setting . InstallLock = false
ctx . Data [ "Err_AdminName" ] = true
ctx . Data [ "Err_AdminEmail" ] = true
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.invalid_admin_setting" , err ) , tplInstall , & form )
2015-07-08 07:47:56 -04:00
return
}
log . Info ( "Admin account already exist" )
2022-05-20 10:08:52 -04:00
u , _ = user_model . GetUserByName ( ctx , u . Name )
2014-03-30 11:09:59 -04:00
}
2015-12-08 00:59:14 -05:00
2020-10-19 17:03:08 -04:00
days := 86400 * setting . LogInRememberDays
2023-04-13 15:45:33 -04:00
ctx . SetSiteCookie ( setting . CookieUserName , u . Name , days )
2021-03-07 03:12:43 -05:00
2020-10-19 17:03:08 -04:00
ctx . SetSuperSecureCookie ( base . EncodeMD5 ( u . Rands + u . Passwd ) ,
2021-03-07 03:12:43 -05:00
setting . CookieRememberName , u . Name , days )
2020-10-19 17:03:08 -04:00
2015-12-08 00:59:14 -05:00
// Auto-login for admin
2016-12-20 07:32:02 -05:00
if err = ctx . Session . Set ( "uid" , u . ID ) ; err != nil {
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
2016-11-10 05:02:01 -05:00
return
}
2016-12-20 07:32:02 -05:00
if err = ctx . Session . Set ( "uname" , u . Name ) ; err != nil {
2016-11-17 22:03:03 -05:00
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
2016-11-10 05:02:01 -05:00
return
}
2020-05-17 08:43:29 -04:00
if err = ctx . Session . Release ( ) ; err != nil {
ctx . RenderWithErr ( ctx . Tr ( "install.save_config_failed" , err ) , tplInstall , & form )
return
}
2014-03-30 11:09:59 -04:00
}
2014-03-29 17:50:51 -04:00
log . Info ( "First-time run install finished!" )
2023-03-03 21:12:02 -05:00
InstallDone ( ctx )
2020-10-19 17:03:08 -04:00
go func ( ) {
2023-03-03 21:12:02 -05:00
// 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 )
2020-10-19 17:03:08 -04:00
if err := srv . Shutdown ( graceful . GetManager ( ) . HammerContext ( ) ) ; err != nil {
log . Error ( "Unable to shutdown the install server! Error: %v" , err )
}
2023-03-03 21:12:02 -05:00
// After the HTTP server for "install" shuts down, the `runWeb()` will continue to run the "normal" server
2020-10-19 17:03:08 -04:00
} ( )
2014-03-25 04:51:42 -04:00
}
2023-03-03 21:12:02 -05:00
// 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 )
}