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

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package setting
     5  
     6  import (
     7  	"encoding/base64"
     8  	"net"
     9  	"net/url"
    10  	"path/filepath"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"code.gitea.io/gitea/modules/json"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/util"
    18  )
    19  
    20  // Scheme describes protocol types
    21  type Scheme string
    22  
    23  // enumerates all the scheme types
    24  const (
    25  	HTTP     Scheme = "http"
    26  	HTTPS    Scheme = "https"
    27  	FCGI     Scheme = "fcgi"
    28  	FCGIUnix Scheme = "fcgi+unix"
    29  	HTTPUnix Scheme = "http+unix"
    30  )
    31  
    32  // LandingPage describes the default page
    33  type LandingPage string
    34  
    35  // enumerates all the landing page types
    36  const (
    37  	LandingPageHome          LandingPage = "/"
    38  	LandingPageExplore       LandingPage = "/explore"
    39  	LandingPageOrganizations LandingPage = "/explore/organizations"
    40  	LandingPageLogin         LandingPage = "/user/login"
    41  )
    42  
    43  var (
    44  	// AppName is the Application name, used in the page title.
    45  	// It maps to ini:"APP_NAME"
    46  	AppName string
    47  	// AppURL is the Application ROOT_URL. It always has a '/' suffix
    48  	// It maps to ini:"ROOT_URL"
    49  	AppURL string
    50  	// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
    51  	// This value is empty if site does not have sub-url.
    52  	AppSubURL string
    53  	// AppDataPath is the default path for storing data.
    54  	// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
    55  	AppDataPath string
    56  	// LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix
    57  	// It maps to ini:"LOCAL_ROOT_URL" in [server]
    58  	LocalURL string
    59  	// AssetVersion holds a opaque value that is used for cache-busting assets
    60  	AssetVersion string
    61  
    62  	// Server settings
    63  
    64  	Protocol                   Scheme
    65  	UseProxyProtocol           bool // `ini:"USE_PROXY_PROTOCOL"`
    66  	ProxyProtocolTLSBridging   bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
    67  	ProxyProtocolHeaderTimeout time.Duration
    68  	ProxyProtocolAcceptUnknown bool
    69  	Domain                     string
    70  	HTTPAddr                   string
    71  	HTTPPort                   string
    72  	LocalUseProxyProtocol      bool
    73  	RedirectOtherPort          bool
    74  	RedirectorUseProxyProtocol bool
    75  	PortToRedirect             string
    76  	OfflineMode                bool
    77  	CertFile                   string
    78  	KeyFile                    string
    79  	StaticRootPath             string
    80  	StaticCacheTime            time.Duration
    81  	EnableGzip                 bool
    82  	LandingPageURL             LandingPage
    83  	UnixSocketPermission       uint32
    84  	EnablePprof                bool
    85  	PprofDataPath              string
    86  	EnableAcme                 bool
    87  	AcmeTOS                    bool
    88  	AcmeLiveDirectory          string
    89  	AcmeEmail                  string
    90  	AcmeURL                    string
    91  	AcmeCARoot                 string
    92  	SSLMinimumVersion          string
    93  	SSLMaximumVersion          string
    94  	SSLCurvePreferences        []string
    95  	SSLCipherSuites            []string
    96  	GracefulRestartable        bool
    97  	GracefulHammerTime         time.Duration
    98  	StartupTimeout             time.Duration
    99  	PerWriteTimeout            = 30 * time.Second
   100  	PerWritePerKbTimeout       = 10 * time.Second
   101  	StaticURLPrefix            string
   102  	AbsoluteAssetURL           string
   103  
   104  	ManifestData string
   105  )
   106  
   107  // MakeManifestData generates web app manifest JSON
   108  func MakeManifestData(appName, appURL, absoluteAssetURL string) []byte {
   109  	type manifestIcon struct {
   110  		Src   string `json:"src"`
   111  		Type  string `json:"type"`
   112  		Sizes string `json:"sizes"`
   113  	}
   114  
   115  	type manifestJSON struct {
   116  		Name      string         `json:"name"`
   117  		ShortName string         `json:"short_name"`
   118  		StartURL  string         `json:"start_url"`
   119  		Icons     []manifestIcon `json:"icons"`
   120  	}
   121  
   122  	bytes, err := json.Marshal(&manifestJSON{
   123  		Name:      appName,
   124  		ShortName: appName,
   125  		StartURL:  appURL,
   126  		Icons: []manifestIcon{
   127  			{
   128  				Src:   absoluteAssetURL + "/assets/img/logo.png",
   129  				Type:  "image/png",
   130  				Sizes: "512x512",
   131  			},
   132  			{
   133  				Src:   absoluteAssetURL + "/assets/img/logo.svg",
   134  				Type:  "image/svg+xml",
   135  				Sizes: "512x512",
   136  			},
   137  		},
   138  	})
   139  	if err != nil {
   140  		log.Error("unable to marshal manifest JSON. Error: %v", err)
   141  		return make([]byte, 0)
   142  	}
   143  
   144  	return bytes
   145  }
   146  
   147  // MakeAbsoluteAssetURL returns the absolute asset url prefix without a trailing slash
   148  func MakeAbsoluteAssetURL(appURL, staticURLPrefix string) string {
   149  	parsedPrefix, err := url.Parse(strings.TrimSuffix(staticURLPrefix, "/"))
   150  	if err != nil {
   151  		log.Fatal("Unable to parse STATIC_URL_PREFIX: %v", err)
   152  	}
   153  
   154  	if err == nil && parsedPrefix.Hostname() == "" {
   155  		if staticURLPrefix == "" {
   156  			return strings.TrimSuffix(appURL, "/")
   157  		}
   158  
   159  		// StaticURLPrefix is just a path
   160  		return util.URLJoin(appURL, strings.TrimSuffix(staticURLPrefix, "/"))
   161  	}
   162  
   163  	return strings.TrimSuffix(staticURLPrefix, "/")
   164  }
   165  
   166  func loadServerFrom(rootCfg ConfigProvider) {
   167  	sec := rootCfg.Section("server")
   168  	AppName = rootCfg.Section("").Key("APP_NAME").MustString("Gitea: Git with a cup of tea")
   169  
   170  	Domain = sec.Key("DOMAIN").MustString("localhost")
   171  	HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
   172  	HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
   173  
   174  	Protocol = HTTP
   175  	protocolCfg := sec.Key("PROTOCOL").String()
   176  	switch protocolCfg {
   177  	case "https":
   178  		Protocol = HTTPS
   179  
   180  		// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
   181  		// if these are removed, the warning will not be shown
   182  		if sec.HasKey("ENABLE_ACME") {
   183  			EnableAcme = sec.Key("ENABLE_ACME").MustBool(false)
   184  		} else {
   185  			deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME", "v1.19.0")
   186  			EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
   187  		}
   188  		if EnableAcme {
   189  			AcmeURL = sec.Key("ACME_URL").MustString("")
   190  			AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("")
   191  
   192  			if sec.HasKey("ACME_ACCEPTTOS") {
   193  				AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false)
   194  			} else {
   195  				deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS", "v1.19.0")
   196  				AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false)
   197  			}
   198  			if !AcmeTOS {
   199  				log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).")
   200  			}
   201  
   202  			if sec.HasKey("ACME_DIRECTORY") {
   203  				AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https")
   204  			} else {
   205  				deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY", "v1.19.0")
   206  				AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https")
   207  			}
   208  
   209  			if sec.HasKey("ACME_EMAIL") {
   210  				AcmeEmail = sec.Key("ACME_EMAIL").MustString("")
   211  			} else {
   212  				deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL", "v1.19.0")
   213  				AcmeEmail = sec.Key("LETSENCRYPT_EMAIL").MustString("")
   214  			}
   215  		} else {
   216  			CertFile = sec.Key("CERT_FILE").String()
   217  			KeyFile = sec.Key("KEY_FILE").String()
   218  			if len(CertFile) > 0 && !filepath.IsAbs(CertFile) {
   219  				CertFile = filepath.Join(CustomPath, CertFile)
   220  			}
   221  			if len(KeyFile) > 0 && !filepath.IsAbs(KeyFile) {
   222  				KeyFile = filepath.Join(CustomPath, KeyFile)
   223  			}
   224  		}
   225  		SSLMinimumVersion = sec.Key("SSL_MIN_VERSION").MustString("")
   226  		SSLMaximumVersion = sec.Key("SSL_MAX_VERSION").MustString("")
   227  		SSLCurvePreferences = sec.Key("SSL_CURVE_PREFERENCES").Strings(",")
   228  		SSLCipherSuites = sec.Key("SSL_CIPHER_SUITES").Strings(",")
   229  	case "fcgi":
   230  		Protocol = FCGI
   231  	case "fcgi+unix", "unix", "http+unix":
   232  		switch protocolCfg {
   233  		case "fcgi+unix":
   234  			Protocol = FCGIUnix
   235  		case "unix":
   236  			log.Warn("unix PROTOCOL value is deprecated, please use http+unix")
   237  			fallthrough
   238  		case "http+unix":
   239  			Protocol = HTTPUnix
   240  		}
   241  		UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
   242  		UnixSocketPermissionParsed, err := strconv.ParseUint(UnixSocketPermissionRaw, 8, 32)
   243  		if err != nil || UnixSocketPermissionParsed > 0o777 {
   244  			log.Fatal("Failed to parse unixSocketPermission: %s", UnixSocketPermissionRaw)
   245  		}
   246  
   247  		UnixSocketPermission = uint32(UnixSocketPermissionParsed)
   248  		if !filepath.IsAbs(HTTPAddr) {
   249  			HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
   250  		}
   251  	}
   252  	UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
   253  	ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
   254  	ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second)
   255  	ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false)
   256  	GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true)
   257  	GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second)
   258  	StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second)
   259  	PerWriteTimeout = sec.Key("PER_WRITE_TIMEOUT").MustDuration(PerWriteTimeout)
   260  	PerWritePerKbTimeout = sec.Key("PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout)
   261  
   262  	defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort
   263  	AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL)
   264  
   265  	// Check validity of AppURL
   266  	appURL, err := url.Parse(AppURL)
   267  	if err != nil {
   268  		log.Fatal("Invalid ROOT_URL '%s': %s", AppURL, err)
   269  	}
   270  	// Remove default ports from AppURL.
   271  	// (scheme-based URL normalization, RFC 3986 section 6.2.3)
   272  	if (appURL.Scheme == string(HTTP) && appURL.Port() == "80") || (appURL.Scheme == string(HTTPS) && appURL.Port() == "443") {
   273  		appURL.Host = appURL.Hostname()
   274  	}
   275  	// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
   276  	AppURL = strings.TrimRight(appURL.String(), "/") + "/"
   277  
   278  	// Suburl should start with '/' and end without '/', such as '/{subpath}'.
   279  	// This value is empty if site does not have sub-url.
   280  	AppSubURL = strings.TrimSuffix(appURL.Path, "/")
   281  	StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
   282  
   283  	// Check if Domain differs from AppURL domain than update it to AppURL's domain
   284  	urlHostname := appURL.Hostname()
   285  	if urlHostname != Domain && net.ParseIP(urlHostname) == nil && urlHostname != "" {
   286  		Domain = urlHostname
   287  	}
   288  
   289  	AbsoluteAssetURL = MakeAbsoluteAssetURL(AppURL, StaticURLPrefix)
   290  	AssetVersion = strings.ReplaceAll(AppVer, "+", "~") // make sure the version string is clear (no real escaping is needed)
   291  
   292  	manifestBytes := MakeManifestData(AppName, AppURL, AbsoluteAssetURL)
   293  	ManifestData = `application/json;base64,` + base64.StdEncoding.EncodeToString(manifestBytes)
   294  
   295  	var defaultLocalURL string
   296  	switch Protocol {
   297  	case HTTPUnix:
   298  		defaultLocalURL = "http://unix/"
   299  	case FCGI:
   300  		defaultLocalURL = AppURL
   301  	case FCGIUnix:
   302  		defaultLocalURL = AppURL
   303  	default:
   304  		defaultLocalURL = string(Protocol) + "://"
   305  		if HTTPAddr == "0.0.0.0" {
   306  			defaultLocalURL += net.JoinHostPort("localhost", HTTPPort) + "/"
   307  		} else {
   308  			defaultLocalURL += net.JoinHostPort(HTTPAddr, HTTPPort) + "/"
   309  		}
   310  	}
   311  	LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
   312  	LocalURL = strings.TrimRight(LocalURL, "/") + "/"
   313  	LocalUseProxyProtocol = sec.Key("LOCAL_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol)
   314  	RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false)
   315  	PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80")
   316  	RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol)
   317  	OfflineMode = sec.Key("OFFLINE_MODE").MustBool(true)
   318  	if len(StaticRootPath) == 0 {
   319  		StaticRootPath = AppWorkPath
   320  	}
   321  	StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(StaticRootPath)
   322  	StaticCacheTime = sec.Key("STATIC_CACHE_TIME").MustDuration(6 * time.Hour)
   323  	AppDataPath = sec.Key("APP_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data"))
   324  	if !filepath.IsAbs(AppDataPath) {
   325  		AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
   326  	}
   327  
   328  	EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
   329  	EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
   330  	PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(filepath.Join(AppWorkPath, "data/tmp/pprof"))
   331  	if !filepath.IsAbs(PprofDataPath) {
   332  		PprofDataPath = filepath.Join(AppWorkPath, PprofDataPath)
   333  	}
   334  	checkOverlappedPath("[server].PPROF_DATA_PATH", PprofDataPath)
   335  
   336  	landingPage := sec.Key("LANDING_PAGE").MustString("home")
   337  	switch landingPage {
   338  	case "explore":
   339  		LandingPageURL = LandingPageExplore
   340  	case "organizations":
   341  		LandingPageURL = LandingPageOrganizations
   342  	case "login":
   343  		LandingPageURL = LandingPageLogin
   344  	case "", "home":
   345  		LandingPageURL = LandingPageHome
   346  	default:
   347  		LandingPageURL = LandingPage(landingPage)
   348  	}
   349  }