diff --git a/routers/install/install.go b/routers/install/install.go index 6bf9ae2b5a7..3819c7d92a2 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -189,7 +189,7 @@ func SubmitInstall(ctx *context.Context) { var err error - form := *web.GetForm[*forms.InstallForm](ctx) + form := web.GetForm[*forms.InstallForm](ctx) // fix form values if form.AppURL != "" && form.AppURL[len(form.AppURL)-1] != '/' { @@ -206,7 +206,7 @@ func SubmitInstall(ctx *context.Context) { } if _, err = exec.LookPath("git"); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.test_git_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.test_git_failed", err), tplInstall, form) return } @@ -223,13 +223,13 @@ func SubmitInstall(ctx *context.Context) { setting.Database.Path = form.DbPath setting.Database.LogSQL = !setting.IsProd - if !checkDatabase(ctx, &form) { + if !checkDatabase(ctx, form) { return } // Prepare AppDataPath, it is very important for Gitea if err = setting.PrepareAppDataPath(); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_app_data_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_app_data_path", err), tplInstall, form) return } @@ -237,7 +237,7 @@ func SubmitInstall(ctx *context.Context) { form.RepoRootPath = strings.ReplaceAll(form.RepoRootPath, "\\", "/") if err = os.MkdirAll(form.RepoRootPath, os.ModePerm); err != nil { ctx.Data["Err_RepoRootPath"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_repo_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_repo_path", err), tplInstall, form) return } @@ -246,7 +246,7 @@ func SubmitInstall(ctx *context.Context) { form.LFSRootPath = strings.ReplaceAll(form.LFSRootPath, "\\", "/") if err := os.MkdirAll(form.LFSRootPath, os.ModePerm); err != nil { ctx.Data["Err_LFSRootPath"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_lfs_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_lfs_path", err), tplInstall, form) return } } @@ -255,7 +255,7 @@ func SubmitInstall(ctx *context.Context) { form.LogRootPath = strings.ReplaceAll(form.LogRootPath, "\\", "/") if err = os.MkdirAll(form.LogRootPath, os.ModePerm); err != nil { ctx.Data["Err_LogRootPath"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_log_root_path", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_log_root_path", err), tplInstall, form) return } @@ -309,16 +309,27 @@ func SubmitInstall(ctx *context.Context) { if err = db.InitEngineWithMigration(ctx, versioned_migration.Migrate); err != nil { db.UnsetDefaultEngine() ctx.Data["Err_DbSetting"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_setting", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_db_setting", err), tplInstall, form) return } - // Save settings. + cfg := fillInstallConfig(ctx, os.Environ(), form) + if cfg == nil { + return + } + saveConfigAndRestart(ctx, cfg, form) +} + +func fillInstallConfig(ctx *context.Context, envs []string, form *forms.InstallForm) setting.ConfigProvider { + // Some logic also depends on the config values, so EnvironmentToConfig should also be applied first. + // EnvironmentToConfig is applied on each start up, so it also must override the "install form", so it must be applied after (twice). cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) if err != nil { log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err) } + setting.EnvironmentToConfig(cfg, envs) + cfg.Section("").Key("APP_NAME").SetValue(form.AppName) cfg.Section("").Key("RUN_USER").SetValue(form.RunUser) cfg.Section("").Key("WORK_PATH").SetValue(setting.AppWorkPath) @@ -362,8 +373,8 @@ func SubmitInstall(ctx *context.Context) { if len(strings.TrimSpace(form.SMTPAddr)) > 0 { if _, err := mail.ParseAddress(form.SMTPFrom); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.smtp_from_invalid"), tplInstall, &form) - return + ctx.RenderWithErrDeprecated(ctx.Tr("install.smtp_from_invalid"), tplInstall, form) + return nil } cfg.Section("mailer").Key("ENABLED").SetValue("true") @@ -407,8 +418,8 @@ func SubmitInstall(ctx *context.Context) { if setting.InternalToken == "" { var internalToken string if internalToken, err = generate.NewInternalToken(); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.internal_token_failed", err), tplInstall, &form) - return + ctx.RenderWithErrDeprecated(ctx.Tr("install.internal_token_failed", err), tplInstall, form) + return nil } cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(internalToken) } @@ -424,8 +435,8 @@ func SubmitInstall(ctx *context.Context) { if setting.SecretKey == "" { var secretKey string if secretKey, err = generate.NewSecretKey(); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.secret_key_failed", err), tplInstall, &form) - return + ctx.RenderWithErrDeprecated(ctx.Tr("install.secret_key_failed", err), tplInstall, form) + return nil } cfg.Section("security").Key("SECRET_KEY").SetValue(secretKey) } @@ -434,24 +445,27 @@ func SubmitInstall(ctx *context.Context) { var algorithm *hash.PasswordHashAlgorithm setting.PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(form.PasswordAlgorithm) if algorithm == nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_password_algorithm"), tplInstall, &form) - return + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_password_algorithm"), tplInstall, form) + return nil } cfg.Section("security").Key("PASSWORD_HASH_ALGO").SetValue(form.PasswordAlgorithm) } + setting.EnvironmentToConfig(cfg, envs) + return cfg +} + +func saveConfigAndRestart(ctx *context.Context, cfg setting.ConfigProvider, form *forms.InstallForm) { log.Info("Save settings to custom config file %s", setting.CustomConf) - err = os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm) + err := os.MkdirAll(filepath.Dir(setting.CustomConf), os.ModePerm) if err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, form) return } - setting.EnvironmentToConfig(cfg, os.Environ()) - - if err = cfg.SaveTo(setting.CustomConf); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + if err := cfg.SaveTo(setting.CustomConf); err != nil { + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, form) return } @@ -482,12 +496,12 @@ func SubmitInstall(ctx *context.Context) { IsActive: optional.Some(true), } - if err = user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { + if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil { if !user_model.IsErrUserAlreadyExist(err) { setting.InstallLock = false ctx.Data["Err_AdminName"] = true ctx.Data["Err_AdminEmail"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_admin_setting", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.invalid_admin_setting", err), tplInstall, form) return } log.Info("Admin account already exist") @@ -504,11 +518,11 @@ func SubmitInstall(ctx *context.Context) { // Auto-login for admin if err = ctx.Session.Set(session.KeyUID, u.ID); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, form) return } if err = ctx.Session.Release(); err != nil { - ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + ctx.RenderWithErrDeprecated(ctx.Tr("install.save_config_failed", err), tplInstall, form) return } } diff --git a/routers/install/install_test.go b/routers/install/install_test.go new file mode 100644 index 00000000000..770fddc1eed --- /dev/null +++ b/routers/install/install_test.go @@ -0,0 +1,62 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package install + +import ( + "net/http" + "net/http/httptest" + "testing" + + "gitea.dev/models/unittest" + "gitea.dev/services/contexttest" + "gitea.dev/services/forms" + + "github.com/stretchr/testify/assert" +) + +func TestRoutes(t *testing.T) { + r := Routes() + assert.NotNil(t, r) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + r.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + assert.Contains(t, w.Body.String(), `class="page-content install"`) + + w = httptest.NewRecorder() + req = httptest.NewRequest(http.MethodGet, "/no-such", nil) + r.ServeHTTP(w, req) + assert.Equal(t, 404, w.Code) + + w = httptest.NewRecorder() + req = httptest.NewRequest(http.MethodGet, "/assets/img/gitea.svg", nil) + r.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) +} + +func TestMain(m *testing.M) { + unittest.MainTest(m) +} + +func TestFillInstallConfig(t *testing.T) { + ctx, _ := contexttest.MockContext(t, "/") + t.Run("WithEnv", func(t *testing.T) { + f := &forms.InstallForm{AppName: "TestAppName"} + cfg := fillInstallConfig(ctx, []string{ + "GITEA__OAUTH2__JWT_SECRET_URI=any", + "GITEA____APP_NAME=EnvAppName", + }, f) + assert.Equal(t, "EnvAppName", cfg.Section("").Key("APP_NAME").String()) + assert.Empty(t, cfg.Section("oauth2").Key("JWT_SECRET").String()) + assert.Equal(t, "any", cfg.Section("oauth2").Key("JWT_SECRET_URI").String()) + }) + t.Run("NoEnv", func(t *testing.T) { + f := &forms.InstallForm{AppName: "TestAppName"} + cfg := fillInstallConfig(ctx, []string{}, f) + assert.Equal(t, "TestAppName", cfg.Section("").Key("APP_NAME").String()) + assert.NotEmpty(t, cfg.Section("oauth2").Key("JWT_SECRET").String()) + assert.Empty(t, cfg.Section("oauth2").Key("JWT_SECRET_URI").String()) + }) +} diff --git a/routers/install/routes_test.go b/routers/install/routes_test.go deleted file mode 100644 index 05413791e21..00000000000 --- a/routers/install/routes_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2023 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package install - -import ( - "net/http" - "net/http/httptest" - "testing" - - "gitea.dev/models/unittest" - - "github.com/stretchr/testify/assert" -) - -func TestRoutes(t *testing.T) { - r := Routes() - assert.NotNil(t, r) - - w := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/", nil) - r.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) - assert.Contains(t, w.Body.String(), `class="page-content install"`) - - w = httptest.NewRecorder() - req = httptest.NewRequest(http.MethodGet, "/no-such", nil) - r.ServeHTTP(w, req) - assert.Equal(t, 404, w.Code) - - w = httptest.NewRecorder() - req = httptest.NewRequest(http.MethodGet, "/assets/img/gitea.svg", nil) - r.ServeHTTP(w, req) - assert.Equal(t, 200, w.Code) -} - -func TestMain(m *testing.M) { - unittest.MainTest(m) -}