// Copyright 2023 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package setting import ( "errors" "os" "os/exec" "path/filepath" "strings" "code.gitea.io/gitea/modules/log" ) var ( // AppPath represents the path to the gitea binary AppPath string // AppWorkPath is the "working directory" of Gitea. It maps to the environment variable GITEA_WORK_DIR. // If that is not set it is the default set here by the linker or failing that the directory of AppPath. // It is used as the base path for several other paths. AppWorkPath string CustomPath string // Custom directory path. Env: GITEA_CUSTOM CustomConf string appWorkPathBuiltin string customPathBuiltin string customConfBuiltin string AppWorkPathMismatch bool ) func getAppPath() (string, error) { var appPath string var err error if IsWindows && filepath.IsAbs(os.Args[0]) { appPath = filepath.Clean(os.Args[0]) } else { appPath, err = exec.LookPath(os.Args[0]) } if err != nil { if !errors.Is(err, exec.ErrDot) { return "", err } appPath, err = filepath.Abs(os.Args[0]) } if err != nil { return "", err } appPath, err = filepath.Abs(appPath) if err != nil { return "", err } // Note: (legacy code) we don't use path.Dir here because it does not handle case which path starts with two "/" in Windows: "//psf/Home/..." return strings.ReplaceAll(appPath, "\\", "/"), err } func init() { var err error if AppPath, err = getAppPath(); err != nil { log.Fatal("Failed to get app path: %v", err) } if AppWorkPath == "" { AppWorkPath = filepath.Dir(AppPath) } appWorkPathBuiltin = AppWorkPath customPathBuiltin = CustomPath customConfBuiltin = CustomConf } type ArgWorkPathAndCustomConf struct { WorkPath string CustomPath string CustomConf string } type stringWithDefault struct { Value string IsSet bool } func (s *stringWithDefault) Set(v string) { s.Value = v s.IsSet = true } // InitWorkPathAndCommonConfig will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf and load common settings, func InitWorkPathAndCommonConfig(getEnvFn func(name string) string, args ArgWorkPathAndCustomConf) { InitWorkPathAndCfgProvider(getEnvFn, args) LoadCommonSettings() } // InitWorkPathAndCfgProvider will set AppWorkPath, CustomPath and CustomConf, init default config provider by CustomConf func InitWorkPathAndCfgProvider(getEnvFn func(name string) string, args ArgWorkPathAndCustomConf) { tryAbsPath := func(paths ...string) string { s := paths[len(paths)-1] for i := len(paths) - 2; i >= 0; i-- { if filepath.IsAbs(s) { break } s = filepath.Join(paths[i], s) } return s } var err error tmpWorkPath := stringWithDefault{Value: appWorkPathBuiltin} if tmpWorkPath.Value == "" { tmpWorkPath.Value = filepath.Dir(AppPath) } tmpCustomPath := stringWithDefault{Value: customPathBuiltin} if tmpCustomPath.Value == "" { tmpCustomPath.Value = "custom" } tmpCustomConf := stringWithDefault{Value: customConfBuiltin} if tmpCustomConf.Value == "" { tmpCustomConf.Value = "conf/app.ini" } readFromEnv := func() { envWorkPath := getEnvFn("GITEA_WORK_DIR") if envWorkPath != "" { tmpWorkPath.Set(envWorkPath) if !filepath.IsAbs(tmpWorkPath.Value) { log.Fatal("GITEA_WORK_DIR (work path) must be absolute path") } } envWorkPath = getEnvFn("FORGEJO_WORK_DIR") if envWorkPath != "" { tmpWorkPath.Set(envWorkPath) if !filepath.IsAbs(tmpWorkPath.Value) { log.Fatal("FORGEJO_WORK_DIR (work path) must be absolute path") } } envCustomPath := getEnvFn("GITEA_CUSTOM") if envCustomPath != "" { tmpCustomPath.Set(envCustomPath) if !filepath.IsAbs(tmpCustomPath.Value) { log.Fatal("GITEA_CUSTOM (custom path) must be absolute path") } } envCustomPath = getEnvFn("FORGEJO_CUSTOM") if envCustomPath != "" { tmpCustomPath.Set(envCustomPath) if !filepath.IsAbs(tmpCustomPath.Value) { log.Fatal("FORGEJO_CUSTOM (custom path) must be absolute path") } } } readFromArgs := func() { if args.WorkPath != "" { tmpWorkPath.Set(args.WorkPath) if !filepath.IsAbs(tmpWorkPath.Value) { log.Fatal("--work-path must be absolute path") } } if args.CustomPath != "" { tmpCustomPath.Set(args.CustomPath) // if it is not abs, it will be based on work-path, it shouldn't happen if !filepath.IsAbs(tmpCustomPath.Value) { log.Error("--custom-path must be absolute path") } } if args.CustomConf != "" { tmpCustomConf.Set(args.CustomConf) if !filepath.IsAbs(tmpCustomConf.Value) { // the config path can be relative to the real current working path if tmpCustomConf.Value, err = filepath.Abs(tmpCustomConf.Value); err != nil { log.Fatal("Failed to get absolute path of config %q: %v", tmpCustomConf.Value, err) } } } } readFromEnv() readFromArgs() if !tmpCustomConf.IsSet { tmpCustomConf.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value, tmpCustomConf.Value)) } // only read the config but do not load/init anything more, because the AppWorkPath and CustomPath are not ready InitCfgProvider(tmpCustomConf.Value) if HasInstallLock(CfgProvider) { ClearEnvConfigKeys() // if the instance has been installed, do not pass the environment variables to sub-processes } configWorkPath := ConfigSectionKeyString(CfgProvider.Section(""), "WORK_PATH") if configWorkPath != "" { if !filepath.IsAbs(configWorkPath) { log.Fatal("WORK_PATH in %q must be absolute path", configWorkPath) } configWorkPath = filepath.Clean(configWorkPath) if tmpWorkPath.Value != "" && (getEnvFn("GITEA_WORK_DIR") != "" || getEnvFn("FORGEJO_WORK_DIR") != "" || args.WorkPath != "") { fi1, err1 := os.Stat(tmpWorkPath.Value) fi2, err2 := os.Stat(configWorkPath) if err1 != nil || err2 != nil || !os.SameFile(fi1, fi2) { AppWorkPathMismatch = true } } tmpWorkPath.Set(configWorkPath) } tmpCustomPath.Set(tryAbsPath(tmpWorkPath.Value, tmpCustomPath.Value)) AppWorkPath = tmpWorkPath.Value CustomPath = tmpCustomPath.Value CustomConf = tmpCustomConf.Value }