Skip to content

Commit b4460cf

Browse files
authored
Make "install page" respect environment config (#25648) (#25799)
Backport #25648 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. The only conflict during backport is "ui divider" in templates/install.tmpl
1 parent a1bc2aa commit b4460cf

File tree

13 files changed

+88
-71
lines changed

13 files changed

+88
-71
lines changed

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

+1-30
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@ package main
55

66
import (
77
"os"
8-
"strings"
98

109
"code.gitea.io/gitea/modules/log"
1110
"code.gitea.io/gitea/modules/setting"
1211

1312
"github.com/urfave/cli"
1413
)
1514

16-
// EnvironmentPrefix environment variables prefixed with this represent ini values to write
17-
const EnvironmentPrefix = "GITEA"
18-
1915
func main() {
2016
app := cli.NewApp()
2117
app.Name = "environment-to-ini"
@@ -70,15 +66,6 @@ func main() {
7066
Value: "",
7167
Usage: "Destination file to write to",
7268
},
73-
cli.BoolFlag{
74-
Name: "clear",
75-
Usage: "Clears the matched variables from the environment",
76-
},
77-
cli.StringFlag{
78-
Name: "prefix, p",
79-
Value: EnvironmentPrefix,
80-
Usage: "Environment prefix to look for - will be suffixed by __ (2 underscores)",
81-
},
8269
}
8370
app.Action = runEnvironmentToIni
8471
err := app.Run(os.Args)
@@ -99,9 +86,7 @@ func runEnvironmentToIni(c *cli.Context) error {
9986
log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err)
10087
}
10188

102-
prefixGitea := c.String("prefix") + "__"
103-
suffixFile := "__FILE"
104-
changed := setting.EnvironmentToConfig(cfg, prefixGitea, suffixFile, os.Environ())
89+
changed := setting.EnvironmentToConfig(cfg, os.Environ())
10590

10691
// try to save the config file
10792
destination := c.String("out")
@@ -116,19 +101,5 @@ func runEnvironmentToIni(c *cli.Context) error {
116101
}
117102
}
118103

119-
// clear Gitea's specific environment variables if requested
120-
if c.Bool("clear") {
121-
for _, kv := range os.Environ() {
122-
idx := strings.IndexByte(kv, '=')
123-
if idx < 0 {
124-
continue
125-
}
126-
eKey := kv[:idx]
127-
if strings.HasPrefix(eKey, prefixGitea) {
128-
_ = os.Unsetenv(eKey)
129-
}
130-
}
131-
}
132-
133104
return nil
134105
}

docs/content/doc/installation/with-docker-rootless.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ docker-compose up -d
288288

289289
In addition to the environment variables above, any settings in `app.ini` can be set
290290
or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`.
291-
These settings are applied each time the docker container starts.
291+
These settings are applied each time the docker container starts, and won't be passed into Gitea's sub-processes.
292292
Full information [here](https://github.com/go-gitea/gitea/tree/main/contrib/environment-to-ini).
293293

294294
These environment variables can be passed to the docker container in `docker-compose.yml`.

docs/content/doc/installation/with-docker.en-us.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ docker-compose up -d
289289

290290
In addition to the environment variables above, any settings in `app.ini` can be set
291291
or overridden with an environment variable of the form: `GITEA__SECTION_NAME__KEY_NAME`.
292-
These settings are applied each time the docker container starts.
292+
These settings are applied each time the docker container starts, and won't be passed into Gitea's sub-processes.
293293
Full information [here](https://github.com/go-gitea/gitea/tree/master/contrib/environment-to-ini).
294294

295295
These environment variables can be passed to the docker container in `docker-compose.yml`.

modules/assetfs/layered.go

+1
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ func (l *LayeredFS) WatchLocalChanges(ctx context.Context, callback func()) {
215215
log.Error("Unable to list directories for asset local file-system %q: %v", layer.localPath, err)
216216
continue
217217
}
218+
layerDirs = append(layerDirs, ".")
218219
for _, dir := range layerDirs {
219220
if err = watcher.Add(util.FilePathJoinAbs(layer.localPath, dir)); err != nil {
220221
log.Error("Unable to watch directory %s: %v", dir, err)

modules/setting/config_env.go

+23-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,31 @@ import (
1212
"code.gitea.io/gitea/modules/log"
1313
)
1414

15+
const (
16+
EnvConfigKeyPrefixGitea = "GITEA__"
17+
EnvConfigKeySuffixFile = "__FILE"
18+
)
19+
1520
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
1621

1722
var escapeRegex = regexp.MustCompile(escapeRegexpString)
1823

24+
func CollectEnvConfigKeys() (keys []string) {
25+
for _, env := range os.Environ() {
26+
if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) {
27+
k, _, _ := strings.Cut(env, "=")
28+
keys = append(keys, k)
29+
}
30+
}
31+
return keys
32+
}
33+
34+
func ClearEnvConfigKeys() {
35+
for _, k := range CollectEnvConfigKeys() {
36+
_ = os.Unsetenv(k)
37+
}
38+
}
39+
1940
// decodeEnvSectionKey will decode a portable string encoded Section__Key pair
2041
// Portable strings are considered to be of the form [A-Z0-9_]*
2142
// 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
87108
return ok, section, key, useFileValue
88109
}
89110

90-
func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, envs []string) (changed bool) {
111+
func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
91112
for _, kv := range envs {
92113
idx := strings.IndexByte(kv, '=')
93114
if idx < 0 {
@@ -97,7 +118,7 @@ func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, env
97118
// parse the environment variable to config section name and key name
98119
envKey := kv[:idx]
99120
envValue := kv[idx+1:]
100-
ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(prefixGitea, suffixFile, envKey)
121+
ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey)
101122
if !ok {
102123
continue
103124
}

modules/setting/config_env_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func TestDecodeEnvironmentKey(t *testing.T) {
7272
func TestEnvironmentToConfig(t *testing.T) {
7373
cfg, _ := NewConfigProviderFromData("")
7474

75-
changed := EnvironmentToConfig(cfg, "GITEA__", "__FILE", nil)
75+
changed := EnvironmentToConfig(cfg, nil)
7676
assert.False(t, changed)
7777

7878
cfg, err := NewConfigProviderFromData(`
@@ -81,16 +81,16 @@ key = old
8181
`)
8282
assert.NoError(t, err)
8383

84-
changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"})
84+
changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key=new"})
8585
assert.True(t, changed)
8686
assert.Equal(t, "new", cfg.Section("sec").Key("key").String())
8787

88-
changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"})
88+
changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key=new"})
8989
assert.False(t, changed)
9090

9191
tmpFile := t.TempDir() + "/the-file"
9292
_ = os.WriteFile(tmpFile, []byte("value-from-file"), 0o644)
93-
changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key__FILE=" + tmpFile})
93+
changed = EnvironmentToConfig(cfg, []string{"GITEA__sec__key__FILE=" + tmpFile})
9494
assert.True(t, changed)
9595
assert.Equal(t, "value-from-file", cfg.Section("sec").Key("key").String())
9696
}

modules/setting/path.go

+3
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,9 @@ func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkP
171171

172172
// only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready
173173
InitCfgProvider(tmpCustomConf.Value)
174+
if HasInstallLock(CfgProvider) {
175+
ClearEnvConfigKeys() // if the instance has been installed, do not pass the environment variables to sub-processes
176+
}
174177
configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH")
175178
if configWorkPath != "" {
176179
if !filepath.IsAbs(configWorkPath) {

modules/setting/security.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
102102

103103
func loadSecurityFrom(rootCfg ConfigProvider) {
104104
sec := rootCfg.Section("security")
105-
InstallLock = sec.Key("INSTALL_LOCK").MustBool(false)
105+
InstallLock = HasInstallLock(rootCfg)
106106
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7)
107107
CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome")
108108
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY")

modules/setting/setting.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,14 @@ func loadRunModeFrom(rootCfg ConfigProvider) {
183183
}
184184
}
185185

186+
// HasInstallLock checks the install-lock in ConfigProvider directly, because sometimes the config file is not loaded into setting variables yet.
187+
func HasInstallLock(rootCfg ConfigProvider) bool {
188+
return rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false)
189+
}
190+
186191
func mustCurrentRunUserMatch(rootCfg ConfigProvider) {
187192
// Does not check run user when the "InstallLock" is off.
188-
installLock := rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false)
189-
if installLock {
193+
if HasInstallLock(rootCfg) {
190194
currentUser, match := IsRunUserMatchCurrentUser(RunUser)
191195
if !match {
192196
log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser)

options/locale/locale_en-US.ini

+2
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ invalid_password_algorithm = Invalid password hash algorithm
290290
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.
291291
enable_update_checker = Enable Update Checker
292292
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
293+
env_config_keys = Environment Configuration
294+
env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
293295

294296
[home]
295297
uname_holder = Username or Email Address

routers/install/install.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func getSupportedDbTypeNames() (dbTypeNames []map[string]string) {
5656
func Contexter() func(next http.Handler) http.Handler {
5757
rnd := templates.HTMLRenderer()
5858
dbTypeNames := getSupportedDbTypeNames()
59+
envConfigKeys := setting.CollectEnvConfigKeys()
5960
return func(next http.Handler) http.Handler {
6061
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
6162
base, baseCleanUp := context.NewBaseContext(resp, req)
@@ -70,11 +71,13 @@ func Contexter() func(next http.Handler) http.Handler {
7071
ctx.AppendContextValue(context.WebContextKey, ctx)
7172
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
7273
ctx.Data.MergeFrom(middleware.ContextData{
73-
"locale": ctx.Locale,
74-
"Title": ctx.Locale.Tr("install.install"),
75-
"PageIsInstall": true,
76-
"DbTypeNames": dbTypeNames,
77-
"AllLangs": translation.AllLangs(),
74+
"locale": ctx.Locale,
75+
"Title": ctx.Locale.Tr("install.install"),
76+
"PageIsInstall": true,
77+
"DbTypeNames": dbTypeNames,
78+
"EnvConfigKeys": envConfigKeys,
79+
"CustomConfFile": setting.CustomConf,
80+
"AllLangs": translation.AllLangs(),
7881

7982
"PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
8083
})
@@ -218,7 +221,7 @@ func checkDatabase(ctx *context.Context, form *forms.InstallForm) bool {
218221
return false
219222
}
220223

221-
log.Info("User confirmed reinstallation of Gitea into a pre-existing database")
224+
log.Info("User confirmed re-installation of Gitea into a pre-existing database")
222225
}
223226

224227
if hasPostInstallationUser || dbMigrationVersion > 0 {
@@ -502,6 +505,8 @@ func SubmitInstall(ctx *context.Context) {
502505
return
503506
}
504507

508+
setting.EnvironmentToConfig(cfg, os.Environ())
509+
505510
if err = cfg.SaveTo(setting.CustomConf); err != nil {
506511
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
507512
return
@@ -568,6 +573,7 @@ func SubmitInstall(ctx *context.Context) {
568573
}
569574
}
570575

576+
setting.ClearEnvConfigKeys()
571577
log.Info("First-time run install finished!")
572578
InstallDone(ctx)
573579

templates/install.tmpl

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{{template "base/head" .}}
22
<div role="main" aria-label="{{.Title}}" class="page-content install">
3-
<div class="ui middle very relaxed page grid">
3+
<div class="ui grid install-config-container">
44
<div class="sixteen wide center aligned centered column">
55
<h3 class="ui top attached header">
66
{{.locale.Tr "install.title"}}
@@ -149,7 +149,7 @@
149149
</div>
150150
<div class="inline field">
151151
<div class="ui checkbox">
152-
<label for="enable_update_checker">{{.locale.Tr "install.enable_update_checker"}}</label>
152+
<label>{{.locale.Tr "install.enable_update_checker"}}</label>
153153
<input name="enable_update_checker" type="checkbox">
154154
</div>
155155
<span class="help">{{.locale.Tr "install.enable_update_checker_helper"}}</span>
@@ -161,7 +161,7 @@
161161

162162
<!-- Email -->
163163
<details class="optional field">
164-
<summary class="title gt-py-3{{if .Err_SMTP}} text red{{end}}">
164+
<summary class="right-content gt-py-3{{if .Err_SMTP}} text red{{end}}">
165165
{{.locale.Tr "install.email_title"}}
166166
</summary>
167167
<div class="inline field">
@@ -201,7 +201,7 @@
201201

202202
<!-- Server and other services -->
203203
<details class="optional field">
204-
<summary class="title gt-py-3{{if .Err_Services}} text red{{end}}">
204+
<summary class="right-content gt-py-3{{if .Err_Services}} text red{{end}}">
205205
{{.locale.Tr "install.server_service_title"}}
206206
</summary>
207207
<div class="inline field">
@@ -299,7 +299,7 @@
299299

300300
<!-- Admin -->
301301
<details class="optional field">
302-
<summary class="title gt-py-3{{if .Err_Admin}} text red{{end}}">
302+
<summary class="right-content gt-py-3{{if .Err_Admin}} text red{{end}}">
303303
{{.locale.Tr "install.admin_title"}}
304304
</summary>
305305
<p class="center">{{.locale.Tr "install.admin_setting_desc"}}</p>
@@ -321,10 +321,30 @@
321321
</div>
322322
</details>
323323

324-
<div class="ui divider"></div>
324+
{{if .EnvConfigKeys}}
325+
<!-- Environment Config -->
326+
<h4 class="ui dividing header">{{.locale.Tr "install.env_config_keys"}}</h4>
325327
<div class="inline field">
326328
<label></label>
327329
<button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>
330+
<div class="right-content">
331+
{{.locale.Tr "install.env_config_keys_prompt"}}
332+
</div>
333+
<div class="right-content gt-mt-3">
334+
{{range .EnvConfigKeys}}<span class="ui label">{{.}}</span>{{end}}
335+
</div>
336+
</div>
337+
{{end}}
338+
339+
<div class="ui divider"></div>
340+
341+
<div class="inline field">
342+
<div class="right-content">
343+
These configuration options will be written into: {{.CustomConfFile}}
344+
</div>
345+
<div class="right-content gt-mt-3">
346+
<button class="ui primary button">{{.locale.Tr "install.install_btn_confirm"}}</button>
347+
</div>
328348
</div>
329349
</form>
330350
</div>

web_src/css/install.css

+7-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
.page-content.install {
2-
padding-top: 45px;
1+
.page-content.install .install-config-container {
2+
max-width: 900px;
3+
margin: auto;
34
}
45

56
.page-content.install form.ui.form .inline.field > label {
@@ -9,26 +10,20 @@
910
margin-right: 0;
1011
}
1112

12-
.page-content.install form.ui.form .inline.field > .ui.checkbox:first-child {
13+
.page-content.install .ui.form .field > .help,
14+
.page-content.install .ui.form .field > .ui.checkbox:first-child,
15+
.page-content.install .ui.form .field > .right-content {
1316
margin-left: 30%;
1417
padding-left: 5px;
15-
}
16-
17-
.page-content.install form.ui.form .inline.field > .ui.checkbox:first-child label {
1818
width: auto;
1919
}
2020

21-
.page-content.install form.ui.form .title {
22-
margin-left: 30%;
23-
padding-left: 5px;
24-
}
25-
2621
.page-content.install form.ui.form input {
2722
width: 60%;
2823
}
2924

3025
.page-content.install form.ui.form details.optional.field[open] {
31-
border-bottom: 1px solid var(--color-secondary);
26+
border-bottom: 1px dashed var(--color-secondary);
3227
padding-bottom: 10px;
3328
}
3429

@@ -44,12 +39,6 @@
4439
text-align: left;
4540
}
4641

47-
.page-content.install form.ui.form .field .help {
48-
margin-left: 30%;
49-
padding-left: 5px;
50-
width: 60%;
51-
}
52-
5342
.page-content.install .ui .reinstall-message {
5443
width: 70%;
5544
margin: 20px auto;

0 commit comments

Comments
 (0)