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 }