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 }