mirror of https://github.com/go-gitea/gitea
Refactor the setting to make unit test easier (#22405)
Some bugs caused by less unit tests in fundamental packages. This PR refactor `setting` package so that create a unit test will be easier than before. - All `LoadFromXXX` files has been splited as two functions, one is `InitProviderFromXXX` and `LoadCommonSettings`. The first functions will only include the code to create or new a ini file. The second function will load common settings. - It also renames all functions in setting from `newXXXService` to `loadXXXSetting` or `loadXXXFrom` to make the function name less confusing. - Move `XORMLog` to `SQLLog` because it's a better name for that. Maybe we should finally move these `loadXXXSetting` into the `XXXInit` function? Any idea? --------- Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: delvh <dev.lh@web.de>pull/22955/head^2
parent
2b02343e21
commit
c53ad052d8
@ -0,0 +1,16 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
// Admin settings
|
||||||
|
var Admin struct { |
||||||
|
DisableRegularOrgCreation bool |
||||||
|
DefaultEmailNotification string |
||||||
|
} |
||||||
|
|
||||||
|
func loadAdminFrom(rootCfg ConfigProvider) { |
||||||
|
mustMapSetting(rootCfg, "admin", &Admin) |
||||||
|
sec := rootCfg.Section("admin") |
||||||
|
Admin.DefaultEmailNotification = sec.Key("DEFAULT_EMAIL_NOTIFICATIONS").MustString("enabled") |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/url" |
||||||
|
"path" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
) |
||||||
|
|
||||||
|
// API settings
|
||||||
|
var API = struct { |
||||||
|
EnableSwagger bool |
||||||
|
SwaggerURL string |
||||||
|
MaxResponseItems int |
||||||
|
DefaultPagingNum int |
||||||
|
DefaultGitTreesPerPage int |
||||||
|
DefaultMaxBlobSize int64 |
||||||
|
}{ |
||||||
|
EnableSwagger: true, |
||||||
|
SwaggerURL: "", |
||||||
|
MaxResponseItems: 50, |
||||||
|
DefaultPagingNum: 30, |
||||||
|
DefaultGitTreesPerPage: 1000, |
||||||
|
DefaultMaxBlobSize: 10485760, |
||||||
|
} |
||||||
|
|
||||||
|
func loadAPIFrom(rootCfg ConfigProvider) { |
||||||
|
mustMapSetting(rootCfg, "api", &API) |
||||||
|
|
||||||
|
defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort |
||||||
|
u, err := url.Parse(rootCfg.Section("server").Key("ROOT_URL").MustString(defaultAppURL)) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Invalid ROOT_URL '%s': %s", AppURL, err) |
||||||
|
} |
||||||
|
u.Path = path.Join(u.Path, "api", "swagger") |
||||||
|
API.SwaggerURL = u.String() |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import "code.gitea.io/gitea/modules/log" |
||||||
|
|
||||||
|
var Camo = struct { |
||||||
|
Enabled bool |
||||||
|
ServerURL string `ini:"SERVER_URL"` |
||||||
|
HMACKey string `ini:"HMAC_KEY"` |
||||||
|
Allways bool |
||||||
|
}{} |
||||||
|
|
||||||
|
func loadCamoFrom(rootCfg ConfigProvider) { |
||||||
|
mustMapSetting(rootCfg, "camo", &Camo) |
||||||
|
if Camo.Enabled { |
||||||
|
if Camo.ServerURL == "" || Camo.HMACKey == "" { |
||||||
|
log.Fatal(`Camo settings require "SERVER_URL" and HMAC_KEY`) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import ( |
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
|
||||||
|
ini "gopkg.in/ini.v1" |
||||||
|
) |
||||||
|
|
||||||
|
// ConfigProvider represents a config provider
|
||||||
|
type ConfigProvider interface { |
||||||
|
Section(section string) *ini.Section |
||||||
|
NewSection(name string) (*ini.Section, error) |
||||||
|
GetSection(name string) (*ini.Section, error) |
||||||
|
} |
||||||
|
|
||||||
|
// a file is an implementation ConfigProvider and other implementations are possible, i.e. from docker, k8s, …
|
||||||
|
var _ ConfigProvider = &ini.File{} |
||||||
|
|
||||||
|
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting interface{}) { |
||||||
|
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil { |
||||||
|
log.Fatal("Failed to map %s settings: %v", sectionName, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey string) { |
||||||
|
if rootCfg.Section(oldSection).HasKey(oldKey) { |
||||||
|
log.Error("Deprecated fallback `[%s]` `%s` present. Use `[%s]` `%s` instead. This fallback will be removed in v1.19.0", oldSection, oldKey, newSection, newKey) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
|
||||||
|
func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { |
||||||
|
if rootCfg.Section(oldSection).HasKey(oldKey) { |
||||||
|
log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey) |
||||||
|
} |
||||||
|
} |
@ -1,39 +0,0 @@ |
|||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package setting |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"os" |
|
||||||
) |
|
||||||
|
|
||||||
// PrepareAppDataPath creates app data directory if necessary
|
|
||||||
func PrepareAppDataPath() error { |
|
||||||
// FIXME: There are too many calls to MkdirAll in old code. It is incorrect.
|
|
||||||
// For example, if someDir=/mnt/vol1/gitea-home/data, if the mount point /mnt/vol1 is not mounted when Gitea runs,
|
|
||||||
// then gitea will make new empty directories in /mnt/vol1, all are stored in the root filesystem.
|
|
||||||
// The correct behavior should be: creating parent directories is end users' duty. We only create sub-directories in existing parent directories.
|
|
||||||
// For quickstart, the parent directories should be created automatically for first startup (eg: a flag or a check of INSTALL_LOCK).
|
|
||||||
// Now we can take the first step to do correctly (using Mkdir) in other packages, and prepare the AppDataPath here, then make a refactor in future.
|
|
||||||
|
|
||||||
st, err := os.Stat(AppDataPath) |
|
||||||
|
|
||||||
if os.IsNotExist(err) { |
|
||||||
err = os.MkdirAll(AppDataPath, os.ModePerm) |
|
||||||
if err != nil { |
|
||||||
return fmt.Errorf("unable to create the APP_DATA_PATH directory: %q, Error: %w", AppDataPath, err) |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
if err != nil { |
|
||||||
return fmt.Errorf("unable to use APP_DATA_PATH %q. Error: %w", AppDataPath, err) |
|
||||||
} |
|
||||||
|
|
||||||
if !st.IsDir() /* also works for symlink */ { |
|
||||||
return fmt.Errorf("the APP_DATA_PATH %q is not a directory (or symlink to a directory) and can't be used", AppDataPath) |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
@ -0,0 +1,17 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
func GetHighlightMapping() map[string]string { |
||||||
|
highlightMapping := map[string]string{} |
||||||
|
if CfgProvider == nil { |
||||||
|
return highlightMapping |
||||||
|
} |
||||||
|
|
||||||
|
keys := CfgProvider.Section("highlight.mapping").Keys() |
||||||
|
for _, key := range keys { |
||||||
|
highlightMapping[key.Name()] = key.Value() |
||||||
|
} |
||||||
|
return highlightMapping |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
// Metrics settings
|
||||||
|
var Metrics = struct { |
||||||
|
Enabled bool |
||||||
|
Token string |
||||||
|
EnabledIssueByLabel bool |
||||||
|
EnabledIssueByRepository bool |
||||||
|
}{ |
||||||
|
Enabled: false, |
||||||
|
Token: "", |
||||||
|
EnabledIssueByLabel: false, |
||||||
|
EnabledIssueByRepository: false, |
||||||
|
} |
||||||
|
|
||||||
|
func loadMetricsFrom(rootCfg ConfigProvider) { |
||||||
|
mustMapSetting(rootCfg, "metrics", &Metrics) |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
var ( |
||||||
|
// Other settings
|
||||||
|
ShowFooterBranding bool |
||||||
|
ShowFooterVersion bool |
||||||
|
ShowFooterTemplateLoadTime bool |
||||||
|
EnableFeed bool |
||||||
|
EnableSitemap bool |
||||||
|
) |
||||||
|
|
||||||
|
func loadOtherFrom(rootCfg ConfigProvider) { |
||||||
|
sec := rootCfg.Section("other") |
||||||
|
ShowFooterBranding = sec.Key("SHOW_FOOTER_BRANDING").MustBool(false) |
||||||
|
ShowFooterVersion = sec.Key("SHOW_FOOTER_VERSION").MustBool(true) |
||||||
|
ShowFooterTemplateLoadTime = sec.Key("SHOW_FOOTER_TEMPLATE_LOAD_TIME").MustBool(true) |
||||||
|
EnableSitemap = sec.Key("ENABLE_SITEMAP").MustBool(true) |
||||||
|
EnableFeed = sec.Key("ENABLE_FEED").MustBool(true) |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/url" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/auth/password/hash" |
||||||
|
"code.gitea.io/gitea/modules/generate" |
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
|
||||||
|
ini "gopkg.in/ini.v1" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// Security settings
|
||||||
|
InstallLock bool |
||||||
|
SecretKey string |
||||||
|
InternalToken string // internal access token
|
||||||
|
LogInRememberDays int |
||||||
|
CookieUserName string |
||||||
|
CookieRememberName string |
||||||
|
ReverseProxyAuthUser string |
||||||
|
ReverseProxyAuthEmail string |
||||||
|
ReverseProxyAuthFullName string |
||||||
|
ReverseProxyLimit int |
||||||
|
ReverseProxyTrustedProxies []string |
||||||
|
MinPasswordLength int |
||||||
|
ImportLocalPaths bool |
||||||
|
DisableGitHooks bool |
||||||
|
DisableWebhooks bool |
||||||
|
OnlyAllowPushIfGiteaEnvironmentSet bool |
||||||
|
PasswordComplexity []string |
||||||
|
PasswordHashAlgo string |
||||||
|
PasswordCheckPwn bool |
||||||
|
SuccessfulTokensCacheSize int |
||||||
|
CSRFCookieName = "_csrf" |
||||||
|
CSRFCookieHTTPOnly = true |
||||||
|
) |
||||||
|
|
||||||
|
// loadSecret load the secret from ini by uriKey or verbatimKey, only one of them could be set
|
||||||
|
// If the secret is loaded from uriKey (file), the file should be non-empty, to guarantee the behavior stable and clear.
|
||||||
|
func loadSecret(sec *ini.Section, uriKey, verbatimKey string) string { |
||||||
|
// don't allow setting both URI and verbatim string
|
||||||
|
uri := sec.Key(uriKey).String() |
||||||
|
verbatim := sec.Key(verbatimKey).String() |
||||||
|
if uri != "" && verbatim != "" { |
||||||
|
log.Fatal("Cannot specify both %s and %s", uriKey, verbatimKey) |
||||||
|
} |
||||||
|
|
||||||
|
// if we have no URI, use verbatim
|
||||||
|
if uri == "" { |
||||||
|
return verbatim |
||||||
|
} |
||||||
|
|
||||||
|
tempURI, err := url.Parse(uri) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Failed to parse %s (%s): %v", uriKey, uri, err) |
||||||
|
} |
||||||
|
switch tempURI.Scheme { |
||||||
|
case "file": |
||||||
|
buf, err := os.ReadFile(tempURI.RequestURI()) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Failed to read %s (%s): %v", uriKey, tempURI.RequestURI(), err) |
||||||
|
} |
||||||
|
val := strings.TrimSpace(string(buf)) |
||||||
|
if val == "" { |
||||||
|
// The file shouldn't be empty, otherwise we can not know whether the user has ever set the KEY or KEY_URI
|
||||||
|
// For example: if INTERNAL_TOKEN_URI=file:///empty-file,
|
||||||
|
// Then if the token is re-generated during installation and saved to INTERNAL_TOKEN
|
||||||
|
// Then INTERNAL_TOKEN and INTERNAL_TOKEN_URI both exist, that's a fatal error (they shouldn't)
|
||||||
|
log.Fatal("Failed to read %s (%s): the file is empty", uriKey, tempURI.RequestURI()) |
||||||
|
} |
||||||
|
return val |
||||||
|
|
||||||
|
// only file URIs are allowed
|
||||||
|
default: |
||||||
|
log.Fatal("Unsupported URI-Scheme %q (INTERNAL_TOKEN_URI = %q)", tempURI.Scheme, uri) |
||||||
|
return "" |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// generateSaveInternalToken generates and saves the internal token to app.ini
|
||||||
|
func generateSaveInternalToken() { |
||||||
|
token, err := generate.NewInternalToken() |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Error generate internal token: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
InternalToken = token |
||||||
|
CreateOrAppendToCustomConf("security.INTERNAL_TOKEN", func(cfg *ini.File) { |
||||||
|
cfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func loadSecurityFrom(rootCfg ConfigProvider) { |
||||||
|
sec := rootCfg.Section("security") |
||||||
|
InstallLock = sec.Key("INSTALL_LOCK").MustBool(false) |
||||||
|
LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) |
||||||
|
CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") |
||||||
|
SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") |
||||||
|
if SecretKey == "" { |
||||||
|
// FIXME: https://github.com/go-gitea/gitea/issues/16832
|
||||||
|
// Until it supports rotating an existing secret key, we shouldn't move users off of the widely used default value
|
||||||
|
SecretKey = "!#@FDEWREWR&*(" //nolint:gosec
|
||||||
|
} |
||||||
|
|
||||||
|
CookieRememberName = sec.Key("COOKIE_REMEMBER_NAME").MustString("gitea_incredible") |
||||||
|
|
||||||
|
ReverseProxyAuthUser = sec.Key("REVERSE_PROXY_AUTHENTICATION_USER").MustString("X-WEBAUTH-USER") |
||||||
|
ReverseProxyAuthEmail = sec.Key("REVERSE_PROXY_AUTHENTICATION_EMAIL").MustString("X-WEBAUTH-EMAIL") |
||||||
|
ReverseProxyAuthFullName = sec.Key("REVERSE_PROXY_AUTHENTICATION_FULL_NAME").MustString("X-WEBAUTH-FULLNAME") |
||||||
|
|
||||||
|
ReverseProxyLimit = sec.Key("REVERSE_PROXY_LIMIT").MustInt(1) |
||||||
|
ReverseProxyTrustedProxies = sec.Key("REVERSE_PROXY_TRUSTED_PROXIES").Strings(",") |
||||||
|
if len(ReverseProxyTrustedProxies) == 0 { |
||||||
|
ReverseProxyTrustedProxies = []string{"127.0.0.0/8", "::1/128"} |
||||||
|
} |
||||||
|
|
||||||
|
MinPasswordLength = sec.Key("MIN_PASSWORD_LENGTH").MustInt(6) |
||||||
|
ImportLocalPaths = sec.Key("IMPORT_LOCAL_PATHS").MustBool(false) |
||||||
|
DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true) |
||||||
|
DisableWebhooks = sec.Key("DISABLE_WEBHOOKS").MustBool(false) |
||||||
|
OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true) |
||||||
|
|
||||||
|
// Ensure that the provided default hash algorithm is a valid hash algorithm
|
||||||
|
var algorithm *hash.PasswordHashAlgorithm |
||||||
|
PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(sec.Key("PASSWORD_HASH_ALGO").MustString("")) |
||||||
|
if algorithm == nil { |
||||||
|
log.Fatal("The provided password hash algorithm was invalid: %s", sec.Key("PASSWORD_HASH_ALGO").MustString("")) |
||||||
|
} |
||||||
|
|
||||||
|
CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) |
||||||
|
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) |
||||||
|
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) |
||||||
|
|
||||||
|
InternalToken = loadSecret(sec, "INTERNAL_TOKEN_URI", "INTERNAL_TOKEN") |
||||||
|
if InstallLock && InternalToken == "" { |
||||||
|
// if Gitea has been installed but the InternalToken hasn't been generated (upgrade from an old release), we should generate
|
||||||
|
// some users do cluster deployment, they still depend on this auto-generating behavior.
|
||||||
|
generateSaveInternalToken() |
||||||
|
} |
||||||
|
|
||||||
|
cfgdata := sec.Key("PASSWORD_COMPLEXITY").Strings(",") |
||||||
|
if len(cfgdata) == 0 { |
||||||
|
cfgdata = []string{"off"} |
||||||
|
} |
||||||
|
PasswordComplexity = make([]string, 0, len(cfgdata)) |
||||||
|
for _, name := range cfgdata { |
||||||
|
name := strings.ToLower(strings.Trim(name, `"`)) |
||||||
|
if name != "" { |
||||||
|
PasswordComplexity = append(PasswordComplexity, name) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,356 @@ |
|||||||
|
// 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 |
||||||
|
// 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 |
||||||
|
LandingPageCustom string |
||||||
|
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 |
||||||
|
|
||||||
|
HasRobotsTxt bool |
||||||
|
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") |
||||||
|
|
||||||
|
Protocol = HTTP |
||||||
|
protocolCfg := sec.Key("PROTOCOL").String() |
||||||
|
switch protocolCfg { |
||||||
|
case "https": |
||||||
|
Protocol = HTTPS |
||||||
|
// FIXME: DEPRECATED to be removed in v1.18.0
|
||||||
|
if sec.HasKey("ENABLE_ACME") { |
||||||
|
EnableAcme = sec.Key("ENABLE_ACME").MustBool(false) |
||||||
|
} else { |
||||||
|
deprecatedSetting(rootCfg, "server", "ENABLE_LETSENCRYPT", "server", "ENABLE_ACME") |
||||||
|
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) |
||||||
|
} |
||||||
|
if EnableAcme { |
||||||
|
AcmeURL = sec.Key("ACME_URL").MustString("") |
||||||
|
AcmeCARoot = sec.Key("ACME_CA_ROOT").MustString("") |
||||||
|
// FIXME: DEPRECATED to be removed in v1.18.0
|
||||||
|
if sec.HasKey("ACME_ACCEPTTOS") { |
||||||
|
AcmeTOS = sec.Key("ACME_ACCEPTTOS").MustBool(false) |
||||||
|
} else { |
||||||
|
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_ACCEPTTOS", "server", "ACME_ACCEPTTOS") |
||||||
|
AcmeTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false) |
||||||
|
} |
||||||
|
if !AcmeTOS { |
||||||
|
log.Fatal("ACME TOS is not accepted (ACME_ACCEPTTOS).") |
||||||
|
} |
||||||
|
// FIXME: DEPRECATED to be removed in v1.18.0
|
||||||
|
if sec.HasKey("ACME_DIRECTORY") { |
||||||
|
AcmeLiveDirectory = sec.Key("ACME_DIRECTORY").MustString("https") |
||||||
|
} else { |
||||||
|
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_DIRECTORY", "server", "ACME_DIRECTORY") |
||||||
|
AcmeLiveDirectory = sec.Key("LETSENCRYPT_DIRECTORY").MustString("https") |
||||||
|
} |
||||||
|
// FIXME: DEPRECATED to be removed in v1.18.0
|
||||||
|
if sec.HasKey("ACME_EMAIL") { |
||||||
|
AcmeEmail = sec.Key("ACME_EMAIL").MustString("") |
||||||
|
} else { |
||||||
|
deprecatedSetting(rootCfg, "server", "LETSENCRYPT_EMAIL", "server", "ACME_EMAIL") |
||||||
|
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() |
||||||
|
Log.DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() |
||||||
|
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) { |
||||||
|
log.Info("The provided APP_DATA_PATH: %s is not absolute - it will be made absolute against the work path: %s", AppDataPath, AppWorkPath) |
||||||
|
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 "": |
||||||
|
case "home": |
||||||
|
LandingPageURL = LandingPageHome |
||||||
|
default: |
||||||
|
LandingPageURL = LandingPage(landingPage) |
||||||
|
} |
||||||
|
|
||||||
|
HasRobotsTxt, err = util.IsFile(path.Join(CustomPath, "robots.txt")) |
||||||
|
if err != nil { |
||||||
|
log.Error("Unable to check if %s is a file. Error: %v", path.Join(CustomPath, "robots.txt"), err) |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,197 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"text/template" |
||||||
|
"time" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
"code.gitea.io/gitea/modules/util" |
||||||
|
|
||||||
|
gossh "golang.org/x/crypto/ssh" |
||||||
|
) |
||||||
|
|
||||||
|
var SSH = struct { |
||||||
|
Disabled bool `ini:"DISABLE_SSH"` |
||||||
|
StartBuiltinServer bool `ini:"START_SSH_SERVER"` |
||||||
|
BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` |
||||||
|
UseProxyProtocol bool `ini:"SSH_SERVER_USE_PROXY_PROTOCOL"` |
||||||
|
Domain string `ini:"SSH_DOMAIN"` |
||||||
|
Port int `ini:"SSH_PORT"` |
||||||
|
User string `ini:"SSH_USER"` |
||||||
|
ListenHost string `ini:"SSH_LISTEN_HOST"` |
||||||
|
ListenPort int `ini:"SSH_LISTEN_PORT"` |
||||||
|
RootPath string `ini:"SSH_ROOT_PATH"` |
||||||
|
ServerCiphers []string `ini:"SSH_SERVER_CIPHERS"` |
||||||
|
ServerKeyExchanges []string `ini:"SSH_SERVER_KEY_EXCHANGES"` |
||||||
|
ServerMACs []string `ini:"SSH_SERVER_MACS"` |
||||||
|
ServerHostKeys []string `ini:"SSH_SERVER_HOST_KEYS"` |
||||||
|
KeyTestPath string `ini:"SSH_KEY_TEST_PATH"` |
||||||
|
KeygenPath string `ini:"SSH_KEYGEN_PATH"` |
||||||
|
AuthorizedKeysBackup bool `ini:"SSH_AUTHORIZED_KEYS_BACKUP"` |
||||||
|
AuthorizedPrincipalsBackup bool `ini:"SSH_AUTHORIZED_PRINCIPALS_BACKUP"` |
||||||
|
AuthorizedKeysCommandTemplate string `ini:"SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE"` |
||||||
|
AuthorizedKeysCommandTemplateTemplate *template.Template `ini:"-"` |
||||||
|
MinimumKeySizeCheck bool `ini:"-"` |
||||||
|
MinimumKeySizes map[string]int `ini:"-"` |
||||||
|
CreateAuthorizedKeysFile bool `ini:"SSH_CREATE_AUTHORIZED_KEYS_FILE"` |
||||||
|
CreateAuthorizedPrincipalsFile bool `ini:"SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE"` |
||||||
|
ExposeAnonymous bool `ini:"SSH_EXPOSE_ANONYMOUS"` |
||||||
|
AuthorizedPrincipalsAllow []string `ini:"SSH_AUTHORIZED_PRINCIPALS_ALLOW"` |
||||||
|
AuthorizedPrincipalsEnabled bool `ini:"-"` |
||||||
|
TrustedUserCAKeys []string `ini:"SSH_TRUSTED_USER_CA_KEYS"` |
||||||
|
TrustedUserCAKeysFile string `ini:"SSH_TRUSTED_USER_CA_KEYS_FILENAME"` |
||||||
|
TrustedUserCAKeysParsed []gossh.PublicKey `ini:"-"` |
||||||
|
PerWriteTimeout time.Duration `ini:"SSH_PER_WRITE_TIMEOUT"` |
||||||
|
PerWritePerKbTimeout time.Duration `ini:"SSH_PER_WRITE_PER_KB_TIMEOUT"` |
||||||
|
}{ |
||||||
|
Disabled: false, |
||||||
|
StartBuiltinServer: false, |
||||||
|
Domain: "", |
||||||
|
Port: 22, |
||||||
|
ServerCiphers: []string{"chacha20-poly1305@openssh.com", "aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "aes256-gcm@openssh.com"}, |
||||||
|
ServerKeyExchanges: []string{"curve25519-sha256", "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group14-sha256", "diffie-hellman-group14-sha1"}, |
||||||
|
ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, |
||||||
|
KeygenPath: "ssh-keygen", |
||||||
|
MinimumKeySizeCheck: true, |
||||||
|
MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2047}, |
||||||
|
ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, |
||||||
|
AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}", |
||||||
|
PerWriteTimeout: PerWriteTimeout, |
||||||
|
PerWritePerKbTimeout: PerWritePerKbTimeout, |
||||||
|
} |
||||||
|
|
||||||
|
func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) { |
||||||
|
anything := false |
||||||
|
email := false |
||||||
|
username := false |
||||||
|
for _, value := range values { |
||||||
|
v := strings.ToLower(strings.TrimSpace(value)) |
||||||
|
switch v { |
||||||
|
case "off": |
||||||
|
return []string{"off"}, false |
||||||
|
case "email": |
||||||
|
email = true |
||||||
|
case "username": |
||||||
|
username = true |
||||||
|
case "anything": |
||||||
|
anything = true |
||||||
|
} |
||||||
|
} |
||||||
|
if anything { |
||||||
|
return []string{"anything"}, true |
||||||
|
} |
||||||
|
|
||||||
|
authorizedPrincipalsAllow := []string{} |
||||||
|
if username { |
||||||
|
authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "username") |
||||||
|
} |
||||||
|
if email { |
||||||
|
authorizedPrincipalsAllow = append(authorizedPrincipalsAllow, "email") |
||||||
|
} |
||||||
|
|
||||||
|
return authorizedPrincipalsAllow, true |
||||||
|
} |
||||||
|
|
||||||
|
func loadSSHFrom(rootCfg ConfigProvider) { |
||||||
|
sec := rootCfg.Section("server") |
||||||
|
if len(SSH.Domain) == 0 { |
||||||
|
SSH.Domain = Domain |
||||||
|
} |
||||||
|
|
||||||
|
homeDir, err := util.HomeDir() |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Failed to get home directory: %v", err) |
||||||
|
} |
||||||
|
homeDir = strings.ReplaceAll(homeDir, "\\", "/") |
||||||
|
|
||||||
|
SSH.RootPath = path.Join(homeDir, ".ssh") |
||||||
|
serverCiphers := sec.Key("SSH_SERVER_CIPHERS").Strings(",") |
||||||
|
if len(serverCiphers) > 0 { |
||||||
|
SSH.ServerCiphers = serverCiphers |
||||||
|
} |
||||||
|
serverKeyExchanges := sec.Key("SSH_SERVER_KEY_EXCHANGES").Strings(",") |
||||||
|
if len(serverKeyExchanges) > 0 { |
||||||
|
SSH.ServerKeyExchanges = serverKeyExchanges |
||||||
|
} |
||||||
|
serverMACs := sec.Key("SSH_SERVER_MACS").Strings(",") |
||||||
|
if len(serverMACs) > 0 { |
||||||
|
SSH.ServerMACs = serverMACs |
||||||
|
} |
||||||
|
SSH.KeyTestPath = os.TempDir() |
||||||
|
if err = sec.MapTo(&SSH); err != nil { |
||||||
|
log.Fatal("Failed to map SSH settings: %v", err) |
||||||
|
} |
||||||
|
for i, key := range SSH.ServerHostKeys { |
||||||
|
if !filepath.IsAbs(key) { |
||||||
|
SSH.ServerHostKeys[i] = filepath.Join(AppDataPath, key) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen") |
||||||
|
SSH.Port = sec.Key("SSH_PORT").MustInt(22) |
||||||
|
SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) |
||||||
|
SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) |
||||||
|
|
||||||
|
// When disable SSH, start builtin server value is ignored.
|
||||||
|
if SSH.Disabled { |
||||||
|
SSH.StartBuiltinServer = false |
||||||
|
} |
||||||
|
|
||||||
|
SSH.TrustedUserCAKeysFile = sec.Key("SSH_TRUSTED_USER_CA_KEYS_FILENAME").MustString(filepath.Join(SSH.RootPath, "gitea-trusted-user-ca-keys.pem")) |
||||||
|
|
||||||
|
for _, caKey := range SSH.TrustedUserCAKeys { |
||||||
|
pubKey, _, _, _, err := gossh.ParseAuthorizedKey([]byte(caKey)) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Failed to parse TrustedUserCaKeys: %s %v", caKey, err) |
||||||
|
} |
||||||
|
|
||||||
|
SSH.TrustedUserCAKeysParsed = append(SSH.TrustedUserCAKeysParsed, pubKey) |
||||||
|
} |
||||||
|
if len(SSH.TrustedUserCAKeys) > 0 { |
||||||
|
// Set the default as email,username otherwise we can leave it empty
|
||||||
|
sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("username,email") |
||||||
|
} else { |
||||||
|
sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").MustString("off") |
||||||
|
} |
||||||
|
|
||||||
|
SSH.AuthorizedPrincipalsAllow, SSH.AuthorizedPrincipalsEnabled = parseAuthorizedPrincipalsAllow(sec.Key("SSH_AUTHORIZED_PRINCIPALS_ALLOW").Strings(",")) |
||||||
|
|
||||||
|
SSH.MinimumKeySizeCheck = sec.Key("MINIMUM_KEY_SIZE_CHECK").MustBool(SSH.MinimumKeySizeCheck) |
||||||
|
minimumKeySizes := rootCfg.Section("ssh.minimum_key_sizes").Keys() |
||||||
|
for _, key := range minimumKeySizes { |
||||||
|
if key.MustInt() != -1 { |
||||||
|
SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt() |
||||||
|
} else { |
||||||
|
delete(SSH.MinimumKeySizes, strings.ToLower(key.Name())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
SSH.AuthorizedKeysBackup = sec.Key("SSH_AUTHORIZED_KEYS_BACKUP").MustBool(true) |
||||||
|
SSH.CreateAuthorizedKeysFile = sec.Key("SSH_CREATE_AUTHORIZED_KEYS_FILE").MustBool(true) |
||||||
|
|
||||||
|
SSH.AuthorizedPrincipalsBackup = false |
||||||
|
SSH.CreateAuthorizedPrincipalsFile = false |
||||||
|
if SSH.AuthorizedPrincipalsEnabled { |
||||||
|
SSH.AuthorizedPrincipalsBackup = sec.Key("SSH_AUTHORIZED_PRINCIPALS_BACKUP").MustBool(true) |
||||||
|
SSH.CreateAuthorizedPrincipalsFile = sec.Key("SSH_CREATE_AUTHORIZED_PRINCIPALS_FILE").MustBool(true) |
||||||
|
} |
||||||
|
|
||||||
|
SSH.ExposeAnonymous = sec.Key("SSH_EXPOSE_ANONYMOUS").MustBool(false) |
||||||
|
SSH.AuthorizedKeysCommandTemplate = sec.Key("SSH_AUTHORIZED_KEYS_COMMAND_TEMPLATE").MustString(SSH.AuthorizedKeysCommandTemplate) |
||||||
|
|
||||||
|
SSH.AuthorizedKeysCommandTemplateTemplate = template.Must(template.New("").Parse(SSH.AuthorizedKeysCommandTemplate)) |
||||||
|
|
||||||
|
SSH.PerWriteTimeout = sec.Key("SSH_PER_WRITE_TIMEOUT").MustDuration(PerWriteTimeout) |
||||||
|
SSH.PerWritePerKbTimeout = sec.Key("SSH_PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout) |
||||||
|
|
||||||
|
// ensure parseRunModeSetting has been executed before this
|
||||||
|
SSH.BuiltinServerUser = rootCfg.Section("server").Key("BUILTIN_SSH_SERVER_USER").MustString(RunUser) |
||||||
|
SSH.User = rootCfg.Section("server").Key("SSH_USER").MustString(SSH.BuiltinServerUser) |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/log" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// Time settings
|
||||||
|
TimeFormat string |
||||||
|
// UILocation is the location on the UI, so that we can display the time on UI.
|
||||||
|
DefaultUILocation = time.Local |
||||||
|
) |
||||||
|
|
||||||
|
func loadTimeFrom(rootCfg ConfigProvider) { |
||||||
|
timeFormatKey := rootCfg.Section("time").Key("FORMAT").MustString("") |
||||||
|
if timeFormatKey != "" { |
||||||
|
TimeFormat = map[string]string{ |
||||||
|
"ANSIC": time.ANSIC, |
||||||
|
"UnixDate": time.UnixDate, |
||||||
|
"RubyDate": time.RubyDate, |
||||||
|
"RFC822": time.RFC822, |
||||||
|
"RFC822Z": time.RFC822Z, |
||||||
|
"RFC850": time.RFC850, |
||||||
|
"RFC1123": time.RFC1123, |
||||||
|
"RFC1123Z": time.RFC1123Z, |
||||||
|
"RFC3339": time.RFC3339, |
||||||
|
"RFC3339Nano": time.RFC3339Nano, |
||||||
|
"Kitchen": time.Kitchen, |
||||||
|
"Stamp": time.Stamp, |
||||||
|
"StampMilli": time.StampMilli, |
||||||
|
"StampMicro": time.StampMicro, |
||||||
|
"StampNano": time.StampNano, |
||||||
|
}[timeFormatKey] |
||||||
|
// When the TimeFormatKey does not exist in the previous map e.g.'2006-01-02 15:04:05'
|
||||||
|
if len(TimeFormat) == 0 { |
||||||
|
TimeFormat = timeFormatKey |
||||||
|
TestTimeFormat, _ := time.Parse(TimeFormat, TimeFormat) |
||||||
|
if TestTimeFormat.Format(time.RFC3339) != "2006-01-02T15:04:05Z" { |
||||||
|
log.Warn("Provided TimeFormat: %s does not create a fully specified date and time.", TimeFormat) |
||||||
|
log.Warn("In order to display dates and times correctly please check your time format has 2006, 01, 02, 15, 04 and 05") |
||||||
|
} |
||||||
|
log.Trace("Custom TimeFormat: %s", TimeFormat) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
zone := rootCfg.Section("time").Key("DEFAULT_UI_LOCATION").String() |
||||||
|
if zone != "" { |
||||||
|
var err error |
||||||
|
DefaultUILocation, err = time.LoadLocation(zone) |
||||||
|
if err != nil { |
||||||
|
log.Fatal("Load time zone failed: %v", err) |
||||||
|
} else { |
||||||
|
log.Info("Default UI Location is %v", zone) |
||||||
|
} |
||||||
|
} |
||||||
|
if DefaultUILocation == nil { |
||||||
|
DefaultUILocation = time.Local |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,152 @@ |
|||||||
|
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/container" |
||||||
|
) |
||||||
|
|
||||||
|
// UI settings
|
||||||
|
var UI = struct { |
||||||
|
ExplorePagingNum int |
||||||
|
SitemapPagingNum int |
||||||
|
IssuePagingNum int |
||||||
|
RepoSearchPagingNum int |
||||||
|
MembersPagingNum int |
||||||
|
FeedMaxCommitNum int |
||||||
|
FeedPagingNum int |
||||||
|
PackagesPagingNum int |
||||||
|
GraphMaxCommitNum int |
||||||
|
CodeCommentLines int |
||||||
|
ReactionMaxUserNum int |
||||||
|
ThemeColorMetaTag string |
||||||
|
MaxDisplayFileSize int64 |
||||||
|
ShowUserEmail bool |
||||||
|
DefaultShowFullName bool |
||||||
|
DefaultTheme string |
||||||
|
Themes []string |
||||||
|
Reactions []string |
||||||
|
ReactionsLookup container.Set[string] `ini:"-"` |
||||||
|
CustomEmojis []string |
||||||
|
CustomEmojisMap map[string]string `ini:"-"` |
||||||
|
SearchRepoDescription bool |
||||||
|
UseServiceWorker bool |
||||||
|
OnlyShowRelevantRepos bool |
||||||
|
|
||||||
|
Notification struct { |
||||||
|
MinTimeout time.Duration |
||||||
|
TimeoutStep time.Duration |
||||||
|
MaxTimeout time.Duration |
||||||
|
EventSourceUpdateTime time.Duration |
||||||
|
} `ini:"ui.notification"` |
||||||
|
|
||||||
|
SVG struct { |
||||||
|
Enabled bool `ini:"ENABLE_RENDER"` |
||||||
|
} `ini:"ui.svg"` |
||||||
|
|
||||||
|
CSV struct { |
||||||
|
MaxFileSize int64 |
||||||
|
} `ini:"ui.csv"` |
||||||
|
|
||||||
|
Admin struct { |
||||||
|
UserPagingNum int |
||||||
|
RepoPagingNum int |
||||||
|
NoticePagingNum int |
||||||
|
OrgPagingNum int |
||||||
|
} `ini:"ui.admin"` |
||||||
|
User struct { |
||||||
|
RepoPagingNum int |
||||||
|
} `ini:"ui.user"` |
||||||
|
Meta struct { |
||||||
|
Author string |
||||||
|
Description string |
||||||
|
Keywords string |
||||||
|
} `ini:"ui.meta"` |
||||||
|
}{ |
||||||
|
ExplorePagingNum: 20, |
||||||
|
SitemapPagingNum: 20, |
||||||
|
IssuePagingNum: 20, |
||||||
|
RepoSearchPagingNum: 20, |
||||||
|
MembersPagingNum: 20, |
||||||
|
FeedMaxCommitNum: 5, |
||||||
|
FeedPagingNum: 20, |
||||||
|
PackagesPagingNum: 20, |
||||||
|
GraphMaxCommitNum: 100, |
||||||
|
CodeCommentLines: 4, |
||||||
|
ReactionMaxUserNum: 10, |
||||||
|
ThemeColorMetaTag: `#6cc644`, |
||||||
|
MaxDisplayFileSize: 8388608, |
||||||
|
DefaultTheme: `auto`, |
||||||
|
Themes: []string{`auto`, `gitea`, `arc-green`}, |
||||||
|
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, |
||||||
|
CustomEmojis: []string{`git`, `gitea`, `codeberg`, `gitlab`, `github`, `gogs`}, |
||||||
|
CustomEmojisMap: map[string]string{"git": ":git:", "gitea": ":gitea:", "codeberg": ":codeberg:", "gitlab": ":gitlab:", "github": ":github:", "gogs": ":gogs:"}, |
||||||
|
Notification: struct { |
||||||
|
MinTimeout time.Duration |
||||||
|
TimeoutStep time.Duration |
||||||
|
MaxTimeout time.Duration |
||||||
|
EventSourceUpdateTime time.Duration |
||||||
|
}{ |
||||||
|
MinTimeout: 10 * time.Second, |
||||||
|
TimeoutStep: 10 * time.Second, |
||||||
|
MaxTimeout: 60 * time.Second, |
||||||
|
EventSourceUpdateTime: 10 * time.Second, |
||||||
|
}, |
||||||
|
SVG: struct { |
||||||
|
Enabled bool `ini:"ENABLE_RENDER"` |
||||||
|
}{ |
||||||
|
Enabled: true, |
||||||
|
}, |
||||||
|
CSV: struct { |
||||||
|
MaxFileSize int64 |
||||||
|
}{ |
||||||
|
MaxFileSize: 524288, |
||||||
|
}, |
||||||
|
Admin: struct { |
||||||
|
UserPagingNum int |
||||||
|
RepoPagingNum int |
||||||
|
NoticePagingNum int |
||||||
|
OrgPagingNum int |
||||||
|
}{ |
||||||
|
UserPagingNum: 50, |
||||||
|
RepoPagingNum: 50, |
||||||
|
NoticePagingNum: 25, |
||||||
|
OrgPagingNum: 50, |
||||||
|
}, |
||||||
|
User: struct { |
||||||
|
RepoPagingNum int |
||||||
|
}{ |
||||||
|
RepoPagingNum: 15, |
||||||
|
}, |
||||||
|
Meta: struct { |
||||||
|
Author string |
||||||
|
Description string |
||||||
|
Keywords string |
||||||
|
}{ |
||||||
|
Author: "Gitea - Git with a cup of tea", |
||||||
|
Description: "Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go", |
||||||
|
Keywords: "go,git,self-hosted,gitea", |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
func loadUIFrom(rootCfg ConfigProvider) { |
||||||
|
mustMapSetting(rootCfg, "ui", &UI) |
||||||
|
sec := rootCfg.Section("ui") |
||||||
|
UI.ShowUserEmail = sec.Key("SHOW_USER_EMAIL").MustBool(true) |
||||||
|
UI.DefaultShowFullName = sec.Key("DEFAULT_SHOW_FULL_NAME").MustBool(false) |
||||||
|
UI.SearchRepoDescription = sec.Key("SEARCH_REPO_DESCRIPTION").MustBool(true) |
||||||
|
UI.UseServiceWorker = sec.Key("USE_SERVICE_WORKER").MustBool(false) |
||||||
|
UI.OnlyShowRelevantRepos = sec.Key("ONLY_SHOW_RELEVANT_REPOS").MustBool(false) |
||||||
|
|
||||||
|
UI.ReactionsLookup = make(container.Set[string]) |
||||||
|
for _, reaction := range UI.Reactions { |
||||||
|
UI.ReactionsLookup.Add(reaction) |
||||||
|
} |
||||||
|
UI.CustomEmojisMap = make(map[string]string) |
||||||
|
for _, emoji := range UI.CustomEmojis { |
||||||
|
UI.CustomEmojisMap[emoji] = ":" + emoji + ":" |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue