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