mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Avoid polluting config file when "save" (#25395)
That's a longstanding INI package problem: the "MustXxx" calls change the option values, and the following "Save" will save a lot of garbage options into the user's config file. Ideally we should refactor the INI package to a clear solution, but it's a huge work. A clear workaround is what this PR does: when "Save", load a clear INI instance and save it. Partially fix #25377, the "install" page needs more fine tunes.
This commit is contained in:
		
							
								
								
									
										12
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -217,9 +217,15 @@ func setPort(port string) error {
 | 
			
		||||
		defaultLocalURL += ":" + setting.HTTPPort + "/"
 | 
			
		||||
 | 
			
		||||
		// Save LOCAL_ROOT_URL if port changed
 | 
			
		||||
		setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
 | 
			
		||||
		if err := setting.CfgProvider.Save(); err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to save config file: %v", err)
 | 
			
		||||
		rootCfg := setting.CfgProvider
 | 
			
		||||
		saveCfg, err := rootCfg.PrepareSaving()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to save config file: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		rootCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
 | 
			
		||||
		saveCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
 | 
			
		||||
		if err = saveCfg.Save(); err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to save config file: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@
 | 
			
		||||
package setting
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
@@ -51,11 +52,17 @@ type ConfigProvider interface {
 | 
			
		||||
	GetSection(name string) (ConfigSection, error)
 | 
			
		||||
	Save() error
 | 
			
		||||
	SaveTo(filename string) error
 | 
			
		||||
 | 
			
		||||
	DisableSaving()
 | 
			
		||||
	PrepareSaving() (ConfigProvider, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type iniConfigProvider struct {
 | 
			
		||||
	opts    *Options
 | 
			
		||||
	ini     *ini.File
 | 
			
		||||
	opts *Options
 | 
			
		||||
	ini  *ini.File
 | 
			
		||||
 | 
			
		||||
	disableSaving bool
 | 
			
		||||
 | 
			
		||||
	newFile bool // whether the file has not existed previously
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -191,7 +198,7 @@ type Options struct {
 | 
			
		||||
// NewConfigProviderFromFile load configuration from file.
 | 
			
		||||
// NOTE: do not print any log except error.
 | 
			
		||||
func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) {
 | 
			
		||||
	cfg := ini.Empty()
 | 
			
		||||
	cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "})
 | 
			
		||||
	newFile := true
 | 
			
		||||
 | 
			
		||||
	if opts.CustomConf != "" {
 | 
			
		||||
@@ -252,8 +259,13 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
 | 
			
		||||
	return &iniConfigSection{sec: sec}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save")
 | 
			
		||||
 | 
			
		||||
// Save saves the content into file
 | 
			
		||||
func (p *iniConfigProvider) Save() error {
 | 
			
		||||
	if p.disableSaving {
 | 
			
		||||
		return errDisableSaving
 | 
			
		||||
	}
 | 
			
		||||
	filename := p.opts.CustomConf
 | 
			
		||||
	if filename == "" {
 | 
			
		||||
		if !p.opts.AllowEmpty {
 | 
			
		||||
@@ -285,9 +297,29 @@ func (p *iniConfigProvider) Save() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *iniConfigProvider) SaveTo(filename string) error {
 | 
			
		||||
	if p.disableSaving {
 | 
			
		||||
		return errDisableSaving
 | 
			
		||||
	}
 | 
			
		||||
	return p.ini.SaveTo(filename)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DisableSaving disables the saving function, use PrepareSaving to get clear config options.
 | 
			
		||||
func (p *iniConfigProvider) DisableSaving() {
 | 
			
		||||
	p.disableSaving = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrepareSaving loads the ini from file again to get clear config options.
 | 
			
		||||
// Otherwise, the "MustXxx" calls would have polluted the current config provider,
 | 
			
		||||
// it makes the "Save" outputs a lot of garbage options
 | 
			
		||||
// After the INI package gets refactored, no "MustXxx" pollution, this workaround can be dropped.
 | 
			
		||||
func (p *iniConfigProvider) PrepareSaving() (ConfigProvider, error) {
 | 
			
		||||
	cfgFile := p.opts.CustomConf
 | 
			
		||||
	if cfgFile == "" {
 | 
			
		||||
		return nil, errors.New("no config file to save")
 | 
			
		||||
	}
 | 
			
		||||
	return NewConfigProviderFromFile(p.opts)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
 | 
			
		||||
	if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
 | 
			
		||||
		log.Fatal("Failed to map %s settings: %v", sectionName, err)
 | 
			
		||||
 
 | 
			
		||||
@@ -84,11 +84,11 @@ func TestNewConfigProviderFromFile(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	bs, err := os.ReadFile(testFile)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "[foo]\nk1=a\n", string(bs))
 | 
			
		||||
	assert.Equal(t, "[foo]\nk1 = a\n", string(bs))
 | 
			
		||||
 | 
			
		||||
	bs, err = os.ReadFile(testFile1)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "[foo]\nk1=a\nk2=b\n", string(bs))
 | 
			
		||||
	assert.Equal(t, "[foo]\nk1 = a\nk2 = b\n", string(bs))
 | 
			
		||||
 | 
			
		||||
	// load existing file and save
 | 
			
		||||
	cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
 | 
			
		||||
@@ -99,7 +99,7 @@ func TestNewConfigProviderFromFile(t *testing.T) {
 | 
			
		||||
	assert.NoError(t, cfg.Save())
 | 
			
		||||
	bs, err = os.ReadFile(testFile)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "[foo]\nk1=a\n\n[bar]\nk1=b\n", string(bs))
 | 
			
		||||
	assert.Equal(t, "[foo]\nk1 = a\n\n[bar]\nk1 = b\n", string(bs))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestNewConfigProviderForLocale(t *testing.T) {
 | 
			
		||||
@@ -119,3 +119,27 @@ func TestNewConfigProviderForLocale(t *testing.T) {
 | 
			
		||||
	assert.Equal(t, "foo", cfg.Section("").Key("k1").String())
 | 
			
		||||
	assert.Equal(t, "xxx", cfg.Section("").Key("k2").String())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDisableSaving(t *testing.T) {
 | 
			
		||||
	testFile := t.TempDir() + "/test.ini"
 | 
			
		||||
	_ = os.WriteFile(testFile, []byte("k1=a\nk2=b"), 0o644)
 | 
			
		||||
	cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	cfg.DisableSaving()
 | 
			
		||||
	err = cfg.Save()
 | 
			
		||||
	assert.ErrorIs(t, err, errDisableSaving)
 | 
			
		||||
 | 
			
		||||
	saveCfg, err := cfg.PrepareSaving()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	saveCfg.Section("").Key("k1").MustString("x")
 | 
			
		||||
	saveCfg.Section("").Key("k2").SetValue("y")
 | 
			
		||||
	saveCfg.Section("").Key("k3").SetValue("z")
 | 
			
		||||
	err = saveCfg.Save()
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
	bs, err := os.ReadFile(testFile)
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Equal(t, "k1 = a\nk2 = y\nk3 = z\n", string(bs))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -59,13 +59,18 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
 | 
			
		||||
	if err != nil || n != 32 {
 | 
			
		||||
		LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Error generating JWT Secret for custom config: %v", err)
 | 
			
		||||
			return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Save secret
 | 
			
		||||
		sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
 | 
			
		||||
		if err := rootCfg.Save(); err != nil {
 | 
			
		||||
			return fmt.Errorf("Error saving JWT Secret for custom config: %v", err)
 | 
			
		||||
		saveCfg, err := rootCfg.PrepareSaving()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
 | 
			
		||||
		saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
 | 
			
		||||
		if err := saveCfg.Save(); err != nil {
 | 
			
		||||
			return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -130,8 +130,13 @@ func loadOAuth2From(rootCfg ConfigProvider) {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			secretBase64 := base64.RawURLEncoding.EncodeToString(key)
 | 
			
		||||
			saveCfg, err := rootCfg.PrepareSaving()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
 | 
			
		||||
			if err := rootCfg.Save(); err != nil {
 | 
			
		||||
			saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
 | 
			
		||||
			if err := saveCfg.Save(); err != nil {
 | 
			
		||||
				log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -89,8 +89,13 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	InternalToken = token
 | 
			
		||||
	saveCfg, err := rootCfg.PrepareSaving()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("Error saving internal token: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	rootCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
 | 
			
		||||
	if err := rootCfg.Save(); err != nil {
 | 
			
		||||
	saveCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
 | 
			
		||||
	if err = saveCfg.Save(); err != nil {
 | 
			
		||||
		log.Fatal("Error saving internal token: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -202,6 +202,7 @@ func Init(opts *Options) {
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	CfgProvider, err = NewConfigProviderFromFile(opts)
 | 
			
		||||
	CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("newConfigProviderFromFile[%v]: %v", opts, err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -214,7 +215,7 @@ func Init(opts *Options) {
 | 
			
		||||
 | 
			
		||||
// loadCommonSettingsFrom loads common configurations from a configuration provider.
 | 
			
		||||
func loadCommonSettingsFrom(cfg ConfigProvider) error {
 | 
			
		||||
	// WARNNING: don't change the sequence except you know what you are doing.
 | 
			
		||||
	// WARNING: don't change the sequence except you know what you are doing.
 | 
			
		||||
	loadRunModeFrom(cfg)
 | 
			
		||||
	loadLogGlobalFrom(cfg)
 | 
			
		||||
	loadServerFrom(cfg)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user