From 63a5301fa060ed06b8c2729bff1c5b7bf98659cc Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 23 Oct 2025 22:27:54 +0800 Subject: [PATCH 1/8] fix --- Dockerfile | 9 +- Dockerfile.rootless | 11 +- cmd/config_update.go | 145 ++++++++++++++++++ cmd/config_update_test.go | 72 +++++++++ cmd/main.go | 1 + contrib/environment-to-ini/README | 47 ------ .../environment-to-ini/environment-to-ini.go | 112 -------------- docker/root/usr/local/bin/environment-to-ini | 2 + .../rootless/usr/local/bin/environment-to-ini | 2 + modules/setting/config_provider.go | 10 ++ 10 files changed, 236 insertions(+), 175 deletions(-) create mode 100644 cmd/config_update.go create mode 100644 cmd/config_update_test.go delete mode 100644 contrib/environment-to-ini/README delete mode 100644 contrib/environment-to-ini/environment-to-ini.go create mode 100644 docker/root/usr/local/bin/environment-to-ini create mode 100644 docker/rootless/usr/local/bin/environment-to-ini diff --git a/Dockerfile b/Dockerfile index 78a556497a6c0..b60d94cc47d4a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,20 +26,16 @@ WORKDIR ${GOPATH}/src/code.gitea.io/gitea RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ && make clean-all build -# Begin env-to-ini build -RUN go build contrib/environment-to-ini/environment-to-ini.go - # Copy local files COPY docker/root /tmp/local # Set permissions RUN chmod 755 /tmp/local/usr/bin/entrypoint \ - /tmp/local/usr/local/bin/gitea \ + /tmp/local/usr/local/bin/* \ /tmp/local/etc/s6/gitea/* \ /tmp/local/etc/s6/openssh/* \ /tmp/local/etc/s6/.s6-svscan/* \ - /go/src/code.gitea.io/gitea/gitea \ - /go/src/code.gitea.io/gitea/environment-to-ini + /go/src/code.gitea.io/gitea/gitea FROM docker.io/library/alpine:3.22 LABEL maintainer="maintainers@gitea.io" @@ -82,4 +78,3 @@ CMD ["/usr/bin/s6-svscan", "/etc/s6"] COPY --from=build-env /tmp/local / COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea -COPY --from=build-env /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini diff --git a/Dockerfile.rootless b/Dockerfile.rootless index e83c1af33b90d..f7a0412be217b 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -26,18 +26,12 @@ WORKDIR ${GOPATH}/src/code.gitea.io/gitea RUN if [ -n "${GITEA_VERSION}" ]; then git checkout "${GITEA_VERSION}"; fi \ && make clean-all build -# Begin env-to-ini build -RUN go build contrib/environment-to-ini/environment-to-ini.go - # Copy local files COPY docker/rootless /tmp/local # Set permissions -RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ - /tmp/local/usr/local/bin/docker-setup.sh \ - /tmp/local/usr/local/bin/gitea \ - /go/src/code.gitea.io/gitea/gitea \ - /go/src/code.gitea.io/gitea/environment-to-ini +RUN chmod 755 /tmp/local/usr/local/bin/* \ + /go/src/code.gitea.io/gitea/gitea FROM docker.io/library/alpine:3.22 LABEL maintainer="maintainers@gitea.io" @@ -71,7 +65,6 @@ RUN chown git:git /var/lib/gitea /etc/gitea COPY --from=build-env /tmp/local / COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea -COPY --from=build-env --chown=root:root /go/src/code.gitea.io/gitea/environment-to-ini /usr/local/bin/environment-to-ini # git:git USER 1000:1000 diff --git a/cmd/config_update.go b/cmd/config_update.go new file mode 100644 index 0000000000000..44bd12a52d97b --- /dev/null +++ b/cmd/config_update.go @@ -0,0 +1,145 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "context" + "errors" + "fmt" + "os" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" + + "github.com/urfave/cli/v3" +) + +func cmdConfig() *cli.Command { + subcmdConfigUpdateIni := &cli.Command{ + Name: "update-ini", + Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.", + Description: ` +Help users to update the gitea configuration INI file: +* Keep specified keys to for the new INI file. +* Map environment variables to values in the INI. + +# Keep Specified Keys + +If you need to re-create the configuration file with only a subset of keys, +you can provide an INI template file and use the "--config-key-template" flag. +For example, if a helm chart needs to reset the settings and only keep SECRET_KEY, +it can use a template file like: + + [security] + SECRET_KEY= + +$ ./gitea config update-ini --config app-old.ini --config-key-template app-template.ini --out app-new.ini + +# Map Environment Variables to INI Configuration + +Environment variables of the form "GITEA__section_name__KEY_NAME" +will be mapped to the ini section "[section_name]" and the key +"KEY_NAME" with the value as provided. + +Environment variables of the form "GITEA__section_name__KEY_NAME__FILE" +will be mapped to the ini section "[section_name]" and the key +"KEY_NAME" with the value loaded from the specified file. + +Environment variable keys can only contain characters "0-9A-Z_", +if a section or key name contains dot ".", it needs to be escaped as _0x2E_. +For example, to apply this config: + + [git.config] + foo.bar=val + +$ export GITEA__git_0x2E_config__foo_0x2E_bar=val + +# Put All Together + +$ ./gitea config update-ini --config app.ini --config-key-template app-template.ini --apply-env +`, + Flags: []cli.Flag{ + // "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper + &cli.StringFlag{ + Name: "config-key-template", + Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.", + }, + &cli.BoolFlag{ + Name: "apply-env", + Usage: "Apply all GITEA__* variables from the environment to the config.", + }, + &cli.StringFlag{ + Name: "out", + Usage: "Destination config file to write to. If not set, will overwrite the source config file.", + }, + }, + Action: runConfigUpdateIni, + } + + return &cli.Command{ + Name: "config", + Usage: "Manage Gitea configuration", + Commands: []*cli.Command{ + subcmdConfigUpdateIni, + }, + } +} + +func runConfigUpdateIni(_ context.Context, c *cli.Command) error { + // the config system may change the environment variables, so get a copy first, to be used later + env := append([]string{}, os.Environ()...) + if !c.IsSet("config") { + return errors.New("flag is required but not set: --config") + } + + configFileIn := c.String("config") + cfgIn, err := setting.NewConfigProviderFromFile(configFileIn) + if err != nil { + return fmt.Errorf("failed to load config file %q: %v", configFileIn, err) + } + + configFileOut := c.String("out") + configFileOut = util.IfZero(configFileOut, configFileIn) + needWriteOut := configFileOut != configFileIn + + cfgOut := cfgIn + if c.IsSet("config-key-template") { + needWriteOut = true + configKeepTemplate := c.String("config-key-template") + cfgOut, err = setting.NewConfigProviderFromFile(configKeepTemplate) + if err != nil { + return fmt.Errorf("failed to load config keep template file %q: %v", configKeepTemplate, err) + } + + for _, secOut := range cfgOut.Sections() { + for _, keyOut := range secOut.Keys() { + secIn := cfgIn.Section(secOut.Name()) + keyIn := setting.ConfigSectionKey(secIn, keyOut.Name()) + if keyIn != nil { + keyOut.SetValue(keyIn.String()) + } else { + secOut.DeleteKey(keyOut.Name()) + } + } + if len(secOut.Keys()) == 0 { + cfgOut.DeleteSection(secOut.Name()) + } + } + } + + if c.Bool("apply-env") { + if setting.EnvironmentToConfig(cfgOut, env) { + needWriteOut = true + } + } + + if needWriteOut { + _, _ = fmt.Fprintf(c.Writer, "Saving config to: %q\n", configFileOut) + err = cfgOut.SaveTo(configFileOut) + if err != nil { + return err + } + } + return nil +} diff --git a/cmd/config_update_test.go b/cmd/config_update_test.go new file mode 100644 index 0000000000000..4df0344be3251 --- /dev/null +++ b/cmd/config_update_test.go @@ -0,0 +1,72 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package cmd + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConfigUpdate(t *testing.T) { + configOld := t.TempDir() + "/app-old.ini" + configTemplate := t.TempDir() + "/app-template.ini" + _ = os.WriteFile(configOld, []byte(` +[sec] +k1=v1 +k2=v2 +`), os.ModePerm) + + _ = os.WriteFile(configTemplate, []byte(` +[sec] +k1=in-template + +[sec2] +k3=v3 +`), os.ModePerm) + + t.Setenv("GITEA__EnV__KeY", "val") + + t.Run("OutputToNewWithEnv", func(t *testing.T) { + configNew := t.TempDir() + "/app-new.ini" + err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ + "./gitea", "--config", configOld, "config", "update-ini", + "--apply-env", + "--config-key-template", configTemplate, + "--out", configNew, + }) + require.NoError(t, err) + + // "k1" old value is kept because its key is in the template + // "k2" is removed because it isn't in the template + // "k3" isn't in new config because it isn't in the old config + // [env] is applied from environment variable + data, _ := os.ReadFile(configNew) + require.Equal(t, `[sec] +k1 = v1 + +[env] +KeY = val +`, string(data)) + }) + + t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) { + err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ + "./gitea", "config", "update-ini", + "--apply-env", + "--config", configOld, + }) + require.NoError(t, err) + + data, _ := os.ReadFile(configOld) + require.Equal(t, `[sec] +k1 = v1 +k2 = v2 + +[env] +KeY = val +`, string(data)) + }) +} diff --git a/cmd/main.go b/cmd/main.go index 3fdaf48ed9665..3a38d675a14d8 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -128,6 +128,7 @@ func NewMainApp(appVer AppVersion) *cli.Command { // these sub-commands do not need the config file, and they do not depend on any path or environment variable. subCmdStandalone := []*cli.Command{ + cmdConfig(), cmdCert(), CmdGenerate, CmdDocs, diff --git a/contrib/environment-to-ini/README b/contrib/environment-to-ini/README deleted file mode 100644 index f1d3f2ae832fc..0000000000000 --- a/contrib/environment-to-ini/README +++ /dev/null @@ -1,47 +0,0 @@ -Environment To Ini -================== - -Multiple docker users have requested that the Gitea docker is changed -to permit arbitrary configuration via environment variables. - -Gitea needs to use an ini file for configuration because the running -environment that starts the docker may not be the same as that used -by the hooks. An ini file also gives a good default and means that -users do not have to completely provide a full environment. - -With those caveats above, this command provides a generic way of -converting suitably structured environment variables into any ini -value. - -To use the command is very simple just run it and the default gitea -app.ini will be rewritten to take account of the variables provided, -however there are various options to give slightly different -behavior and these can be interrogated with the `-h` option. - -The environment variables should be of the form: - - GITEA__SECTION_NAME__KEY_NAME - -Note, SECTION_NAME in the notation above is case-insensitive. - -Environment variables are usually restricted to a reduced character -set "0-9A-Z_" - in order to allow the setting of sections with -characters outside of that set, they should be escaped as following: -"_0X2E_" for "." and "_0X2D_" for "-". The entire section and key names -can be escaped as a UTF8 byte string if necessary. E.g. to configure: - - """ - ... - [log.console] - COLORIZE=false - STDERR=true - ... - """ - -You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false" -and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found -on the configuration cheat sheet. - -To build locally, run: - - go build contrib/environment-to-ini/environment-to-ini.go diff --git a/contrib/environment-to-ini/environment-to-ini.go b/contrib/environment-to-ini/environment-to-ini.go deleted file mode 100644 index 5eb576c6feab7..0000000000000 --- a/contrib/environment-to-ini/environment-to-ini.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package main - -import ( - "context" - "os" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" - - "github.com/urfave/cli/v3" -) - -func main() { - app := cli.Command{} - app.Name = "environment-to-ini" - app.Usage = "Use provided environment to update configuration ini" - app.Description = `As a helper to allow docker users to update the gitea configuration - through the environment, this command allows environment variables to - be mapped to values in the ini. - - Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME" - will be mapped to the ini section "[section_name]" and the key - "KEY_NAME" with the value as provided. - - Environment variables of the form "GITEA__SECTION_NAME__KEY_NAME__FILE" - will be mapped to the ini section "[section_name]" and the key - "KEY_NAME" with the value loaded from the specified file. - - Environment variables are usually restricted to a reduced character - set "0-9A-Z_" - in order to allow the setting of sections with - characters outside of that set, they should be escaped as following: - "_0X2E_" for ".". The entire section and key names can be escaped as - a UTF8 byte string if necessary. E.g. to configure: - - """ - ... - [log.console] - COLORIZE=false - STDERR=true - ... - """ - - You would set the environment variables: "GITEA__LOG_0x2E_CONSOLE__COLORIZE=false" - and "GITEA__LOG_0x2E_CONSOLE__STDERR=false". Other examples can be found - on the configuration cheat sheet.` - app.Flags = []cli.Flag{ - &cli.StringFlag{ - Name: "custom-path", - Aliases: []string{"C"}, - Value: setting.CustomPath, - Usage: "Custom path file path", - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Value: setting.CustomConf, - Usage: "Custom configuration file path", - }, - &cli.StringFlag{ - Name: "work-path", - Aliases: []string{"w"}, - Value: setting.AppWorkPath, - Usage: "Set the gitea working path", - }, - &cli.StringFlag{ - Name: "out", - Aliases: []string{"o"}, - Value: "", - Usage: "Destination file to write to", - }, - } - app.Action = runEnvironmentToIni - err := app.Run(context.Background(), os.Args) - if err != nil { - log.Fatal("Failed to run app with %s: %v", os.Args, err) - } -} - -func runEnvironmentToIni(_ context.Context, c *cli.Command) error { - // the config system may change the environment variables, so get a copy first, to be used later - env := append([]string{}, os.Environ()...) - setting.InitWorkPathAndCfgProvider(os.Getenv, setting.ArgWorkPathAndCustomConf{ - WorkPath: c.String("work-path"), - CustomPath: c.String("custom-path"), - CustomConf: c.String("config"), - }) - - cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf) - if err != nil { - log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) - } - - changed := setting.EnvironmentToConfig(cfg, env) - - // try to save the config file - destination := c.String("out") - if len(destination) == 0 { - destination = setting.CustomConf - } - if destination != setting.CustomConf || changed { - log.Info("Settings saved to: %q", destination) - err = cfg.SaveTo(destination) - if err != nil { - return err - } - } - - return nil -} diff --git a/docker/root/usr/local/bin/environment-to-ini b/docker/root/usr/local/bin/environment-to-ini new file mode 100644 index 0000000000000..6c590a9349461 --- /dev/null +++ b/docker/root/usr/local/bin/environment-to-ini @@ -0,0 +1,2 @@ +#!/bin/bash +exec /app/gitea/gitea config update-ini --apply-env "$@" diff --git a/docker/rootless/usr/local/bin/environment-to-ini b/docker/rootless/usr/local/bin/environment-to-ini new file mode 100644 index 0000000000000..6c590a9349461 --- /dev/null +++ b/docker/rootless/usr/local/bin/environment-to-ini @@ -0,0 +1,2 @@ +#!/bin/bash +exec /app/gitea/gitea config update-ini --apply-env "$@" diff --git a/modules/setting/config_provider.go b/modules/setting/config_provider.go index 777885cb4f042..b6f9f07f98e6c 100644 --- a/modules/setting/config_provider.go +++ b/modules/setting/config_provider.go @@ -41,6 +41,7 @@ type ConfigSection interface { HasKey(key string) bool NewKey(name, value string) (ConfigKey, error) Key(key string) ConfigKey + DeleteKey(key string) Keys() []ConfigKey ChildSections() []ConfigSection } @@ -51,6 +52,7 @@ type ConfigProvider interface { Sections() []ConfigSection NewSection(name string) (ConfigSection, error) GetSection(name string) (ConfigSection, error) + DeleteSection(name string) Save() error SaveTo(filename string) error @@ -168,6 +170,10 @@ func (s *iniConfigSection) Keys() (keys []ConfigKey) { return keys } +func (s *iniConfigSection) DeleteKey(key string) { + s.sec.DeleteKey(key) +} + func (s *iniConfigSection) ChildSections() (sections []ConfigSection) { for _, s := range s.sec.ChildSections() { sections = append(sections, &iniConfigSection{s}) @@ -249,6 +255,10 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) { return &iniConfigSection{sec: sec}, nil } +func (p *iniConfigProvider) DeleteSection(name string) { + p.ini.DeleteSection(name) +} + var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save") // Save saves the content into file From c6d18370c10fae911a3759ee41f007d66bc56d54 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 07:02:00 +0800 Subject: [PATCH 2/8] fix abused TempDir --- cmd/config_update_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/config_update_test.go b/cmd/config_update_test.go index 4df0344be3251..23cafda160a9d 100644 --- a/cmd/config_update_test.go +++ b/cmd/config_update_test.go @@ -11,8 +11,9 @@ import ( ) func TestConfigUpdate(t *testing.T) { - configOld := t.TempDir() + "/app-old.ini" - configTemplate := t.TempDir() + "/app-template.ini" + tmpDir := t.TempDir() + configOld := tmpDir + "/app-old.ini" + configTemplate := tmpDir + "/app-template.ini" _ = os.WriteFile(configOld, []byte(` [sec] k1=v1 @@ -30,7 +31,7 @@ k3=v3 t.Setenv("GITEA__EnV__KeY", "val") t.Run("OutputToNewWithEnv", func(t *testing.T) { - configNew := t.TempDir() + "/app-new.ini" + configNew := tmpDir + "/app-new.ini" err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ "./gitea", "--config", configOld, "config", "update-ini", "--apply-env", From 6802be3b1367b31e20559f476bf1bac72c242f3c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 18:07:01 +0800 Subject: [PATCH 3/8] refactor: "edit-ini --in-place --config-keep-keys ..." --- cmd/{config_update.go => config.go} | 39 ++++++++++++------- cmd/{config_update_test.go => config_test.go} | 19 +++++++-- docker/root/usr/local/bin/environment-to-ini | 2 +- .../rootless/usr/local/bin/environment-to-ini | 2 +- 4 files changed, 43 insertions(+), 19 deletions(-) rename cmd/{config_update.go => config.go} (80%) rename cmd/{config_update_test.go => config_test.go} (70%) diff --git a/cmd/config_update.go b/cmd/config.go similarity index 80% rename from cmd/config_update.go rename to cmd/config.go index 44bd12a52d97b..5c1d69fe342e4 100644 --- a/cmd/config_update.go +++ b/cmd/config.go @@ -10,14 +10,12 @@ import ( "os" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" - "github.com/urfave/cli/v3" ) func cmdConfig() *cli.Command { - subcmdConfigUpdateIni := &cli.Command{ - Name: "update-ini", + subcmdConfigEditIni := &cli.Command{ + Name: "edit-ini", Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.", Description: ` Help users to update the gitea configuration INI file: @@ -27,14 +25,14 @@ Help users to update the gitea configuration INI file: # Keep Specified Keys If you need to re-create the configuration file with only a subset of keys, -you can provide an INI template file and use the "--config-key-template" flag. +you can provide an INI template file and use the "--config-keep-keys" flag. For example, if a helm chart needs to reset the settings and only keep SECRET_KEY, -it can use a template file like: +it can use a template file (only keys take effect, values are ignored): [security] SECRET_KEY= -$ ./gitea config update-ini --config app-old.ini --config-key-template app-template.ini --out app-new.ini +$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-template.ini --out app-new.ini # Map Environment Variables to INI Configuration @@ -57,18 +55,22 @@ $ export GITEA__git_0x2E_config__foo_0x2E_bar=val # Put All Together -$ ./gitea config update-ini --config app.ini --config-key-template app-template.ini --apply-env +$ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini --apply-env `, Flags: []cli.Flag{ // "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper &cli.StringFlag{ - Name: "config-key-template", + Name: "config-keep-keys", Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.", }, &cli.BoolFlag{ Name: "apply-env", Usage: "Apply all GITEA__* variables from the environment to the config.", }, + &cli.BoolFlag{ + Name: "in-place", + Usage: "Output to the same config file as input.", + }, &cli.StringFlag{ Name: "out", Usage: "Destination config file to write to. If not set, will overwrite the source config file.", @@ -81,7 +83,7 @@ $ ./gitea config update-ini --config app.ini --config-key-template app-template. Name: "config", Usage: "Manage Gitea configuration", Commands: []*cli.Command{ - subcmdConfigUpdateIni, + subcmdConfigEditIni, }, } } @@ -99,14 +101,25 @@ func runConfigUpdateIni(_ context.Context, c *cli.Command) error { return fmt.Errorf("failed to load config file %q: %v", configFileIn, err) } + inPlace := c.Bool("in-place") configFileOut := c.String("out") - configFileOut = util.IfZero(configFileOut, configFileIn) + if inPlace { + if configFileOut != "" { + return errors.New("cannot use --in-place and --out together") + } + configFileOut = configFileIn + } else { + if configFileOut == "" { + return errors.New("either --in-place or --out must be specified") + } + } + needWriteOut := configFileOut != configFileIn cfgOut := cfgIn - if c.IsSet("config-key-template") { + if c.IsSet("config-keep-keys") { needWriteOut = true - configKeepTemplate := c.String("config-key-template") + configKeepTemplate := c.String("config-keep-keys") cfgOut, err = setting.NewConfigProviderFromFile(configKeepTemplate) if err != nil { return fmt.Errorf("failed to load config keep template file %q: %v", configKeepTemplate, err) diff --git a/cmd/config_update_test.go b/cmd/config_test.go similarity index 70% rename from cmd/config_update_test.go rename to cmd/config_test.go index 23cafda160a9d..884a75347f7a8 100644 --- a/cmd/config_update_test.go +++ b/cmd/config_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestConfigUpdate(t *testing.T) { +func TestConfigEdit(t *testing.T) { tmpDir := t.TempDir() configOld := tmpDir + "/app-old.ini" configTemplate := tmpDir + "/app-template.ini" @@ -33,9 +33,10 @@ k3=v3 t.Run("OutputToNewWithEnv", func(t *testing.T) { configNew := tmpDir + "/app-new.ini" err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ - "./gitea", "--config", configOld, "config", "update-ini", + "./gitea", "--config", configOld, + "config", "edit-ini", "--apply-env", - "--config-key-template", configTemplate, + "--config-keep-keys", configTemplate, "--out", configNew, }) require.NoError(t, err) @@ -54,8 +55,18 @@ KeY = val }) t.Run("OutputToExisting(environment-to-ini)", func(t *testing.T) { + // the legacy "environment-to-ini" (now a wrapper script) behavior: + // if no "--out", then "--in-place" must be used to overwrite the existing "--config" file err := NewMainApp(AppVersion{}).Run(t.Context(), []string{ - "./gitea", "config", "update-ini", + "./gitea", "config", "edit-ini", + "--apply-env", + "--config", configOld, + }) + require.ErrorContains(t, err, "either --in-place or --out must be specified") + + err = NewMainApp(AppVersion{}).Run(t.Context(), []string{ + "./gitea", "config", "edit-ini", + "--in-place", "--apply-env", "--config", configOld, }) diff --git a/docker/root/usr/local/bin/environment-to-ini b/docker/root/usr/local/bin/environment-to-ini index 6c590a9349461..bb0c540685c20 100644 --- a/docker/root/usr/local/bin/environment-to-ini +++ b/docker/root/usr/local/bin/environment-to-ini @@ -1,2 +1,2 @@ #!/bin/bash -exec /app/gitea/gitea config update-ini --apply-env "$@" +exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@" diff --git a/docker/rootless/usr/local/bin/environment-to-ini b/docker/rootless/usr/local/bin/environment-to-ini index 6c590a9349461..bb0c540685c20 100644 --- a/docker/rootless/usr/local/bin/environment-to-ini +++ b/docker/rootless/usr/local/bin/environment-to-ini @@ -1,2 +1,2 @@ #!/bin/bash -exec /app/gitea/gitea config update-ini --apply-env "$@" +exec /app/gitea/gitea config edit-ini --in-place --apply-env "$@" From 3fc0346d72ee50f2620d3f95673a7804c3a61a4b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 18:20:10 +0800 Subject: [PATCH 4/8] fine tune and fix lint --- cmd/config.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 5c1d69fe342e4..6b2f631030d2c 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -69,7 +69,7 @@ $ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini - }, &cli.BoolFlag{ Name: "in-place", - Usage: "Output to the same config file as input.", + Usage: "Output to the same config file as input. This flag will be ignored if --out is set.", }, &cli.StringFlag{ Name: "out", @@ -103,15 +103,11 @@ func runConfigUpdateIni(_ context.Context, c *cli.Command) error { inPlace := c.Bool("in-place") configFileOut := c.String("out") - if inPlace { - if configFileOut != "" { - return errors.New("cannot use --in-place and --out together") - } - configFileOut = configFileIn - } else { - if configFileOut == "" { + if configFileOut == "" { + if !inPlace { return errors.New("either --in-place or --out must be specified") } + configFileOut = configFileIn // in-place edit } needWriteOut := configFileOut != configFileIn From f21a0f8e09403498900c5897f2174899ec8316f2 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 18:30:56 +0800 Subject: [PATCH 5/8] fine tune --- cmd/config.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index 6b2f631030d2c..b01ecd046d417 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -55,10 +55,15 @@ $ export GITEA__git_0x2E_config__foo_0x2E_bar=val # Put All Together -$ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini --apply-env +$ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini --apply-env {--in-place|--out app-new.ini} `, Flags: []cli.Flag{ // "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper + // "--in-place" is also used by "environment-to-ini" script wrapper for its old behavior: always overwrite the existing config file + &cli.BoolFlag{ + Name: "in-place", + Usage: "Output to the same config file as input. This flag will be ignored if --out is set.", + }, &cli.StringFlag{ Name: "config-keep-keys", Usage: "An INI template file containing keys for keeping. Only the keys defined in the INI template will be kept from old config. If not set, all keys will be kept.", @@ -67,13 +72,9 @@ $ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini - Name: "apply-env", Usage: "Apply all GITEA__* variables from the environment to the config.", }, - &cli.BoolFlag{ - Name: "in-place", - Usage: "Output to the same config file as input. This flag will be ignored if --out is set.", - }, &cli.StringFlag{ Name: "out", - Usage: "Destination config file to write to. If not set, will overwrite the source config file.", + Usage: "Destination config file to write to.", }, }, Action: runConfigUpdateIni, @@ -91,6 +92,7 @@ $ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini - func runConfigUpdateIni(_ context.Context, c *cli.Command) error { // the config system may change the environment variables, so get a copy first, to be used later env := append([]string{}, os.Environ()...) + // don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly if !c.IsSet("config") { return errors.New("flag is required but not set: --config") } @@ -144,7 +146,6 @@ func runConfigUpdateIni(_ context.Context, c *cli.Command) error { } if needWriteOut { - _, _ = fmt.Fprintf(c.Writer, "Saving config to: %q\n", configFileOut) err = cfgOut.SaveTo(configFileOut) if err != nil { return err From 70f1c8f0658f13b4cc82b10b503db97a830a7f6d Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 18:37:06 +0800 Subject: [PATCH 6/8] fine tune --- cmd/config.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index b01ecd046d417..58f024830f994 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -18,9 +18,7 @@ func cmdConfig() *cli.Command { Name: "edit-ini", Usage: "Load an existing INI file, apply environment variables, keep specified keys, and output to a new INI file.", Description: ` -Help users to update the gitea configuration INI file: -* Keep specified keys to for the new INI file. -* Map environment variables to values in the INI. +Help users to edit the Gitea configuration INI file. # Keep Specified Keys @@ -115,12 +113,12 @@ func runConfigUpdateIni(_ context.Context, c *cli.Command) error { needWriteOut := configFileOut != configFileIn cfgOut := cfgIn - if c.IsSet("config-keep-keys") { + configKeepKeys := c.String("config-keep-keys") + if configKeepKeys != "" { needWriteOut = true - configKeepTemplate := c.String("config-keep-keys") - cfgOut, err = setting.NewConfigProviderFromFile(configKeepTemplate) + cfgOut, err = setting.NewConfigProviderFromFile(configKeepKeys) if err != nil { - return fmt.Errorf("failed to load config keep template file %q: %v", configKeepTemplate, err) + return fmt.Errorf("failed to load config-keep-keys template file %q: %v", configKeepKeys, err) } for _, secOut := range cfgOut.Sections() { From 614648ee6dc4e36d07179ccf8bc4b81197bce8f2 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Fri, 24 Oct 2025 18:37:29 +0800 Subject: [PATCH 7/8] fix lint --- cmd/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/config.go b/cmd/config.go index 58f024830f994..ccd7ea7d78c45 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -10,6 +10,7 @@ import ( "os" "code.gitea.io/gitea/modules/setting" + "github.com/urfave/cli/v3" ) From 37e9a0aef990a4159d99191c2c9f0374b241a133 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sat, 25 Oct 2025 09:22:43 +0800 Subject: [PATCH 8/8] fine tune (update comment, rename func) --- cmd/config.go | 14 ++++++++------ cmd/config_test.go | 1 + 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index ccd7ea7d78c45..5303b0e1e0758 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -24,14 +24,14 @@ Help users to edit the Gitea configuration INI file. # Keep Specified Keys If you need to re-create the configuration file with only a subset of keys, -you can provide an INI template file and use the "--config-keep-keys" flag. +you can provide an INI template file for the kept keys and use the "--config-keep-keys" flag. For example, if a helm chart needs to reset the settings and only keep SECRET_KEY, it can use a template file (only keys take effect, values are ignored): [security] SECRET_KEY= -$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-template.ini --out app-new.ini +$ ./gitea config edit-ini --config app-old.ini --config-keep-keys app-keys.ini --out app-new.ini # Map Environment Variables to INI Configuration @@ -54,7 +54,7 @@ $ export GITEA__git_0x2E_config__foo_0x2E_bar=val # Put All Together -$ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini --apply-env {--in-place|--out app-new.ini} +$ ./gitea config edit-ini --config app.ini --config-keep-keys app-keys.ini --apply-env {--in-place|--out app-new.ini} `, Flags: []cli.Flag{ // "--config" flag is provided by global flags, and this flag is also used by "environment-to-ini" script wrapper @@ -76,7 +76,7 @@ $ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini - Usage: "Destination config file to write to.", }, }, - Action: runConfigUpdateIni, + Action: runConfigEditIni, } return &cli.Command{ @@ -88,20 +88,22 @@ $ ./gitea config edit-ini --config app.ini --config-keep-keys app-template.ini - } } -func runConfigUpdateIni(_ context.Context, c *cli.Command) error { +func runConfigEditIni(_ context.Context, c *cli.Command) error { // the config system may change the environment variables, so get a copy first, to be used later env := append([]string{}, os.Environ()...) + // don't use the guessed setting.CustomConf, instead, require the user to provide --config explicitly if !c.IsSet("config") { return errors.New("flag is required but not set: --config") } - configFileIn := c.String("config") + cfgIn, err := setting.NewConfigProviderFromFile(configFileIn) if err != nil { return fmt.Errorf("failed to load config file %q: %v", configFileIn, err) } + // determine output config file: use "--out" flag or use "--in-place" flag to overwrite input file inPlace := c.Bool("in-place") configFileOut := c.String("out") if configFileOut == "" { diff --git a/cmd/config_test.go b/cmd/config_test.go index 884a75347f7a8..d123daa6173cf 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -64,6 +64,7 @@ KeY = val }) require.ErrorContains(t, err, "either --in-place or --out must be specified") + // simulate the "environment-to-ini" behavior with "--in-place" err = NewMainApp(AppVersion{}).Run(t.Context(), []string{ "./gitea", "config", "edit-ini", "--in-place",