diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go index 230ed58269aa..e472384a950b 100644 --- a/contrib/environment-to-ini/environment-to-ini.go +++ b/contrib/environment-to-ini/environment-to-ini.go @@ -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 } diff --git a/docs/content/doc/installation/with-docker-rootless.en-us.md b/docs/content/doc/installation/with-docker-rootless.en-us.md index b8b40fcbd72e..5aa4e46e12c8 100644 --- a/docs/content/doc/installation/with-docker-rootless.en-us.md +++ b/docs/content/doc/installation/with-docker-rootless.en-us.md @@ -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`. diff --git a/docs/content/doc/installation/with-docker.en-us.md b/docs/content/doc/installation/with-docker.en-us.md index e70a6ab1331f..a7a575293d3c 100644 --- a/docs/content/doc/installation/with-docker.en-us.md +++ b/docs/content/doc/installation/with-docker.en-us.md @@ -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`. diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index d032160a6fec..e18a13e4aafe 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -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) diff --git a/modules/setting/config_env.go b/modules/setting/config_env.go index 63488037059a..e23b64557f8f 100644 --- a/modules/setting/config_env.go +++ b/modules/setting/config_env.go @@ -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 } diff --git a/modules/setting/config_env_test.go b/modules/setting/config_env_test.go index d574554bcc04..2c1dd2f5c71f 100644 --- a/modules/setting/config_env_test.go +++ b/modules/setting/config_env_test.go @@ -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()) } diff --git a/modules/setting/path.go b/modules/setting/path.go index 163f1d159067..32ed8d81fa3e 100644 --- a/modules/setting/path.go +++ b/modules/setting/path.go @@ -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) { diff --git a/modules/setting/security.go b/modules/setting/security.go index c39eb7f3ebd6..872f9b53c546 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -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") diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 0d69847dbeab..d444d9a0175c 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -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) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1a9f146e026b..8aee3bebbc89 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -290,6 +290,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 diff --git a/routers/install/install.go b/routers/install/install.go index f121f313769d..a2e89d3dac48 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -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) diff --git a/templates/install.tmpl b/templates/install.tmpl index 0ef1cd88b94d..24533e1a5c6e 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -1,6 +1,6 @@ {{template "base/head" .}}
{{.locale.Tr "install.admin_setting_desc"}}
@@ -321,10 +321,30 @@