mirror of https://github.com/go-gitea/gitea.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
14 KiB
397 lines
14 KiB
// Copyright 2023 The Gitea Authors. All rights reserved. |
|
// SPDX-License-Identifier: MIT |
|
|
|
package setting |
|
|
|
import ( |
|
"encoding/base64" |
|
"net" |
|
"net/url" |
|
"os" |
|
"path/filepath" |
|
"strconv" |
|
"strings" |
|
"time" |
|
|
|
"code.gitea.io/gitea/modules/json" |
|
"code.gitea.io/gitea/modules/log" |
|
"code.gitea.io/gitea/modules/util" |
|
) |
|
|
|
// Scheme describes protocol types |
|
type Scheme string |
|
|
|
// enumerates all the scheme types |
|
const ( |
|
HTTP Scheme = "http" |
|
HTTPS Scheme = "https" |
|
FCGI Scheme = "fcgi" |
|
FCGIUnix Scheme = "fcgi+unix" |
|
HTTPUnix Scheme = "http+unix" |
|
) |
|
|
|
// LandingPage describes the default page |
|
type LandingPage string |
|
|
|
// enumerates all the landing page types |
|
const ( |
|
LandingPageHome LandingPage = "/" |
|
LandingPageExplore LandingPage = "/explore" |
|
LandingPageOrganizations LandingPage = "/explore/organizations" |
|
LandingPageLogin LandingPage = "/user/login" |
|
) |
|
|
|
const ( |
|
PublicURLAuto = "auto" |
|
PublicURLLegacy = "legacy" |
|
) |
|
|
|
// Server settings |
|
var ( |
|
// AppURL is the Application ROOT_URL. It always has a '/' suffix |
|
// It maps to ini:"ROOT_URL" |
|
AppURL string |
|
|
|
// PublicURLDetection controls how to use the HTTP request headers to detect public URL |
|
PublicURLDetection string |
|
|
|
// AppSubURL represents the sub-url mounting point for gitea, parsed from "ROOT_URL" |
|
// It is either "" or starts with '/' and ends without '/', such as '/{sub-path}'. |
|
// This value is empty if site does not have sub-url. |
|
AppSubURL string |
|
|
|
// UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...", |
|
// to make it easier to debug sub-path related problems without a reverse proxy. |
|
UseSubURLPath bool |
|
|
|
// AppDataPath is the default path for storing data. |
|
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data" |
|
AppDataPath string |
|
|
|
// LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix |
|
// It maps to ini:"LOCAL_ROOT_URL" in [server] |
|
LocalURL string |
|
|
|
// AssetVersion holds an opaque value that is used for cache-busting assets |
|
AssetVersion string |
|
|
|
// appTempPathInternal is the temporary path for the app, it is only an internal variable |
|
// DO NOT use it directly, always use AppDataTempDir |
|
appTempPathInternal string |
|
|
|
Protocol Scheme |
|
UseProxyProtocol bool |
|
ProxyProtocolTLSBridging bool |
|
ProxyProtocolHeaderTimeout time.Duration |
|
ProxyProtocolAcceptUnknown bool |
|
Domain string |
|
HTTPAddr string |
|
HTTPPort string |
|
LocalUseProxyProtocol bool |
|
RedirectOtherPort bool |
|
RedirectorUseProxyProtocol bool |
|
PortToRedirect string |
|
OfflineMode bool |
|
CertFile string |
|
KeyFile string |
|
StaticRootPath string |
|
StaticCacheTime time.Duration |
|
EnableGzip bool |
|
LandingPageURL LandingPage |
|
UnixSocketPermission uint32 |
|
EnablePprof bool |
|
PprofDataPath string |
|
EnableAcme bool |
|
AcmeTOS bool |
|
AcmeLiveDirectory string |
|
AcmeEmail string |
|
AcmeURL string |
|
AcmeCARoot string |
|
SSLMinimumVersion string |
|
SSLMaximumVersion string |
|
SSLCurvePreferences []string |
|
SSLCipherSuites []string |
|
GracefulRestartable bool |
|
GracefulHammerTime time.Duration |
|
StartupTimeout time.Duration |
|
PerWriteTimeout = 30 * time.Second |
|
PerWritePerKbTimeout = 10 * time.Second |
|
StaticURLPrefix string |
|
AbsoluteAssetURL string |
|
|
|
ManifestData string |
|
) |
|
|
|
// MakeManifestData generates web app manifest JSON |
|
func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte { |
|
type manifestIcon struct { |
|
Src string `json:"src"` |
|
Type string `json:"type"` |
|
Sizes string `json:"sizes"` |
|
} |
|
|
|
type manifestJSON struct { |
|
Name string `json:"name"` |
|
ShortName string `json:"short_name"` |
|
StartURL string `json:"start_url"` |
|
Icons []manifestIcon `json:"icons"` |
|
} |
|
|
|
bytes, err := json.Marshal(&manifestJSON{ |
|
Name: appName, |
|
ShortName: appName, |
|
StartURL: appURL, |
|
Icons: []manifestIcon{ |
|
{ |
|
Src: absoluteAssetURL + "/assets/img/logo.png", |
|
Type: "image/png", |
|
Sizes: "512x512", |
|
}, |
|
{ |
|
Src: absoluteAssetURL + "/assets/img/logo.svg", |
|
Type: "image/svg+xml", |
|
Sizes: "512x512", |
|
}, |
|
}, |
|
}) |
|
if err != nil { |
|
log.Error("unable to marshal manifest JSON. Error: %v", err) |
|
return make([]byte, 0) |
|
} |
|
|
|
return bytes |
|
} |
|
|
|
// MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash |
|
func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string { |
|
parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/")) |
|
if err != nil { |
|
log.Fatal("Unable to parse STATIC_URL_PREFIX: %v", err) |
|
} |
|
|
|
if err == nil && parsedPrefix.Hostname() == "" { |
|
if staticURLPrefix == "" { |
|
return strings.TrimSuffix(appURL, "/") |
|
} |
|
|
|
// StaticURLPrefix is just a path |
|
return util.URLJoin(appURL, strings.TrimSuffix(staticURLPrefix, "/")) |
|
} |
|
|
|
return strings.TrimSuffix(staticURLPrefix, "/") |
|
} |
|
|
|
func loadServerFrom(rootCfg ConfigProvider) { |
|
sec := rootCfg.Section("server") |
|
AppName = rootCfg.Section("").Key("APP_NAME").MustString("Gitea: Git with a cup of tea") |
|
|
|
Domain = sec.Key("DOMAIN").MustString("localhost") |
|
HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") |
|
HTTPPort = sec.Key("HTTP_PORT").MustString("3000") |
|
|
|
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version |
|
// if these are removed, the warning will not be shown |
|
if sec.HasKey("ENABLE_ACME") { |
|
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false) |
|
} else { |
|
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0") |
|
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) |
|
} |
|
|
|
protocolCfg := sec.Key("PROTOCOL").String() |
|
if protocolCfg != "https" && EnableAcme { |
|
log.Fatal("ACME could only be used with HTTPS protocol") |
|
} |
|
|
|
switch protocolCfg { |
|
case "", "http": |
|
Protocol = HTTP |
|
case "https": |
|
Protocol = HTTPS |
|
if EnableAcme { |
|
AcmeURL = sec.Key("ACME_URL").MustString("") |
|
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("") |
|
|
|
if sec.HasKey("ACME_ACCEPTTOS") { |
|
AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false) |
|
} else { |
|
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", "v1.19.0") |
|
AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false) |
|
} |
|
if !AcmeTOS { |
|
log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).") |
|
} |
|
|
|
if sec.HasKey("ACME_DIRECTORY") { |
|
AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https") |
|
} else { |
|
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", "v1.19.0") |
|
AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https") |
|
} |
|
|
|
if sec.HasKey("ACME_EMAIL") { |
|
AcmeEmail = sec.Key("ACME_EMAIL").MustString("") |
|
} else { |
|
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0") |
|
AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("") |
|
} |
|
if AcmeEmail == "" { |
|
log.Fatal("ACME Email is not set (ACME_EMAIL).") |
|
} |
|
} else { |
|
CertFile = sec.Key("CERT_FILE").String() |
|
KeyFile = sec.Key("KEY_FILE").String() |
|
if len(CertFile) > 0 && !filepath.IsAbs(CertFile) { |
|
CertFile = filepath.Join(CustomPath, CertFile) |
|
} |
|
if len(KeyFile) > 0 && !filepath.IsAbs(KeyFile) { |
|
KeyFile = filepath.Join(CustomPath, KeyFile) |
|
} |
|
} |
|
SSLMinimumVersion = sec.Key("SSL_MIN_VERSION").MustString("") |
|
SSLMaximumVersion = sec.Key("SSL_MAX_VERSION").MustString("") |
|
SSLCurvePreferences = sec.Key("SSL_CURVE_PREFERENCES").Strings(",") |
|
SSLCipherSuites = sec.Key("SSL_CIPHER_SUITES").Strings(",") |
|
case "fcgi": |
|
Protocol = FCGI |
|
case "fcgi+unix", "unix", "http+unix": |
|
switch protocolCfg { |
|
case "fcgi+unix": |
|
Protocol = FCGIUnix |
|
case "unix": |
|
log.Warn("unix PROTOCOL value is deprecated, please use http+unix") |
|
fallthrough |
|
default: // "http+unix" |
|
Protocol = HTTPUnix |
|
} |
|
UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666") |
|
UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32) |
|
if err != nil || UnixSocketPermissionParsed > 0o777 { |
|
log.Fatal("Failed to parse unixSocketPermission: %s", UnixSocketPermissionRaw) |
|
} |
|
|
|
UnixSocketPermission = uint32(UnixSocketPermissionParsed) |
|
if !filepath.IsAbs(HTTPAddr) { |
|
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr) |
|
} |
|
default: |
|
log.Fatal("Invalid PROTOCOL %q", protocolCfg) |
|
} |
|
UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false) |
|
ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false) |
|
ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second) |
|
ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false) |
|
GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true) |
|
GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second) |
|
StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second) |
|
PerWriteTimeout = sec.Key("PER_WRITE_TIMEOUT").MustDuration(PerWriteTimeout) |
|
PerWritePerKbTimeout = sec.Key("PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout) |
|
|
|
defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort |
|
AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL) |
|
PublicURLDetection = sec.Key("PUBLIC_URL_DETECTION").MustString(PublicURLLegacy) |
|
if PublicURLDetection != PublicURLAuto && PublicURLDetection != PublicURLLegacy { |
|
log.Fatal("Invalid PUBLIC_URL_DETECTION value: %s", PublicURLDetection) |
|
} |
|
|
|
// Check validity of AppURL |
|
appURL, err := url.Parse(AppURL) |
|
if err != nil { |
|
log.Fatal("Invalid ROOT_URL %q: %s", AppURL, err) |
|
} |
|
// Remove default ports from AppURL. |
|
// (scheme-based URL normalization, RFC 3986 section 6.2.3) |
|
if (appURL.Scheme == string(HTTP) && appURL.Port() == "80") || (appURL.Scheme == string(HTTPS) && appURL.Port() == "443") { |
|
appURL.Host = appURL.Hostname() |
|
} |
|
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL. |
|
AppURL = strings.TrimRight(appURL.String(), "/") + "/" |
|
|
|
// AppSubURL should start with '/' and end without '/', such as '/{subpath}'. |
|
// This value is empty if site does not have sub-url. |
|
AppSubURL = strings.TrimSuffix(appURL.Path, "/") |
|
UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false) |
|
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/") |
|
|
|
// Check if Domain differs from AppURL domain than update it to AppURL's domain |
|
urlHostname := appURL.Hostname() |
|
if urlHostname != Domain && net.ParseIP(urlHostname) == nil && urlHostname != "" { |
|
Domain = urlHostname |
|
} |
|
|
|
AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix) |
|
AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed) |
|
|
|
manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL) |
|
ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes) |
|
|
|
var defaultLocalURL string |
|
switch Protocol { |
|
case HTTPUnix: |
|
defaultLocalURL = "http://unix/" |
|
case FCGI: |
|
defaultLocalURL = AppURL |
|
case FCGIUnix: |
|
defaultLocalURL = AppURL |
|
case HTTP, HTTPS: |
|
defaultLocalURL = string(Protocol) + "://" |
|
if HTTPAddr == "0.0.0.0" { |
|
defaultLocalURL += net.JoinHostPort("localhost", HTTPPort) + "/" |
|
} else { |
|
defaultLocalURL += net.JoinHostPort(HTTPAddr, HTTPPort) + "/" |
|
} |
|
default: |
|
log.Fatal("Invalid PROTOCOL %q", Protocol) |
|
} |
|
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL) |
|
LocalURL = strings.TrimRight(LocalURL, "/") + "/" |
|
LocalUseProxyProtocol = sec.Key("LOCAL_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) |
|
RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) |
|
PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") |
|
RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) |
|
OfflineMode = sec.Key("OFFLINE_MODE").MustBool(true) |
|
if len(StaticRootPath) == 0 { |
|
StaticRootPath = AppWorkPath |
|
} |
|
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath) |
|
StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour) |
|
AppDataPath = sec.Key("APP_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data")) |
|
if !filepath.IsAbs(AppDataPath) { |
|
AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath)) |
|
} |
|
if IsInTesting && HasInstallLock(rootCfg) { |
|
// FIXME: in testing, the "app data" directory is not correctly initialized before loading settings |
|
if _, err := os.Stat(AppDataPath); err != nil { |
|
_ = os.MkdirAll(AppDataPath, os.ModePerm) |
|
} |
|
} |
|
|
|
appTempPathInternal = sec.Key("APP_TEMP_PATH").String() |
|
if appTempPathInternal != "" { |
|
if _, err := os.Stat(appTempPathInternal); err != nil { |
|
log.Fatal("APP_TEMP_PATH %q is not accessible: %v", appTempPathInternal, err) |
|
} |
|
} |
|
|
|
EnableGzip = sec.Key("ENABLE_GZIP").MustBool() |
|
EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false) |
|
PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data/tmp/pprof")) |
|
if !filepath.IsAbs(PprofDataPath) { |
|
PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath) |
|
} |
|
checkOverlappedPath("[server].PPROF_DATA_PATH", PprofDataPath) |
|
|
|
landingPage := sec.Key("LANDING_PAGE").MustString("home") |
|
switch landingPage { |
|
case "explore": |
|
LandingPageURL = LandingPageExplore |
|
case "organizations": |
|
LandingPageURL = LandingPageOrganizations |
|
case "login": |
|
LandingPageURL = LandingPageLogin |
|
case "", "home": |
|
LandingPageURL = LandingPageHome |
|
default: |
|
LandingPageURL = LandingPage(landingPage) |
|
} |
|
}
|
|
|