2023-02-20 00:12:01 +08:00
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
2023-06-21 10:31:40 +08:00
"errors"
2023-04-25 23:06:39 +08:00
"fmt"
"os"
"path/filepath"
2023-06-14 11:42:38 +08:00
"strconv"
2023-04-25 23:06:39 +08:00
"strings"
2023-06-02 17:27:30 +08:00
"time"
2023-04-25 23:06:39 +08:00
2023-02-20 00:12:01 +08:00
"code.gitea.io/gitea/modules/log"
2023-04-25 23:06:39 +08:00
"code.gitea.io/gitea/modules/util"
2023-02-20 00:12:01 +08:00
2025-06-27 15:48:03 +02:00
"gopkg.in/ini.v1" //nolint:depguard // wrapper for this package
2023-02-20 00:12:01 +08:00
)
2023-06-02 17:27:30 +08:00
type ConfigKey interface {
Name ( ) string
Value ( ) string
SetValue ( v string )
In ( defaultVal string , candidates [ ] string ) string
String ( ) string
Strings ( delim string ) [ ] string
2025-03-30 13:26:19 +08:00
Bool ( ) ( bool , error )
2023-06-02 17:27:30 +08:00
MustString ( defaultVal string ) string
MustBool ( defaultVal ... bool ) bool
MustInt ( defaultVal ... int ) int
MustInt64 ( defaultVal ... int64 ) int64
MustDuration ( defaultVal ... time . Duration ) time . Duration
}
2023-04-25 23:06:39 +08:00
type ConfigSection interface {
Name ( ) string
2023-06-02 17:27:30 +08:00
MapTo ( any ) error
2023-04-25 23:06:39 +08:00
HasKey ( key string ) bool
2023-06-02 17:27:30 +08:00
NewKey ( name , value string ) ( ConfigKey , error )
Key ( key string ) ConfigKey
2025-10-25 10:54:55 +08:00
DeleteKey ( key string )
2023-06-02 17:27:30 +08:00
Keys ( ) [ ] ConfigKey
ChildSections ( ) [ ] ConfigSection
2023-04-25 23:06:39 +08:00
}
2023-02-20 00:12:01 +08:00
// ConfigProvider represents a config provider
type ConfigProvider interface {
2023-04-25 23:06:39 +08:00
Section ( section string ) ConfigSection
2023-06-02 17:27:30 +08:00
Sections ( ) [ ] ConfigSection
2023-04-25 23:06:39 +08:00
NewSection ( name string ) ( ConfigSection , error )
GetSection ( name string ) ( ConfigSection , error )
2025-10-25 10:54:55 +08:00
DeleteSection ( name string )
2023-04-25 23:06:39 +08:00
Save ( ) error
2023-06-02 17:27:30 +08:00
SaveTo ( filename string ) error
2023-06-21 10:31:40 +08:00
DisableSaving ( )
PrepareSaving ( ) ( ConfigProvider , error )
2023-06-21 13:50:26 +08:00
IsLoadedFromEmpty ( ) bool
2023-06-02 17:27:30 +08:00
}
type iniConfigProvider struct {
2023-06-21 13:50:26 +08:00
file string
2023-06-21 10:31:40 +08:00
ini * ini . File
2023-06-21 13:50:26 +08:00
disableSaving bool // disable the "Save" method because the config options could be polluted
loadedFromEmpty bool // whether the file has not existed previously
2023-06-02 17:27:30 +08:00
}
type iniConfigSection struct {
sec * ini . Section
2023-04-25 23:06:39 +08:00
}
2023-06-02 17:27:30 +08:00
var (
_ ConfigProvider = ( * iniConfigProvider ) ( nil )
_ ConfigSection = ( * iniConfigSection ) ( nil )
_ ConfigKey = ( * ini . Key ) ( nil )
)
2023-05-22 06:35:11 +08:00
// ConfigSectionKey only searches the keys in the given section, but it is O(n).
// ini package has a special behavior: with "[sec] a=1" and an empty "[sec.sub]",
// then in "[sec.sub]", Key()/HasKey() can always see "a=1" because it always tries parent sections.
// It returns nil if the key doesn't exist.
2023-06-02 17:27:30 +08:00
func ConfigSectionKey ( sec ConfigSection , key string ) ConfigKey {
2023-05-22 06:35:11 +08:00
if sec == nil {
return nil
}
for _ , k := range sec . Keys ( ) {
if k . Name ( ) == key {
return k
}
}
return nil
}
func ConfigSectionKeyString ( sec ConfigSection , key string , def ... string ) string {
k := ConfigSectionKey ( sec , key )
if k != nil && k . String ( ) != "" {
return k . String ( )
}
if len ( def ) > 0 {
return def [ 0 ]
}
return ""
}
2023-06-14 11:42:38 +08:00
func ConfigSectionKeyBool ( sec ConfigSection , key string , def ... bool ) bool {
k := ConfigSectionKey ( sec , key )
if k != nil && k . String ( ) != "" {
b , _ := strconv . ParseBool ( k . String ( ) )
return b
}
if len ( def ) > 0 {
return def [ 0 ]
}
return false
}
2023-05-22 06:35:11 +08:00
// ConfigInheritedKey works like ini.Section.Key(), but it always returns a new key instance, it is O(n) because NewKey is O(n)
// and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values.
// Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys.
// It never returns nil.
2023-06-02 17:27:30 +08:00
func ConfigInheritedKey ( sec ConfigSection , key string ) ConfigKey {
2023-05-22 06:35:11 +08:00
k := sec . Key ( key )
if k != nil && k . String ( ) != "" {
newKey , _ := sec . NewKey ( k . Name ( ) , k . String ( ) )
return newKey
}
newKey , _ := sec . NewKey ( key , "" )
return newKey
}
func ConfigInheritedKeyString ( sec ConfigSection , key string , def ... string ) string {
k := sec . Key ( key )
if k != nil && k . String ( ) != "" {
return k . String ( )
}
if len ( def ) > 0 {
return def [ 0 ]
}
return ""
}
2023-06-02 17:27:30 +08:00
func ( s * iniConfigSection ) Name ( ) string {
return s . sec . Name ( )
}
func ( s * iniConfigSection ) MapTo ( v any ) error {
return s . sec . MapTo ( v )
}
func ( s * iniConfigSection ) HasKey ( key string ) bool {
return s . sec . HasKey ( key )
}
func ( s * iniConfigSection ) NewKey ( name , value string ) ( ConfigKey , error ) {
return s . sec . NewKey ( name , value )
}
func ( s * iniConfigSection ) Key ( key string ) ConfigKey {
return s . sec . Key ( key )
}
func ( s * iniConfigSection ) Keys ( ) ( keys [ ] ConfigKey ) {
for _ , k := range s . sec . Keys ( ) {
keys = append ( keys , k )
}
return keys
2023-04-25 23:06:39 +08:00
}
2025-10-25 10:54:55 +08:00
func ( s * iniConfigSection ) DeleteKey ( key string ) {
s . sec . DeleteKey ( key )
}
2023-06-02 17:27:30 +08:00
func ( s * iniConfigSection ) ChildSections ( ) ( sections [ ] ConfigSection ) {
for _ , s := range s . sec . ChildSections ( ) {
sections = append ( sections , & iniConfigSection { s } )
}
return sections
}
2023-09-11 00:15:51 +08:00
func configProviderLoadOptions ( ) ini . LoadOptions {
return ini . LoadOptions {
KeyValueDelimiterOnWrite : " = " ,
IgnoreContinuation : true ,
}
}
2023-06-02 17:27:30 +08:00
// NewConfigProviderFromData this function is mainly for testing purpose
2023-05-08 19:49:59 +08:00
func NewConfigProviderFromData ( configContent string ) ( ConfigProvider , error ) {
2023-09-11 00:15:51 +08:00
cfg , err := ini . LoadSources ( configProviderLoadOptions ( ) , strings . NewReader ( configContent ) )
2023-06-02 17:27:30 +08:00
if err != nil {
return nil , err
2023-04-25 23:06:39 +08:00
}
cfg . NameMapper = ini . SnackCase
2023-06-02 17:27:30 +08:00
return & iniConfigProvider {
2023-06-21 13:50:26 +08:00
ini : cfg ,
loadedFromEmpty : true ,
2023-04-25 23:06:39 +08:00
} , nil
}
2023-06-02 17:27:30 +08:00
// NewConfigProviderFromFile load configuration from file.
2023-04-25 23:06:39 +08:00
// NOTE: do not print any log except error.
2024-02-08 20:31:38 +08:00
func NewConfigProviderFromFile ( file string ) ( ConfigProvider , error ) {
2023-09-11 00:15:51 +08:00
cfg := ini . Empty ( configProviderLoadOptions ( ) )
2023-06-21 13:50:26 +08:00
loadedFromEmpty := true
2023-04-25 23:06:39 +08:00
2023-06-21 13:50:26 +08:00
if file != "" {
2025-10-21 02:43:08 +08:00
isExist , err := util . IsExist ( file )
2023-04-25 23:06:39 +08:00
if err != nil {
2025-10-21 02:43:08 +08:00
return nil , fmt . Errorf ( "unable to check if %q exists: %v" , file , err )
2023-04-25 23:06:39 +08:00
}
2025-10-21 02:43:08 +08:00
if isExist {
2023-06-21 13:50:26 +08:00
if err = cfg . Append ( file ) ; err != nil {
return nil , fmt . Errorf ( "failed to load config file %q: %v" , file , err )
2023-04-25 23:06:39 +08:00
}
2023-06-21 13:50:26 +08:00
loadedFromEmpty = false
2023-04-25 23:06:39 +08:00
}
}
cfg . NameMapper = ini . SnackCase
2023-06-02 17:27:30 +08:00
return & iniConfigProvider {
2023-06-21 13:50:26 +08:00
file : file ,
ini : cfg ,
loadedFromEmpty : loadedFromEmpty ,
2023-04-25 23:06:39 +08:00
} , nil
}
2023-06-02 17:27:30 +08:00
func ( p * iniConfigProvider ) Section ( section string ) ConfigSection {
return & iniConfigSection { sec : p . ini . Section ( section ) }
}
func ( p * iniConfigProvider ) Sections ( ) ( sections [ ] ConfigSection ) {
for _ , s := range p . ini . Sections ( ) {
sections = append ( sections , & iniConfigSection { s } )
}
return sections
2023-04-25 23:06:39 +08:00
}
2023-06-02 17:27:30 +08:00
func ( p * iniConfigProvider ) NewSection ( name string ) ( ConfigSection , error ) {
sec , err := p . ini . NewSection ( name )
if err != nil {
return nil , err
}
return & iniConfigSection { sec : sec } , nil
2023-04-25 23:06:39 +08:00
}
2023-06-02 17:27:30 +08:00
func ( p * iniConfigProvider ) GetSection ( name string ) ( ConfigSection , error ) {
sec , err := p . ini . GetSection ( name )
if err != nil {
return nil , err
}
return & iniConfigSection { sec : sec } , nil
2023-04-25 23:06:39 +08:00
}
2025-10-25 10:54:55 +08:00
func ( p * iniConfigProvider ) DeleteSection ( name string ) {
p . ini . DeleteSection ( name )
}
2023-06-21 10:31:40 +08:00
var errDisableSaving = errors . New ( "this config can't be saved, developers should prepare a new config to save" )
2023-06-02 17:27:30 +08:00
// Save saves the content into file
func ( p * iniConfigProvider ) Save ( ) error {
2023-06-21 10:31:40 +08:00
if p . disableSaving {
return errDisableSaving
}
2023-06-21 13:50:26 +08:00
filename := p . file
2023-06-02 17:27:30 +08:00
if filename == "" {
2025-04-01 12:14:01 +02:00
return errors . New ( "config file path must not be empty" )
2023-04-25 23:06:39 +08:00
}
2023-06-21 13:50:26 +08:00
if p . loadedFromEmpty {
2023-06-02 17:27:30 +08:00
if err := os . MkdirAll ( filepath . Dir ( filename ) , os . ModePerm ) ; err != nil {
2023-06-21 13:50:26 +08:00
return fmt . Errorf ( "failed to create %q: %v" , filename , err )
2023-04-25 23:06:39 +08:00
}
}
2023-06-02 17:27:30 +08:00
if err := p . ini . SaveTo ( filename ) ; err != nil {
2023-06-21 13:50:26 +08:00
return fmt . Errorf ( "failed to save %q: %v" , filename , err )
2023-04-25 23:06:39 +08:00
}
// Change permissions to be more restrictive
2023-06-02 17:27:30 +08:00
fi , err := os . Stat ( filename )
2023-04-25 23:06:39 +08:00
if err != nil {
return fmt . Errorf ( "failed to determine current conf file permissions: %v" , err )
}
if fi . Mode ( ) . Perm ( ) > 0 o600 {
2023-06-02 17:27:30 +08:00
if err = os . Chmod ( filename , 0 o600 ) ; err != nil {
2023-04-25 23:06:39 +08:00
log . Warn ( "Failed changing conf file permissions to -rw-------. Consider changing them manually." )
}
}
return nil
2023-02-20 00:12:01 +08:00
}
2023-06-02 17:27:30 +08:00
func ( p * iniConfigProvider ) SaveTo ( filename string ) error {
2023-06-21 10:31:40 +08:00
if p . disableSaving {
return errDisableSaving
}
2023-06-02 17:27:30 +08:00
return p . ini . SaveTo ( filename )
}
2023-02-20 00:12:01 +08:00
2023-06-21 10:31:40 +08:00
// 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 ) {
2023-06-21 13:50:26 +08:00
if p . file == "" {
2023-06-21 10:31:40 +08:00
return nil , errors . New ( "no config file to save" )
}
2023-06-21 13:50:26 +08:00
return NewConfigProviderFromFile ( p . file )
}
func ( p * iniConfigProvider ) IsLoadedFromEmpty ( ) bool {
return p . loadedFromEmpty
2023-06-21 10:31:40 +08:00
}
2023-06-02 17:27:30 +08:00
func mustMapSetting ( rootCfg ConfigProvider , sectionName string , setting any ) {
2023-02-20 00:12:01 +08:00
if err := rootCfg . Section ( sectionName ) . MapTo ( setting ) ; err != nil {
log . Fatal ( "Failed to map %s settings: %v" , sectionName , err )
}
}
2024-04-07 09:11:25 +08:00
// StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
var StartupProblems [ ] string
2024-04-24 00:18:41 +08:00
func LogStartupProblem ( skip int , level log . Level , format string , args ... any ) {
2024-04-07 09:11:25 +08:00
msg := fmt . Sprintf ( format , args ... )
log . Log ( skip + 1 , level , "%s" , msg )
StartupProblems = append ( StartupProblems , msg )
}
2023-06-14 11:42:38 +08:00
2023-07-26 11:53:37 +08:00
func deprecatedSetting ( rootCfg ConfigProvider , oldSection , oldKey , newSection , newKey , version string ) {
2023-06-14 11:42:38 +08:00
if rootCfg . Section ( oldSection ) . HasKey ( oldKey ) {
2025-12-09 16:14:05 +01:00
LogStartupProblem ( 1 , log . ERROR , "Deprecation: config option `[%s].%s` present, please use `[%s].%s` instead because this fallback will be/has been removed in %s" , oldSection , oldKey , newSection , newKey , version )
2023-02-20 00:12:01 +08:00
}
}
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
func deprecatedSettingDB ( rootCfg ConfigProvider , oldSection , oldKey string ) {
if rootCfg . Section ( oldSection ) . HasKey ( oldKey ) {
2025-12-09 16:14:05 +01:00
LogStartupProblem ( 1 , log . ERROR , "Deprecation: config option `[%s].%s` present but it won't take effect because it has been moved to admin panel -> config setting" , oldSection , oldKey )
2023-02-20 00:12:01 +08:00
}
}
2023-06-02 17:27:30 +08:00
func init ( ) {
ini . PrettyFormat = false
}