Skip to content

Commit 042cac5

Browse files
authored
Improve install code to avoid low-level mistakes. (#17779)
* 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).
1 parent a3517d8 commit 042cac5

36 files changed

+472
-177
lines changed

cmd/cmd.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"syscall"
1717

1818
"code.gitea.io/gitea/models/db"
19+
"code.gitea.io/gitea/modules/log"
1920
"code.gitea.io/gitea/modules/setting"
2021
"code.gitea.io/gitea/modules/util"
2122

@@ -57,15 +58,17 @@ func confirm() (bool, error) {
5758
}
5859

5960
func initDB(ctx context.Context) error {
60-
return initDBDisableConsole(ctx, false)
61-
}
62-
63-
func initDBDisableConsole(ctx context.Context, disableConsole bool) error {
64-
setting.NewContext()
61+
setting.LoadFromExisting()
6562
setting.InitDBConfig()
66-
setting.NewXORMLogService(disableConsole)
63+
setting.NewXORMLogService(false)
64+
65+
if setting.Database.Type == "" {
66+
log.Fatal(`Database settings are missing from the configuration file: %q.
67+
Ensure you are running in the correct environment or set the correct configuration file with -c.
68+
If this is the intended configuration file complete the [database] section.`, setting.CustomConf)
69+
}
6770
if err := db.InitEngine(ctx); err != nil {
68-
return fmt.Errorf("models.SetEngine: %v", err)
71+
return fmt.Errorf("unable to initialise the database using the configuration in %q. Error: %v", setting.CustomConf, err)
6972
}
7073
return nil
7174
}

cmd/convert.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ func runConvert(ctx *cli.Context) error {
3535
log.Info("Custom path: %s", setting.CustomPath)
3636
log.Info("Log path: %s", setting.LogRootPath)
3737
log.Info("Configuration file: %s", setting.CustomConf)
38-
setting.InitDBConfig()
3938

4039
if !setting.Database.UseMySQL {
4140
fmt.Println("This command can only be used with a MySQL database")

cmd/doctor.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func runRecreateTable(ctx *cli.Context) error {
8787
golog.SetPrefix("")
8888
golog.SetOutput(log.NewLoggerAsWriter("INFO", log.GetLogger(log.DEFAULT)))
8989

90-
setting.NewContext()
90+
setting.LoadFromExisting()
9191
setting.InitDBConfig()
9292

9393
setting.EnableXORMLog = ctx.Bool("debug")

cmd/dump.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,8 @@ func runDump(ctx *cli.Context) error {
159159
fatal("Deleting default logger failed. Can not write to stdout: %v", err)
160160
}
161161
}
162-
setting.NewContext()
162+
setting.LoadFromExisting()
163+
163164
// make sure we are logging to the console no matter what the configuration tells us do to
164165
if _, err := setting.Cfg.Section("log").NewKey("MODE", "console"); err != nil {
165166
fatal("Setting logging mode to console failed: %v", err)

cmd/dump_repo.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ func runDumpRepository(ctx *cli.Context) error {
8888
log.Info("Custom path: %s", setting.CustomPath)
8989
log.Info("Log path: %s", setting.LogRootPath)
9090
log.Info("Configuration file: %s", setting.CustomConf)
91-
setting.InitDBConfig()
9291

9392
var (
9493
serviceType structs.GitServiceType

cmd/embedded.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func initEmbeddedExtractor(c *cli.Context) error {
115115
log.DelNamedLogger(log.DEFAULT)
116116

117117
// Read configuration file
118-
setting.NewContext()
118+
setting.LoadAllowEmpty()
119119

120120
pats, err := getPatterns(c.Args())
121121
if err != nil {

cmd/mailer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ func runSendMail(c *cli.Context) error {
1818
ctx, cancel := installSignals()
1919
defer cancel()
2020

21-
setting.NewContext()
21+
setting.LoadFromExisting()
2222

2323
if err := argsSet(c, "title"); err != nil {
2424
return err

cmd/migrate.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ func runMigrate(ctx *cli.Context) error {
3636
log.Info("Custom path: %s", setting.CustomPath)
3737
log.Info("Log path: %s", setting.LogRootPath)
3838
log.Info("Configuration file: %s", setting.CustomConf)
39-
setting.InitDBConfig()
4039

4140
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
4241
log.Fatal("Failed to initialize ORM engine: %v", err)

cmd/migrate_storage.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ func runMigrateStorage(ctx *cli.Context) error {
121121
log.Info("Custom path: %s", setting.CustomPath)
122122
log.Info("Log path: %s", setting.LogRootPath)
123123
log.Info("Configuration file: %s", setting.CustomConf)
124-
setting.InitDBConfig()
125124

126125
if err := db.InitEngineWithMigration(context.Background(), migrations.Migrate); err != nil {
127126
log.Fatal("Failed to initialize ORM engine: %v", err)

cmd/restore_repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func runRestoreRepository(c *cli.Context) error {
5050
ctx, cancel := installSignals()
5151
defer cancel()
5252

53-
setting.NewContext()
53+
setting.LoadFromExisting()
5454

5555
statusCode, errStr := private.RestoreRepo(
5656
ctx,

cmd/serv.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func setup(logPath string, debug bool) {
5858
} else {
5959
_ = log.NewLogger(1000, "console", "console", `{"level":"fatal","stacktracelevel":"NONE","stderr":true}`)
6060
}
61-
setting.NewContext()
61+
setting.LoadFromExisting()
6262
if debug {
6363
setting.RunMode = "dev"
6464
}

cmd/web.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ func runWeb(ctx *cli.Context) error {
124124
}
125125
c := install.Routes()
126126
err := listen(c, false)
127+
if err != nil {
128+
log.Critical("Unable to open listener for installer. Is Gitea already running?")
129+
graceful.GetManager().DoGracefulShutdown()
130+
}
127131
select {
128132
case <-graceful.GetManager().IsShutdown():
129133
<-graceful.GetManager().Done()
@@ -145,7 +149,15 @@ func runWeb(ctx *cli.Context) error {
145149

146150
log.Info("Global init")
147151
// Perform global initialization
148-
routers.GlobalInit(graceful.GetManager().HammerContext())
152+
setting.LoadFromExisting()
153+
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
154+
155+
// We check that AppDataPath exists here (it should have been created during installation)
156+
// We can't check it in `GlobalInitInstalled`, because some integration tests
157+
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.
158+
if _, err := os.Stat(setting.AppDataPath); err != nil {
159+
log.Fatal("Can not find APP_DATA_PATH '%s'", setting.AppDataPath)
160+
}
149161

150162
// Override the provided port number within the configuration
151163
if ctx.IsSet("port") {

contrib/environment-to-ini/environment-to-ini.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ func runEnvironmentToIni(c *cli.Context) error {
156156
destination = setting.CustomConf
157157
}
158158
if destination != setting.CustomConf || changed {
159+
log.Info("Settings saved to: %q", destination)
159160
err = cfg.SaveTo(destination)
160161
if err != nil {
161162
return err

contrib/pr/checkout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func runPR() {
4949
log.Fatal(err)
5050
}
5151
setting.SetCustomPathAndConf("", "", "")
52-
setting.NewContext()
52+
setting.LoadAllowEmpty()
5353

5454
setting.RepoRootPath, err = os.MkdirTemp(os.TempDir(), "repos")
5555
if err != nil {

integrations/integration_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ func initIntegrationTest() {
164164
}
165165

166166
setting.SetCustomPathAndConf("", "", "")
167-
setting.NewContext()
168-
util.RemoveAll(models.LocalCopyPath())
167+
setting.LoadForTest()
168+
_ = util.RemoveAll(models.LocalCopyPath())
169169
git.CheckLFSVersion()
170170
setting.InitDBConfig()
171171
if err := storage.Init(); err != nil {
@@ -240,7 +240,8 @@ func initIntegrationTest() {
240240
}
241241
defer db.Close()
242242
}
243-
routers.GlobalInit(graceful.GetManager().HammerContext())
243+
244+
routers.GlobalInitInstalled(graceful.GetManager().HammerContext())
244245
}
245246

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

256257
assert.NoError(t, util.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"), setting.RepoRootPath))
258+
257259
return deferFn
258260
}
259261

integrations/migration-test/migration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func initMigrationTest(t *testing.T) func() {
5656
setting.CustomConf = giteaConf
5757
}
5858

59-
setting.NewContext()
59+
setting.LoadForTest()
6060

6161
assert.True(t, len(setting.RepoRootPath) != 0)
6262
assert.NoError(t, util.RemoveAll(setting.RepoRootPath))

models/db/engine.go

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ package db
88
import (
99
"context"
1010
"database/sql"
11-
"errors"
1211
"fmt"
1312
"io"
1413
"reflect"
@@ -92,8 +91,8 @@ func init() {
9291
}
9392
}
9493

95-
// NewEngine returns a new xorm engine from the configuration
96-
func NewEngine() (*xorm.Engine, error) {
94+
// newXORMEngine returns a new XORM engine from the configuration
95+
func newXORMEngine() (*xorm.Engine, error) {
9796
connStr, err := setting.DBConnStr()
9897
if err != nil {
9998
return nil, err
@@ -126,40 +125,49 @@ func SyncAllTables() error {
126125
return x.StoreEngine("InnoDB").Sync2(tables...)
127126
}
128127

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

136-
x.SetMapper(names.GonicMapper{})
135+
xormEngine.SetMapper(names.GonicMapper{})
137136
// WARNING: for serv command, MUST remove the output to os.stdout,
138137
// so use log file to instead print to stdout.
139-
x.SetLogger(NewXORMLogger(setting.Database.LogSQL))
140-
x.ShowSQL(setting.Database.LogSQL)
141-
x.SetMaxOpenConns(setting.Database.MaxOpenConns)
142-
x.SetMaxIdleConns(setting.Database.MaxIdleConns)
143-
x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
138+
xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL))
139+
xormEngine.ShowSQL(setting.Database.LogSQL)
140+
xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns)
141+
xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns)
142+
xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
143+
xormEngine.SetDefaultContext(ctx)
144+
145+
SetDefaultEngine(ctx, xormEngine)
146+
return nil
147+
}
144148

149+
// SetDefaultEngine sets the default engine for db
150+
func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) {
151+
x = eng
145152
DefaultContext = &Context{
146153
Context: ctx,
147154
e: x,
148155
}
149-
x.SetDefaultContext(ctx)
150-
return nil
151156
}
152157

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

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

229-
// Ping tests if database is alive
230-
func Ping() error {
231-
if x != nil {
232-
return x.Ping()
233-
}
234-
return errors.New("database not configured")
235-
}
236-
237237
// DumpDatabase dumps all data from database according the special database SQL syntax to file system.
238238
func DumpDatabase(filePath, dbType string) error {
239239
var tbs []*schemas.Table
@@ -291,11 +291,3 @@ func GetMaxID(beanOrTableName interface{}) (maxID int64, err error) {
291291
_, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID)
292292
return
293293
}
294-
295-
// FindByMaxID filled results as the condition from database
296-
func FindByMaxID(maxID int64, limit int, results interface{}) error {
297-
return x.Where("id <= ?", maxID).
298-
OrderBy("id DESC").
299-
Limit(limit).
300-
Find(results)
301-
}

models/db/install/db.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2021 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package install
6+
7+
import (
8+
"code.gitea.io/gitea/models/db"
9+
"code.gitea.io/gitea/modules/setting"
10+
11+
"xorm.io/xorm"
12+
)
13+
14+
func getXORMEngine() *xorm.Engine {
15+
return db.DefaultContext.(*db.Context).Engine().(*xorm.Engine)
16+
}
17+
18+
// CheckDatabaseConnection checks the database connection
19+
func CheckDatabaseConnection() error {
20+
e := db.GetEngine(db.DefaultContext)
21+
_, err := e.Exec("SELECT 1")
22+
return err
23+
}
24+
25+
// GetMigrationVersion gets the database migration version
26+
func GetMigrationVersion() (int64, error) {
27+
var installedDbVersion int64
28+
x := getXORMEngine()
29+
exist, err := x.IsTableExist("version")
30+
if err != nil {
31+
return 0, err
32+
}
33+
if !exist {
34+
return 0, nil
35+
}
36+
_, err = x.Table("version").Cols("version").Get(&installedDbVersion)
37+
if err != nil {
38+
return 0, err
39+
}
40+
return installedDbVersion, nil
41+
}
42+
43+
// HasPostInstallationUsers checks whether there are users after installation
44+
func HasPostInstallationUsers() (bool, error) {
45+
x := getXORMEngine()
46+
exist, err := x.IsTableExist("user")
47+
if err != nil {
48+
return false, err
49+
}
50+
if !exist {
51+
return false, nil
52+
}
53+
54+
// if there are 2 or more users in database, we consider there are users created after installation
55+
threshold := 2
56+
if !setting.IsProd {
57+
// to debug easily, with non-prod RUN_MODE, we only check the count to 1
58+
threshold = 1
59+
}
60+
res, err := x.Table("user").Cols("id").Limit(threshold).Query()
61+
if err != nil {
62+
return false, err
63+
}
64+
return len(res) >= threshold, nil
65+
}

0 commit comments

Comments
 (0)