mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-02 23:41:05 +00:00 
			
		
		
		
	This is a PR for #3616 Currently added a new optional config `SLOGAN` in ini file. When this config is set title page is modified in APP_NAME [ - SLOGAN] Example in image below  Add the new config value in the admin settings page (readonly)  ## TODO * [x] Add the possibility to add the `SLOGAN` config from the installation form * [ ] Update https://forgejo.org/docs/next/admin/config-cheat-sheet Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3752 Reviewed-by: 0ko <0ko@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: mirko <mirko.perillo@gmail.com> Co-committed-by: mirko <mirko.perillo@gmail.com>
		
			
				
	
	
		
			368 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			368 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2023 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package setting
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/base64"
 | 
						|
	"net"
 | 
						|
	"net/url"
 | 
						|
	"path"
 | 
						|
	"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"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	// AppName is the Application name, used in the page title.
 | 
						|
	// It maps to ini:"APP_NAME"
 | 
						|
	AppName string
 | 
						|
	// AppSlogan is the Application slogan.
 | 
						|
	// It maps to ini:"APP_SLOGAN"
 | 
						|
	AppSlogan string
 | 
						|
	// AppDisplayNameFormat defines how the AppDisplayName should be presented
 | 
						|
	// It maps to ini:"APP_DISPLAY_NAME_FORMAT"
 | 
						|
	AppDisplayNameFormat string
 | 
						|
	// AppDisplayName is the display name for the application, defined following AppDisplayNameFormat
 | 
						|
	AppDisplayName string
 | 
						|
	// AppURL is the Application ROOT_URL. It always has a '/' suffix
 | 
						|
	// It maps to ini:"ROOT_URL"
 | 
						|
	AppURL string
 | 
						|
	// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
 | 
						|
	// This value is empty if site does not have sub-url.
 | 
						|
	AppSubURL string
 | 
						|
	// 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 a opaque value that is used for cache-busting assets
 | 
						|
	AssetVersion string
 | 
						|
 | 
						|
	// Server settings
 | 
						|
 | 
						|
	Protocol                   Scheme
 | 
						|
	UseProxyProtocol           bool // `ini:"USE_PROXY_PROTOCOL"`
 | 
						|
	ProxyProtocolTLSBridging   bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
 | 
						|
	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 generateDisplayName() string {
 | 
						|
	appDisplayName := AppName
 | 
						|
	if AppSlogan != "" {
 | 
						|
		appDisplayName = strings.Replace(AppDisplayNameFormat, "{APP_NAME}", AppName, 1)
 | 
						|
		appDisplayName = strings.Replace(appDisplayName, "{APP_SLOGAN}", AppSlogan, 1)
 | 
						|
	}
 | 
						|
	return appDisplayName
 | 
						|
}
 | 
						|
 | 
						|
func loadServerFrom(rootCfg ConfigProvider) {
 | 
						|
	sec := rootCfg.Section("server")
 | 
						|
	AppName = rootCfg.Section("").Key("APP_NAME").MustString("Forgejo: Beyond coding. We Forge.")
 | 
						|
	AppSlogan = rootCfg.Section("").Key("APP_SLOGAN").MustString("")
 | 
						|
	AppDisplayNameFormat = rootCfg.Section("").Key("APP_DISPLAY_NAME_FORMAT").MustString("{APP_NAME}: {APP_SLOGAN}")
 | 
						|
	AppDisplayName = generateDisplayName()
 | 
						|
	Domain = sec.Key("DOMAIN").MustString("localhost")
 | 
						|
	HTTPAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0")
 | 
						|
	HTTPPort = sec.Key("HTTP_PORT").MustString("3000")
 | 
						|
 | 
						|
	Protocol = HTTP
 | 
						|
	protocolCfg := sec.Key("PROTOCOL").String()
 | 
						|
	switch protocolCfg {
 | 
						|
	case "https":
 | 
						|
		Protocol = HTTPS
 | 
						|
 | 
						|
		// 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)
 | 
						|
		}
 | 
						|
		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("")
 | 
						|
			}
 | 
						|
		} 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
 | 
						|
		case "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)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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)
 | 
						|
 | 
						|
	// Check validity of AppURL
 | 
						|
	appURL, err := url.Parse(AppURL)
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal("Invalid ROOT_URL '%s': %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(), "/") + "/"
 | 
						|
 | 
						|
	// Suburl 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, "/")
 | 
						|
	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
 | 
						|
	default:
 | 
						|
		defaultLocalURL = string(Protocol) + "://"
 | 
						|
		if HTTPAddr == "0.0.0.0" {
 | 
						|
			defaultLocalURL += net.JoinHostPort("localhost", HTTPPort) + "/"
 | 
						|
		} else {
 | 
						|
			defaultLocalURL += net.JoinHostPort(HTTPAddr, HTTPPort) + "/"
 | 
						|
		}
 | 
						|
	}
 | 
						|
	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(path.Join(AppWorkPath, "data"))
 | 
						|
	if !filepath.IsAbs(AppDataPath) {
 | 
						|
		AppDataPath = filepath.ToSlash(filepath.Join(AppWorkPath, AppDataPath))
 | 
						|
	}
 | 
						|
 | 
						|
	EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
 | 
						|
	EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
 | 
						|
	PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(path.Join(AppWorkPath, "data/tmp/pprof"))
 | 
						|
	if !filepath.IsAbs(PprofDataPath) {
 | 
						|
		PprofDataPath = filepath.Join(AppWorkPath, 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)
 | 
						|
	}
 | 
						|
}
 |