]> source.dussan.org Git - gitea.git/commitdiff
Make "install page" respect environment config (#25648)
authorwxiaoguang <wxiaoguang@gmail.com>
Sun, 9 Jul 2023 22:43:37 +0000 (06:43 +0800)
committerGitHub <noreply@github.com>
Sun, 9 Jul 2023 22:43:37 +0000 (22:43 +0000)
Replace #25580

Fix #19453

The problem was: when users set "GITEA__XXX__YYY" , the "install page"
doesn't respect it.

So, to make the result consistent and avoid surprising end users, now
the "install page" also writes the environment variables to the config
file.

And, to make things clear, there are enough messages on the UI to tell
users what will happen.

There are some necessary/related changes to `environment-to-ini.go`:

* The "--clear" flag is removed and it was incorrectly written there.
The "clear" operation should be done if INSTALL_LOCK=true
* The "--prefix" flag is removed because it's never used, never
documented and it only causes inconsistent behavior.

![image](https://github.com/go-gitea/gitea/assets/2114189/12778ee4-3fb5-4664-a73a-41ebbd77cd5b)

13 files changed:
contrib/environment-to-ini/environment-to-ini.go
docs/content/doc/installation/with-docker-rootless.en-us.md
docs/content/doc/installation/with-docker.en-us.md
modules/assetfs/layered.go
modules/setting/config_env.go
modules/setting/config_env_test.go
modules/setting/path.go
modules/setting/security.go
modules/setting/setting.go
options/locale/locale_en-US.ini
routers/install/install.go
templates/install.tmpl
web_src/css/install.css

index 230ed58269aab0b77e890e41130001599b6408eb..e472384a950b48e224bbe7e0b09a0d399e2c7362 100644 (file)
@@ -5,7 +5,6 @@ package main
 
 import (
        "os"
-       "strings"
 
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/setting"
@@ -13,9 +12,6 @@ import (
        "github.com/urfave/cli"
 )
 
-// EnvironmentPrefix environment variables prefixed with this represent ini values to write
-const EnvironmentPrefix = "GITEA"
-
 func main() {
        app := cli.NewApp()
        app.Name = "environment-to-ini"
@@ -70,15 +66,6 @@ func main() {
                        Value: "",
                        Usage: "Destination file to write to",
                },
-               cli.BoolFlag{
-                       Name:  "clear",
-                       Usage: "Clears the matched variables from the environment",
-               },
-               cli.StringFlag{
-                       Name:  "prefix, p",
-                       Value: EnvironmentPrefix,
-                       Usage: "Environment prefix to look for - will be suffixed by __ (2 underscores)",
-               },
        }
        app.Action = runEnvironmentToIni
        err := app.Run(os.Args)
@@ -99,9 +86,7 @@ func runEnvironmentToIni(c *cli.Context) error {
                log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
        }
 
-       prefixGitea := c.String("prefix") + "__"
-       suffixFile := "__FILE"
-       changed := setting.EnvironmentToConfig(cfg, prefixGitea, suffixFile, os.Environ())
+       changed := setting.EnvironmentToConfig(cfg, os.Environ())
 
        // try to save the config file
        destination := c.String("out")
@@ -116,19 +101,5 @@ func runEnvironmentToIni(c *cli.Context) error {
                }
        }
 
-       // clear Gitea's specific environment variables if requested
-       if c.Bool("clear") {
-               for _, kv := range os.Environ() {
-                       idx := strings.IndexByte(kv, '=')
-                       if idx < 0 {
-                               continue
-                       }
-                       eKey := kv[:idx]
-                       if strings.HasPrefix(eKey, prefixGitea) {
-                               _ = os.Unsetenv(eKey)
-                       }
-               }
-       }
-
        return nil
 }
index b8b40fcbd72eef9bb93badb5d08c50b8f226427b..5aa4e46e12c87e670f8fe83e65df2b51409f106a 100644 (file)
@@ -288,7 +288,7 @@ docker-compose up -d
 
 In addition to the environment variables above, any settings in `app.ini` can be set
 or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`.
-These settings are applied each time the docker container starts.
+These settings are applied each time the docker container starts, and won't be passed into Gitea's sub-processes.
 Full information [here](https://github.com/go-gitea/gitea/tree/main/contrib/environment-to-ini).
 
 These environment variables can be passed to the docker container in `docker-compose.yml`.
index e70a6ab1331fd4b0b8484091dd692b7612494111..a7a575293d3ceac0977017826fa7304301160e30 100644 (file)
@@ -289,7 +289,7 @@ docker-compose up -d
 
 In addition to the environment variables above, any settings in `app.ini` can be set
 or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`.
-These settings are applied each time the docker container starts.
+These settings are applied each time the docker container starts, and won't be passed into Gitea's sub-processes.
 Full information [here](https://github.com/go-gitea/gitea/tree/master/contrib/environment-to-ini).
 
 These environment variables can be passed to the docker container in `docker-compose.yml`.
index d032160a6fecaa9da553340d9edc3a98a643673d..e18a13e4aafe4ee0e758e88fdeb58926c3fed30d 100644 (file)
@@ -215,6 +215,7 @@ func (l *LayeredFS) WatchLocalChanges(ctx context.Context, callback func()) {
                        log.Error("Unable to list directories for asset local file-system %q: %v", layer.localPath, err)
                        continue
                }
+               layerDirs = append(layerDirs, ".")
                for _, dir := range layerDirs {
                        if err = watcher.Add(util.FilePathJoinAbs(layer.localPath, dir)); err != nil {
                                log.Error("Unable to watch directory %s: %v", dir, err)
index 63488037059ab1766528a463dc0c0fc772f2b7f6..e23b64557f8fefa41adae35982e5331c2a15e6ee 100644 (file)
@@ -12,10 +12,31 @@ import (
        "code.gitea.io/gitea/modules/log"
 )
 
+const (
+       EnvConfigKeyPrefixGitea = "GITEA__"
+       EnvConfigKeySuffixFile  = "__FILE"
+)
+
 const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
 
 var escapeRegex = regexp.MustCompile(escapeRegexpString)
 
+func CollectEnvConfigKeys() (keys []string) {
+       for _, env := range os.Environ() {
+               if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) {
+                       k, _, _ := strings.Cut(env, "=")
+                       keys = append(keys, k)
+               }
+       }
+       return keys
+}
+
+func ClearEnvConfigKeys() {
+       for _, k := range CollectEnvConfigKeys() {
+               _ = os.Unsetenv(k)
+       }
+}
+
 // decodeEnvSectionKey will decode a portable string encoded Section__Key pair
 // Portable strings are considered to be of the form [A-Z0-9_]*
 // We will encode a disallowed value as the UTF8 byte string preceded by _0X and
@@ -87,7 +108,7 @@ func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, sect
        return ok, section, key, useFileValue
 }
 
-func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, envs []string) (changed bool) {
+func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
        for _, kv := range envs {
                idx := strings.IndexByte(kv, '=')
                if idx < 0 {
@@ -97,7 +118,7 @@ func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, env
                // parse the environment variable to config section name and key name
                envKey := kv[:idx]
                envValue := kv[idx+1:]
-               ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(prefixGitea, suffixFile, envKey)
+               ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey)
                if !ok {
                        continue
                }
index d574554bcc049d800f5e712ba0663e1a8fc61249..2c1dd2f5c71f7702a55f099328aa6e27596c58f8 100644 (file)
@@ -72,7 +72,7 @@ func TestDecodeEnvironmentKey(t *testing.T) {
 func TestEnvironmentToConfig(t *testing.T) {
        cfg, _ := NewConfigProviderFromData("")
 
-       changed := EnvironmentToConfig(cfg, "GITEA__", "__FILE", nil)
+       changed := EnvironmentToConfig(cfg, nil)
        assert.False(t, changed)
 
        cfg, err := NewConfigProviderFromData(`
@@ -81,16 +81,16 @@ key = old
 `)
        assert.NoError(t, err)
 
-       changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"})
+       changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key=new"})
        assert.True(t, changed)
        assert.Equal(t, "new", cfg.Section("sec").Key("key").String())
 
-       changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"})
+       changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key=new"})
        assert.False(t, changed)
 
        tmpFile := t.TempDir() + "/the-file"
        _ = os.WriteFile(tmpFile, []byte("value-from-file"), 0o644)
-       changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key__FILE=" + tmpFile})
+       changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
        assert.True(t, changed)
        assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
 }
index 163f1d159067f944ef0c4e7010dfc4ca555f3627..32ed8d81fa3e9e59a81d48b36bb1dcb1572212c5 100644 (file)
@@ -171,6 +171,9 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP
 
        // only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready
        InitCfgProvider(tmpCustomConf.Value)
+       if HasInstallLock(CfgProvider) {
+               ClearEnvConfigKeys() // if the instance has been installed, do not pass the environment variables to sub-processes
+       }
        configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH")
        if configWorkPath != "" {
                if !filepath.IsAbs(configWorkPath) {
index 5f1f9f4ade89417c2e8d10e7eb709926d7ca0870..7064d7a008f405ddbba90847b3082575d39aed73 100644 (file)
@@ -102,7 +102,7 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
 
 func loadSecurityFrom(rootCfg ConfigProvider) {
        sec := rootCfg.Section("security")
-       InstallLock = sec.Key("INSTALL_LOCK").MustBool(false)
+       InstallLock = HasInstallLock(rootCfg)
        LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
        CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
        SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")
index 0d69847dbeab232534b34b4d239b213103acec6d..d444d9a0175c65edc708681d02d95d60b9bfa075 100644 (file)
@@ -183,10 +183,14 @@ func loadRunModeFrom(rootCfg ConfigProvider) {
        }
 }
 
+// HasInstallLock checks the install-lock in ConfigProvider directly, because sometimes the config file is not loaded into setting variables yet.
+func HasInstallLock(rootCfg ConfigProvider) bool {
+       return rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false)
+}
+
 func mustCurrentRunUserMatch(rootCfg ConfigProvider) {
        // Does not check run user when the "InstallLock" is off.
-       installLock := rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false)
-       if installLock {
+       if HasInstallLock(rootCfg) {
                currentUser, match := IsRunUserMatchCurrentUser(RunUser)
                if !match {
                        log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser)
index 58fd84308d3e5a10662f0d4a11038f56003b8244..c4c9d32e1d4ac13ce2d8694baf06001c1b02fdbc 100644 (file)
@@ -296,6 +296,8 @@ invalid_password_algorithm = Invalid password hash algorithm
 password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. The argon2 algorithm is rather secure but uses a lot of memory and may be inappropriate for small systems.
 enable_update_checker = Enable Update Checker
 enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
+env_config_keys = Environment Configuration
+env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
 
 [home]
 uname_holder = Username or Email Address
index f121f313769d13f09e6aaddaf2ac9bde633f8578..a2e89d3dac48d12f19afd0924b51d351b2950a36 100644 (file)
@@ -56,6 +56,7 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
 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)
@@ -70,11 +71,13 @@ func Contexter() func(next http.Handler) http.Handler {
                        ctx.AppendContextValue(context.WebContextKey, ctx)
                        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(),
+                               "locale":         ctx.Locale,
+                               "Title":          ctx.Locale.Tr("install.install"),
+                               "PageIsInstall":  true,
+                               "DbTypeNames":    dbTypeNames,
+                               "EnvConfigKeys":  envConfigKeys,
+                               "CustomConfFile": setting.CustomConf,
+                               "AllLangs":       translation.AllLangs(),
 
                                "PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
                        })
@@ -218,7 +221,7 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
                        return false
                }
 
-               log.Info("User confirmed reinstallation of Gitea into a pre-existing database")
+               log.Info("User confirmed re-installation of Gitea into a pre-existing database")
        }
 
        if hasPostInstallationUser || dbMigrationVersion > 0 {
@@ -502,6 +505,8 @@ func SubmitInstall(ctx *context.Context) {
                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
@@ -568,6 +573,7 @@ func SubmitInstall(ctx *context.Context) {
                }
        }
 
+       setting.ClearEnvConfigKeys()
        log.Info("First-time run install finished!")
        InstallDone(ctx)
 
index 48eabe8e7410d009b29b71f9170a0e1cde73b757..b5caab148981d1853376886152bee0e33e1cca8e 100644 (file)
@@ -1,6 +1,6 @@
 {{template "base/head" .}}
 <div role="main" aria-label="{{.Title}}" class="page-content install">
-       <div class="ui middle very relaxed page grid">
+       <div class="ui grid install-config-container">
                <div class="sixteen wide center aligned centered column">
                        <h3 class="ui top attached header">
                                {{.locale.Tr "install.title"}}
                                        </div>
                                        <div class="inline field">
                                                <div class="ui checkbox">
-                                                       <label for="enable_update_checker">{{.locale.Tr "install.enable_update_checker"}}</label>
+                                                       <label>{{.locale.Tr "install.enable_update_checker"}}</label>
                                                        <input name="enable_update_checker" type="checkbox">
                                                </div>
                                                <span class="help">{{.locale.Tr "install.enable_update_checker_helper"}}</span>
 
                                        <!-- Email -->
                                        <details class="optional field">
-                                               <summary class="title gt-py-3{{if .Err_SMTP}} text red{{end}}">
+                                               <summary class="right-content gt-py-3{{if .Err_SMTP}} text red{{end}}">
                                                        {{.locale.Tr "install.email_title"}}
                                                </summary>
                                                <div class="inline field">
 
                                        <!-- Server and other services -->
                                        <details class="optional field">
-                                               <summary class="title gt-py-3{{if .Err_Services}} text red{{end}}">
+                                               <summary class="right-content gt-py-3{{if .Err_Services}} text red{{end}}">
                                                        {{.locale.Tr "install.server_service_title"}}
                                                </summary>
                                                <div class="inline field">
 
                                        <!-- Admin -->
                                        <details class="optional field">
-                                               <summary class="title gt-py-3{{if .Err_Admin}} text red{{end}}">
+                                               <summary class="right-content gt-py-3{{if .Err_Admin}} text red{{end}}">
                                                        {{.locale.Tr "install.admin_title"}}
                                                </summary>
                                                <p class="center">{{.locale.Tr "install.admin_setting_desc"}}</p>
                                                </div>
                                        </details>
 
+                                       {{if .EnvConfigKeys}}
+                                       <!-- Environment Config -->
+                                       <h4 class="ui dividing header">{{.locale.Tr "install.env_config_keys"}}</h4>
+                                       <div class="inline field">
+                                               <div class="right-content">
+                                                       {{.locale.Tr "install.env_config_keys_prompt"}}
+                                               </div>
+                                               <div class="right-content gt-mt-3">
+                                                       {{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}}
+                                               </div>
+                                       </div>
+                                       {{end}}
+
                                        <div class="divider"></div>
                                        <div class="inline field">
-                                               <label></label>
-                                               <button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>
+                                               <div class="right-content">
+                                                       These configuration options will be written into: {{.CustomConfFile}}
+                                               </div>
+                                               <div class="right-content gt-mt-3">
+                                                       <button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>
+                                               </div>
                                        </div>
                                </form>
                        </div>
index a65a213fd5e2ec9bd828ee4261b454f9a0ecca09..4ac294e902c53265bbafce4a0e08362df2e19051 100644 (file)
@@ -1,5 +1,6 @@
-.page-content.install {
-  padding-top: 45px;
+.page-content.install .install-config-container {
+  max-width: 900px;
+  margin: auto;
 }
 
 .page-content.install form.ui.form .inline.field > label {
   margin-right: 0;
 }
 
-.page-content.install form.ui.form .inline.field > .ui.checkbox:first-child {
+.page-content.install .ui.form .field > .help,
+.page-content.install .ui.form .field > .ui.checkbox:first-child,
+.page-content.install .ui.form .field > .right-content {
   margin-left: 30%;
   padding-left: 5px;
-}
-
-.page-content.install form.ui.form .inline.field > .ui.checkbox:first-child label {
   width: auto;
 }
 
-.page-content.install form.ui.form .title {
-  margin-left: 30%;
-  padding-left: 5px;
-}
-
 .page-content.install form.ui.form input {
   width: 60%;
 }
 
 .page-content.install form.ui.form details.optional.field[open] {
-  border-bottom: 1px solid var(--color-secondary);
+  border-bottom: 1px dashed var(--color-secondary);
   padding-bottom: 10px;
 }
 
   text-align: left;
 }
 
-.page-content.install form.ui.form .field .help {
-  margin-left: 30%;
-  padding-left: 5px;
-  width: 60%;
-}
-
 .page-content.install .ui .reinstall-message {
   width: 70%;
   margin: 20px auto;