Skip to content

Commit

Permalink
Improve install code to avoid low-level mistakes. (#17779)
Browse files Browse the repository at this point in the history
* Improve install code to avoid low-level mistakes.

If a user tries to do a re-install in a Gitea database, they gets a warning and double check.
When Gitea runs, it never create empty app.ini automatically.

Also some small (related) refactoring:

* Refactor db.InitEngine related logic make it more clean (especially for the install code)
* Move some i18n strings out from setting.go to make the setting.go can be easily maintained.
* Show errors in CLI code if an incorrect app.ini is used.
* APP_DATA_PATH is created when installing, and checked when starting (no empty directory is created any more).
  • Loading branch information
wxiaoguang authored Dec 1, 2021
1 parent a3517d8 commit 042cac5
Show file tree
Hide file tree
Showing 36 changed files with 472 additions and 177 deletions.
17 changes: 10 additions & 7 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"syscall"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"

Expand Down Expand Up @@ -57,15 +58,17 @@ func confirm() (bool, error) {
}

func initDB(ctx context.Context) error {
return initDBDisableConsole(ctx, false)
}

func initDBDisableConsole(ctx context.Context, disableConsole bool) error {
setting.NewContext()
setting.LoadFromExisting()
setting.InitDBConfig()
setting.NewXORMLogService(disableConsole)
setting.NewXORMLogService(false)

if setting.Database.Type == "" {
log.Fatal(`Database settings are missing from the configuration file: %q.
Ensure you are running in the correct environment or set the correct configuration file with -c.
If this is the intended configuration file complete the [database] section.`, setting.CustomConf)
}
if err := db.InitEngine(ctx); err != nil {
return fmt.Errorf("models.SetEngine: %v", err)
return fmt.Errorf("unable to initialise the database using the configuration in %q. Error: %v", setting.CustomConf, err)
}
return nil
}
Expand Down
1 change: 0 additions & 1 deletion cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func runConvert(ctx *cli.Context) error {
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf)
setting.InitDBConfig()

if !setting.Database.UseMySQL {
fmt.Println("This command can only be used with a MySQL database")
Expand Down
2 changes: 1 addition & 1 deletion cmd/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func runRecreateTable(ctx *cli.Context) error {
golog.SetPrefix("")
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))

setting.NewContext()
setting.LoadFromExisting()
setting.InitDBConfig()

setting.EnableXORMLog = ctx.Bool("debug")
Expand Down
3 changes: 2 additions & 1 deletion cmd/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,8 @@ func runDump(ctx *cli.Context) error {
fatal("Deleting default logger failed. Can not write to stdout: %v", err)
}
}
setting.NewContext()
setting.LoadFromExisting()

// make sure we are logging to the console no matter what the configuration tells us do to
if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
fatal("Setting logging mode to console failed: %v", err)
Expand Down
1 change: 0 additions & 1 deletion cmd/dump_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ func runDumpRepository(ctx *cli.Context) error {
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf)
setting.InitDBConfig()

var (
serviceType structs.GitServiceType
Expand Down
2 changes: 1 addition & 1 deletion cmd/embedded.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func initEmbeddedExtractor(c *cli.Context) error {
log.DelNamedLogger(log.DEFAULT)

// Read configuration file
setting.NewContext()
setting.LoadAllowEmpty()

pats, err := getPatterns(c.Args())
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/mailer.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ func runSendMail(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()

setting.NewContext()
setting.LoadFromExisting()

if err := argsSet(c, "title"); err != nil {
return err
Expand Down
1 change: 0 additions & 1 deletion cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func runMigrate(ctx *cli.Context) error {
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf)
setting.InitDBConfig()

if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err)
Expand Down
1 change: 0 additions & 1 deletion cmd/migrate_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ func runMigrateStorage(ctx *cli.Context) error {
log.Info("Custom path: %s", setting.CustomPath)
log.Info("Log path: %s", setting.LogRootPath)
log.Info("Configuration file: %s", setting.CustomConf)
setting.InitDBConfig()

if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
log.Fatal("Failed to initialize ORM engine: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion cmd/restore_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func runRestoreRepository(c *cli.Context) error {
ctx, cancel := installSignals()
defer cancel()

setting.NewContext()
setting.LoadFromExisting()

statusCode, errStr := private.RestoreRepo(
ctx,
Expand Down
2 changes: 1 addition & 1 deletion cmd/serv.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func setup(logPath string, debug bool) {
} else {
_ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`)
}
setting.NewContext()
setting.LoadFromExisting()
if debug {
setting.RunMode = "dev"
}
Expand Down
14 changes: 13 additions & 1 deletion cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ func runWeb(ctx *cli.Context) error {
}
c := install.Routes()
err := listen(c, false)
if err != nil {
log.Critical("Unable to open listener for installer. Is Gitea already running?")
graceful.GetManager().DoGracefulShutdown()
}
select {
case <-graceful.GetManager().IsShutdown():
<-graceful.GetManager().Done()
Expand All @@ -145,7 +149,15 @@ func runWeb(ctx *cli.Context) error {

log.Info("Global init")
// Perform global initialization
routers.GlobalInit(graceful.GetManager().HammerContext())
setting.LoadFromExisting()
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())

// We check that AppDataPath exists here (it should have been created during installation)
// We can't check it in `GlobalInitInstalled`, because some integration tests
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.
if _, err := os.Stat(setting.AppDataPath); err != nil {
log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath)
}

// Override the provided port number within the configuration
if ctx.IsSet("port") {
Expand Down
1 change: 1 addition & 0 deletions contrib/environment-to-ini/environment-to-ini.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func runEnvironmentToIni(c *cli.Context) error {
destination = setting.CustomConf
}
if destination != setting.CustomConf || changed {
log.Info("Settings saved to: %q", destination)
err = cfg.SaveTo(destination)
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion contrib/pr/checkout.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func runPR() {
log.Fatal(err)
}
setting.SetCustomPathAndConf("", "", "")
setting.NewContext()
setting.LoadAllowEmpty()

setting.RepoRootPath, err = os.MkdirTemp(os.TempDir(), "repos")
if err != nil {
Expand Down
8 changes: 5 additions & 3 deletions integrations/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ func initIntegrationTest() {
}

setting.SetCustomPathAndConf("", "", "")
setting.NewContext()
util.RemoveAll(models.LocalCopyPath())
setting.LoadForTest()
_ = util.RemoveAll(models.LocalCopyPath())
git.CheckLFSVersion()
setting.InitDBConfig()
if err := storage.Init(); err != nil {
Expand Down Expand Up @@ -240,7 +240,8 @@ func initIntegrationTest() {
}
defer db.Close()
}
routers.GlobalInit(graceful.GetManager().HammerContext())

routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
}

func prepareTestEnv(t testing.TB, skip ...int) func() {
Expand All @@ -254,6 +255,7 @@ func prepareTestEnv(t testing.TB, skip ...int) func() {
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))

assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))

return deferFn
}

Expand Down
2 changes: 1 addition & 1 deletion integrations/migration-test/migration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func initMigrationTest(t *testing.T) func() {
setting.CustomConf = giteaConf
}

setting.NewContext()
setting.LoadForTest()

assert.True(t, len(setting.RepoRootPath) != 0)
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
Expand Down
68 changes: 30 additions & 38 deletions models/db/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package db
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"reflect"
Expand Down Expand Up @@ -92,8 +91,8 @@ func init() {
}
}

// NewEngine returns a new xorm engine from the configuration
func NewEngine() (*xorm.Engine, error) {
// newXORMEngine returns a new XORM engine from the configuration
func newXORMEngine() (*xorm.Engine, error) {
connStr, err := setting.DBConnStr()
if err != nil {
return nil, err
Expand Down Expand Up @@ -126,40 +125,49 @@ func SyncAllTables() error {
return x.StoreEngine("InnoDB").Sync2(tables...)
}

// InitEngine sets the xorm.Engine
func InitEngine(ctx context.Context) (err error) {
x, err = NewEngine()
// InitEngine initializes the xorm.Engine and sets it as db.DefaultContext
func InitEngine(ctx context.Context) error {
xormEngine, err := newXORMEngine()
if err != nil {
return fmt.Errorf("Failed to connect to database: %v", err)
return fmt.Errorf("failed to connect to database: %v", err)
}

x.SetMapper(names.GonicMapper{})
xormEngine.SetMapper(names.GonicMapper{})
// WARNING: for serv command, MUST remove the output to os.stdout,
// so use log file to instead print to stdout.
x.SetLogger(NewXORMLogger(setting.Database.LogSQL))
x.ShowSQL(setting.Database.LogSQL)
x.SetMaxOpenConns(setting.Database.MaxOpenConns)
x.SetMaxIdleConns(setting.Database.MaxIdleConns)
x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL))
xormEngine.ShowSQL(setting.Database.LogSQL)
xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
xormEngine.SetDefaultContext(ctx)

SetDefaultEngine(ctx, xormEngine)
return nil
}

// SetDefaultEngine sets the default engine for db
func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
x = eng
DefaultContext = &Context{
Context: ctx,
e: x,
}
x.SetDefaultContext(ctx)
return nil
}

// SetEngine is used by unit test code
func SetEngine(eng *xorm.Engine) {
x = eng
DefaultContext = &Context{
Context: context.Background(),
e: x,
// UnsetDefaultEngine closes and unsets the default engine
// We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now,
// there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close
// Global database engine related functions are all racy and there is no graceful close right now.
func UnsetDefaultEngine() {
if x != nil {
_ = x.Close()
x = nil
}
DefaultContext = nil
}

// InitEngineWithMigration initializes a new xorm.Engine
// InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext
// This function must never call .Sync2() if the provided migration function fails.
// When called from the "doctor" command, the migration function is a version check
// that prevents the doctor from fixing anything in the database if the migration level
Expand Down Expand Up @@ -226,14 +234,6 @@ func NamesToBean(names ...string) ([]interface{}, error) {
return beans, nil
}

// Ping tests if database is alive
func Ping() error {
if x != nil {
return x.Ping()
}
return errors.New("database not configured")
}

// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
func DumpDatabase(filePath, dbType string) error {
var tbs []*schemas.Table
Expand Down Expand Up @@ -291,11 +291,3 @@ func GetMaxID(beanOrTableName interface{}) (maxID int64, err error) {
_, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID)
return
}

// FindByMaxID filled results as the condition from database
func FindByMaxID(maxID int64, limit int, results interface{}) error {
return x.Where("id <= ?", maxID).
OrderBy("id DESC").
Limit(limit).
Find(results)
}
65 changes: 65 additions & 0 deletions models/db/install/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package install

import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"

"xorm.io/xorm"
)

func getXORMEngine() *xorm.Engine {
return db.DefaultContext.(*db.Context).Engine().(*xorm.Engine)
}

// CheckDatabaseConnection checks the database connection
func CheckDatabaseConnection() error {
e := db.GetEngine(db.DefaultContext)
_, err := e.Exec("SELECT 1")
return err
}

// GetMigrationVersion gets the database migration version
func GetMigrationVersion() (int64, error) {
var installedDbVersion int64
x := getXORMEngine()
exist, err := x.IsTableExist("version")
if err != nil {
return 0, err
}
if !exist {
return 0, nil
}
_, err = x.Table("version").Cols("version").Get(&installedDbVersion)
if err != nil {
return 0, err
}
return installedDbVersion, nil
}

// HasPostInstallationUsers checks whether there are users after installation
func HasPostInstallationUsers() (bool, error) {
x := getXORMEngine()
exist, err := x.IsTableExist("user")
if err != nil {
return false, err
}
if !exist {
return false, nil
}

// if there are 2 or more users in database, we consider there are users created after installation
threshold := 2
if !setting.IsProd {
// to debug easily, with non-prod RUN_MODE, we only check the count to 1
threshold = 1
}
res, err := x.Table("user").Cols("id").Limit(threshold).Query()
if err != nil {
return false, err
}
return len(res) >= threshold, nil
}
Loading

0 comments on commit 042cac5

Please sign in to comment.