code.gitea.io/gitea@v1.19.3/modules/setting/setting.go (about)

     1  // Copyright 2014 The Gogs Authors. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package setting
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"os"
    11  	"os/exec"
    12  	"path"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"code.gitea.io/gitea/modules/auth/password/hash"
    20  	"code.gitea.io/gitea/modules/log"
    21  	"code.gitea.io/gitea/modules/user"
    22  	"code.gitea.io/gitea/modules/util"
    23  
    24  	ini "gopkg.in/ini.v1"
    25  )
    26  
    27  // settings
    28  var (
    29  	// AppVer is the version of the current build of Gitea. It is set in main.go from main.Version.
    30  	AppVer string
    31  	// AppBuiltWith represents a human readable version go runtime build version and build tags. (See main.go formatBuiltWith().)
    32  	AppBuiltWith string
    33  	// AppStartTime store time gitea has started
    34  	AppStartTime time.Time
    35  
    36  	// AppPath represents the path to the gitea binary
    37  	AppPath string
    38  	// AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR.
    39  	// If that is not set it is the default set here by the linker or failing that the directory of AppPath.
    40  	//
    41  	// AppWorkPath is used as the base path for several other paths.
    42  	AppWorkPath string
    43  
    44  	// Global setting objects
    45  	CfgProvider  ConfigProvider
    46  	CustomPath   string // Custom directory path
    47  	CustomConf   string
    48  	PIDFile      = "/run/gitea.pid"
    49  	WritePIDFile bool
    50  	RunMode      string
    51  	RunUser      string
    52  	IsProd       bool
    53  	IsWindows    bool
    54  )
    55  
    56  func getAppPath() (string, error) {
    57  	var appPath string
    58  	var err error
    59  	if IsWindows && filepath.IsAbs(os.Args[0]) {
    60  		appPath = filepath.Clean(os.Args[0])
    61  	} else {
    62  		appPath, err = exec.LookPath(os.Args[0])
    63  	}
    64  
    65  	if err != nil {
    66  		if !errors.Is(err, exec.ErrDot) {
    67  			return "", err
    68  		}
    69  		appPath, err = filepath.Abs(os.Args[0])
    70  	}
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  	appPath, err = filepath.Abs(appPath)
    75  	if err != nil {
    76  		return "", err
    77  	}
    78  	// Note: we don't use path.Dir here because it does not handle case
    79  	//	which path starts with two "/" in Windows: "//psf/Home/..."
    80  	return strings.ReplaceAll(appPath, "\\", "/"), err
    81  }
    82  
    83  func getWorkPath(appPath string) string {
    84  	workPath := AppWorkPath
    85  
    86  	if giteaWorkPath, ok := os.LookupEnv("GITEA_WORK_DIR"); ok {
    87  		workPath = giteaWorkPath
    88  	}
    89  	if len(workPath) == 0 {
    90  		i := strings.LastIndex(appPath, "/")
    91  		if i == -1 {
    92  			workPath = appPath
    93  		} else {
    94  			workPath = appPath[:i]
    95  		}
    96  	}
    97  	workPath = strings.ReplaceAll(workPath, "\\", "/")
    98  	if !filepath.IsAbs(workPath) {
    99  		log.Info("Provided work path %s is not absolute - will be made absolute against the current working directory", workPath)
   100  
   101  		absPath, err := filepath.Abs(workPath)
   102  		if err != nil {
   103  			log.Error("Unable to absolute %s against the current working directory %v. Will absolute against the AppPath %s", workPath, err, appPath)
   104  			workPath = filepath.Join(appPath, workPath)
   105  		} else {
   106  			workPath = absPath
   107  		}
   108  	}
   109  	return strings.ReplaceAll(workPath, "\\", "/")
   110  }
   111  
   112  func init() {
   113  	IsWindows = runtime.GOOS == "windows"
   114  	// We can rely on log.CanColorStdout being set properly because modules/log/console_windows.go comes before modules/setting/setting.go lexicographically
   115  	// By default set this logger at Info - we'll change it later but we need to start with something.
   116  	log.NewLogger(0, "console", "console", fmt.Sprintf(`{"level": "info", "colorize": %t, "stacktraceLevel": "none"}`, log.CanColorStdout))
   117  
   118  	var err error
   119  	if AppPath, err = getAppPath(); err != nil {
   120  		log.Fatal("Failed to get app path: %v", err)
   121  	}
   122  	AppWorkPath = getWorkPath(AppPath)
   123  }
   124  
   125  func forcePathSeparator(path string) {
   126  	if strings.Contains(path, "\\") {
   127  		log.Fatal("Do not use '\\' or '\\\\' in paths, instead, please use '/' in all places")
   128  	}
   129  }
   130  
   131  // IsRunUserMatchCurrentUser returns false if configured run user does not match
   132  // actual user that runs the app. The first return value is the actual user name.
   133  // This check is ignored under Windows since SSH remote login is not the main
   134  // method to login on Windows.
   135  func IsRunUserMatchCurrentUser(runUser string) (string, bool) {
   136  	if IsWindows || SSH.StartBuiltinServer {
   137  		return "", true
   138  	}
   139  
   140  	currentUser := user.CurrentUsername()
   141  	return currentUser, runUser == currentUser
   142  }
   143  
   144  func createPIDFile(pidPath string) {
   145  	currentPid := os.Getpid()
   146  	if err := os.MkdirAll(filepath.Dir(pidPath), os.ModePerm); err != nil {
   147  		log.Fatal("Failed to create PID folder: %v", err)
   148  	}
   149  
   150  	file, err := os.Create(pidPath)
   151  	if err != nil {
   152  		log.Fatal("Failed to create PID file: %v", err)
   153  	}
   154  	defer file.Close()
   155  	if _, err := file.WriteString(strconv.FormatInt(int64(currentPid), 10)); err != nil {
   156  		log.Fatal("Failed to write PID information: %v", err)
   157  	}
   158  }
   159  
   160  // SetCustomPathAndConf will set CustomPath and CustomConf with reference to the
   161  // GITEA_CUSTOM environment variable and with provided overrides before stepping
   162  // back to the default
   163  func SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath string) {
   164  	if len(providedWorkPath) != 0 {
   165  		AppWorkPath = filepath.ToSlash(providedWorkPath)
   166  	}
   167  	if giteaCustom, ok := os.LookupEnv("GITEA_CUSTOM"); ok {
   168  		CustomPath = giteaCustom
   169  	}
   170  	if len(providedCustom) != 0 {
   171  		CustomPath = providedCustom
   172  	}
   173  	if len(CustomPath) == 0 {
   174  		CustomPath = path.Join(AppWorkPath, "custom")
   175  	} else if !filepath.IsAbs(CustomPath) {
   176  		CustomPath = path.Join(AppWorkPath, CustomPath)
   177  	}
   178  
   179  	if len(providedConf) != 0 {
   180  		CustomConf = providedConf
   181  	}
   182  	if len(CustomConf) == 0 {
   183  		CustomConf = path.Join(CustomPath, "conf/app.ini")
   184  	} else if !filepath.IsAbs(CustomConf) {
   185  		CustomConf = path.Join(CustomPath, CustomConf)
   186  		log.Warn("Using 'custom' directory as relative origin for configuration file: '%s'", CustomConf)
   187  	}
   188  }
   189  
   190  // PrepareAppDataPath creates app data directory if necessary
   191  func PrepareAppDataPath() error {
   192  	// FIXME: There are too many calls to MkdirAll in old code. It is incorrect.
   193  	// For example, if someDir=/mnt/vol1/gitea-home/data, if the mount point /mnt/vol1 is not mounted when Gitea runs,
   194  	// then gitea will make new empty directories in /mnt/vol1, all are stored in the root filesystem.
   195  	// The correct behavior should be: creating parent directories is end users' duty. We only create sub-directories in existing parent directories.
   196  	// For quickstart, the parent directories should be created automatically for first startup (eg: a flag or a check of INSTALL_LOCK).
   197  	// Now we can take the first step to do correctly (using Mkdir) in other packages, and prepare the AppDataPath here, then make a refactor in future.
   198  
   199  	st, err := os.Stat(AppDataPath)
   200  	if os.IsNotExist(err) {
   201  		err = os.MkdirAll(AppDataPath, os.ModePerm)
   202  		if err != nil {
   203  			return fmt.Errorf("unable to create the APP_DATA_PATH directory: %q, Error: %w", AppDataPath, err)
   204  		}
   205  		return nil
   206  	}
   207  
   208  	if err != nil {
   209  		return fmt.Errorf("unable to use APP_DATA_PATH %q. Error: %w", AppDataPath, err)
   210  	}
   211  
   212  	if !st.IsDir() /* also works for symlink */ {
   213  		return fmt.Errorf("the APP_DATA_PATH %q is not a directory (or symlink to a directory) and can't be used", AppDataPath)
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  // InitProviderFromExistingFile initializes config provider from an existing config file (app.ini)
   220  func InitProviderFromExistingFile() {
   221  	CfgProvider = newFileProviderFromConf(CustomConf, WritePIDFile, false, PIDFile, "")
   222  }
   223  
   224  // InitProviderAllowEmpty initializes config provider from file, it's also fine that if the config file (app.ini) doesn't exist
   225  func InitProviderAllowEmpty() {
   226  	CfgProvider = newFileProviderFromConf(CustomConf, WritePIDFile, true, PIDFile, "")
   227  }
   228  
   229  // InitProviderAndLoadCommonSettingsForTest initializes config provider and load common setttings for tests
   230  func InitProviderAndLoadCommonSettingsForTest(extraConfigs ...string) {
   231  	CfgProvider = newFileProviderFromConf(CustomConf, WritePIDFile, true, PIDFile, strings.Join(extraConfigs, "\n"))
   232  	loadCommonSettingsFrom(CfgProvider)
   233  	if err := PrepareAppDataPath(); err != nil {
   234  		log.Fatal("Can not prepare APP_DATA_PATH: %v", err)
   235  	}
   236  	// register the dummy hash algorithm function used in the test fixtures
   237  	_ = hash.Register("dummy", hash.NewDummyHasher)
   238  
   239  	PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
   240  }
   241  
   242  // newFileProviderFromConf initializes configuration context.
   243  // NOTE: do not print any log except error.
   244  func newFileProviderFromConf(customConf string, writePIDFile, allowEmpty bool, pidFile, extraConfig string) *ini.File {
   245  	cfg := ini.Empty()
   246  
   247  	if writePIDFile && len(pidFile) > 0 {
   248  		createPIDFile(pidFile)
   249  	}
   250  
   251  	isFile, err := util.IsFile(customConf)
   252  	if err != nil {
   253  		log.Error("Unable to check if %s is a file. Error: %v", customConf, err)
   254  	}
   255  	if isFile {
   256  		if err := cfg.Append(customConf); err != nil {
   257  			log.Fatal("Failed to load custom conf '%s': %v", customConf, err)
   258  		}
   259  	} else if !allowEmpty {
   260  		log.Fatal("Unable to find configuration file: %q.\nEnsure you are running in the correct environment or set the correct configuration file with -c.", CustomConf)
   261  	} // else: no config file, a config file might be created at CustomConf later (might not)
   262  
   263  	if extraConfig != "" {
   264  		if err = cfg.Append([]byte(extraConfig)); err != nil {
   265  			log.Fatal("Unable to append more config: %v", err)
   266  		}
   267  	}
   268  
   269  	cfg.NameMapper = ini.SnackCase
   270  	return cfg
   271  }
   272  
   273  // LoadCommonSettings loads common configurations from a configuration provider.
   274  func LoadCommonSettings() {
   275  	loadCommonSettingsFrom(CfgProvider)
   276  }
   277  
   278  // loadCommonSettingsFrom loads common configurations from a configuration provider.
   279  func loadCommonSettingsFrom(cfg ConfigProvider) {
   280  	// WARNNING: don't change the sequence except you know what you are doing.
   281  	loadRunModeFrom(cfg)
   282  	loadLogFrom(cfg)
   283  	loadServerFrom(cfg)
   284  	loadSSHFrom(cfg)
   285  
   286  	mustCurrentRunUserMatch(cfg) // it depends on the SSH config, only non-builtin SSH server requires this check
   287  
   288  	loadOAuth2From(cfg)
   289  	loadSecurityFrom(cfg)
   290  	loadAttachmentFrom(cfg)
   291  	loadLFSFrom(cfg)
   292  	loadTimeFrom(cfg)
   293  	loadRepositoryFrom(cfg)
   294  	loadPictureFrom(cfg)
   295  	loadPackagesFrom(cfg)
   296  	loadActionsFrom(cfg)
   297  	loadUIFrom(cfg)
   298  	loadAdminFrom(cfg)
   299  	loadAPIFrom(cfg)
   300  	loadMetricsFrom(cfg)
   301  	loadCamoFrom(cfg)
   302  	loadI18nFrom(cfg)
   303  	loadGitFrom(cfg)
   304  	loadMirrorFrom(cfg)
   305  	loadMarkupFrom(cfg)
   306  	loadOtherFrom(cfg)
   307  }
   308  
   309  func loadRunModeFrom(rootCfg ConfigProvider) {
   310  	rootSec := rootCfg.Section("")
   311  	RunUser = rootSec.Key("RUN_USER").MustString(user.CurrentUsername())
   312  	// The following is a purposefully undocumented option. Please do not run Gitea as root. It will only cause future headaches.
   313  	// Please don't use root as a bandaid to "fix" something that is broken, instead the broken thing should instead be fixed properly.
   314  	unsafeAllowRunAsRoot := rootSec.Key("I_AM_BEING_UNSAFE_RUNNING_AS_ROOT").MustBool(false)
   315  	RunMode = os.Getenv("GITEA_RUN_MODE")
   316  	if RunMode == "" {
   317  		RunMode = rootSec.Key("RUN_MODE").MustString("prod")
   318  	}
   319  	IsProd = strings.EqualFold(RunMode, "prod")
   320  
   321  	// check if we run as root
   322  	if os.Getuid() == 0 {
   323  		if !unsafeAllowRunAsRoot {
   324  			// Special thanks to VLC which inspired the wording of this messaging.
   325  			log.Fatal("Gitea is not supposed to be run as root. Sorry. If you need to use privileged TCP ports please instead use setcap and the `cap_net_bind_service` permission")
   326  		}
   327  		log.Critical("You are running Gitea using the root user, and have purposely chosen to skip built-in protections around this. You have been warned against this.")
   328  	}
   329  }
   330  
   331  func mustCurrentRunUserMatch(rootCfg ConfigProvider) {
   332  	// Does not check run user when the "InstallLock" is off.
   333  	installLock := rootCfg.Section("security").Key("INSTALL_LOCK").MustBool(false)
   334  	if installLock {
   335  		currentUser, match := IsRunUserMatchCurrentUser(RunUser)
   336  		if !match {
   337  			log.Fatal("Expect user '%s' but current user is: %s", RunUser, currentUser)
   338  		}
   339  	}
   340  }
   341  
   342  // CreateOrAppendToCustomConf creates or updates the custom config.
   343  // Use the callback to set individual values.
   344  func CreateOrAppendToCustomConf(purpose string, callback func(cfg *ini.File)) {
   345  	if CustomConf == "" {
   346  		log.Error("Custom config path must not be empty")
   347  		return
   348  	}
   349  
   350  	cfg := ini.Empty()
   351  	isFile, err := util.IsFile(CustomConf)
   352  	if err != nil {
   353  		log.Error("Unable to check if %s is a file. Error: %v", CustomConf, err)
   354  	}
   355  	if isFile {
   356  		if err := cfg.Append(CustomConf); err != nil {
   357  			log.Error("failed to load custom conf %s: %v", CustomConf, err)
   358  			return
   359  		}
   360  	}
   361  
   362  	callback(cfg)
   363  
   364  	if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
   365  		log.Fatal("failed to create '%s': %v", CustomConf, err)
   366  		return
   367  	}
   368  	if err := cfg.SaveTo(CustomConf); err != nil {
   369  		log.Fatal("error saving to custom config: %v", err)
   370  	}
   371  	log.Info("Settings for %s saved to: %q", purpose, CustomConf)
   372  
   373  	// Change permissions to be more restrictive
   374  	fi, err := os.Stat(CustomConf)
   375  	if err != nil {
   376  		log.Error("Failed to determine current conf file permissions: %v", err)
   377  		return
   378  	}
   379  
   380  	if fi.Mode().Perm() > 0o600 {
   381  		if err = os.Chmod(CustomConf, 0o600); err != nil {
   382  			log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.")
   383  		}
   384  	}
   385  }
   386  
   387  // LoadSettings initializes the settings for normal start up
   388  func LoadSettings() {
   389  	LoadDBSetting()
   390  	loadServiceFrom(CfgProvider)
   391  	loadOAuth2ClientFrom(CfgProvider)
   392  	InitLogs(false)
   393  	loadCacheFrom(CfgProvider)
   394  	loadSessionFrom(CfgProvider)
   395  	loadCorsFrom(CfgProvider)
   396  	loadMailsFrom(CfgProvider)
   397  	loadProxyFrom(CfgProvider)
   398  	loadWebhookFrom(CfgProvider)
   399  	loadMigrationsFrom(CfgProvider)
   400  	loadIndexerFrom(CfgProvider)
   401  	loadTaskFrom(CfgProvider)
   402  	LoadQueueSettings()
   403  	loadProjectFrom(CfgProvider)
   404  	loadMimeTypeMapFrom(CfgProvider)
   405  	loadFederationFrom(CfgProvider)
   406  }
   407  
   408  // LoadSettingsForInstall initializes the settings for install
   409  func LoadSettingsForInstall() {
   410  	LoadDBSetting()
   411  	loadServiceFrom(CfgProvider)
   412  	loadMailerFrom(CfgProvider)
   413  }