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>release
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