mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-10-24 11:02:42 +00:00
(cherry picked from commitb075991747) (cherry picked from commitda3f76228e) (cherry picked from commit20d196e74f) (cherry picked from commit0bf8b1824e) (cherry picked from commit655bb770a7) (cherry picked from commitd69d5c2c46) (cherry picked from commit00b55e5a53) (cherry picked from commit456121fd8a) (cherry picked from commit9716a158e4) (cherry picked from commit7d60a6f511) (cherry picked from commitd32a6d9437) (cherry picked from commitee1de38527) (cherry picked from commit54e7799d13) (cherry picked from commit4f04da7ab7) (cherry picked from commit0d39a0a520) (cherry picked from commit7d8ae8279f) (cherry picked from commit76b6770b73) (cherry picked from commit9bc0d96064) Conflicts: contrib/environment-to-ini/environment-to-ini.go https://codeberg.org/forgejo/forgejo/pulls/1769 (cherry picked from commite21bf9b144) (cherry picked from commit96e501c5f0) (cherry picked from commit466a66a1f6) (cherry picked from commit7814cf700a) (cherry picked from commit4d12344871) (cherry picked from commitfad4cf84c3) (cherry picked from commit7ad89400ee) (cherry picked from commitec91140447) (cherry picked from commit295a7f4487) (cherry picked from commit66163a5dcf) (cherry picked from commit9e06f57269)
170 lines
4.7 KiB
Go
170 lines
4.7 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/modules/log"
|
|
)
|
|
|
|
const (
|
|
EnvConfigKeyPrefixGitea = "^(FORGEJO|GITEA)__"
|
|
EnvConfigKeySuffixFile = "__FILE"
|
|
)
|
|
|
|
const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_"
|
|
|
|
var escapeRegex = regexp.MustCompile(escapeRegexpString)
|
|
|
|
func CollectEnvConfigKeys() (keys []string) {
|
|
for _, env := range os.Environ() {
|
|
if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) {
|
|
k, _, _ := strings.Cut(env, "=")
|
|
keys = append(keys, k)
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func ClearEnvConfigKeys() {
|
|
for _, k := range CollectEnvConfigKeys() {
|
|
_ = os.Unsetenv(k)
|
|
}
|
|
}
|
|
|
|
// decodeEnvSectionKey will decode a portable string encoded Section__Key pair
|
|
// Portable strings are considered to be of the form [A-Z0-9_]*
|
|
// We will encode a disallowed value as the UTF8 byte string preceded by _0X and
|
|
// followed by _. E.g. _0X2C_ for a '-' and _0X2E_ for '.'
|
|
// Section and Key are separated by a plain '__'.
|
|
// The entire section can be encoded as a UTF8 byte string
|
|
func decodeEnvSectionKey(encoded string) (ok bool, section, key string) {
|
|
inKey := false
|
|
last := 0
|
|
escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1)
|
|
for _, unescapeIdx := range escapeStringIndices {
|
|
preceding := encoded[last:unescapeIdx[0]]
|
|
if !inKey {
|
|
if splitter := strings.Index(preceding, "__"); splitter > -1 {
|
|
section += preceding[:splitter]
|
|
inKey = true
|
|
key += preceding[splitter+2:]
|
|
} else {
|
|
section += preceding
|
|
}
|
|
} else {
|
|
key += preceding
|
|
}
|
|
toDecode := encoded[unescapeIdx[0]+3 : unescapeIdx[1]-1]
|
|
decodedBytes := make([]byte, len(toDecode)/2)
|
|
for i := 0; i < len(toDecode)/2; i++ {
|
|
// Can ignore error here as we know these should be hexadecimal from the regexp
|
|
byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 0)
|
|
decodedBytes[i] = byte(byteInt)
|
|
}
|
|
if inKey {
|
|
key += string(decodedBytes)
|
|
} else {
|
|
section += string(decodedBytes)
|
|
}
|
|
last = unescapeIdx[1]
|
|
}
|
|
remaining := encoded[last:]
|
|
if !inKey {
|
|
if splitter := strings.Index(remaining, "__"); splitter > -1 {
|
|
section += remaining[:splitter]
|
|
key += remaining[splitter+2:]
|
|
} else {
|
|
section += remaining
|
|
}
|
|
} else {
|
|
key += remaining
|
|
}
|
|
section = strings.ToLower(section)
|
|
ok = key != ""
|
|
if !ok {
|
|
section = ""
|
|
key = ""
|
|
}
|
|
return ok, section, key
|
|
}
|
|
|
|
// decodeEnvironmentKey decode the environment key to section and key
|
|
// The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE
|
|
func decodeEnvironmentKey(prefixRegexp *regexp.Regexp, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) {
|
|
if strings.HasSuffix(envKey, suffixFile) {
|
|
useFileValue = true
|
|
envKey = envKey[:len(envKey)-len(suffixFile)]
|
|
}
|
|
loc := prefixRegexp.FindStringIndex(envKey)
|
|
if loc == nil {
|
|
return false, "", "", false
|
|
}
|
|
ok, section, key = decodeEnvSectionKey(envKey[loc[1]:])
|
|
return ok, section, key, useFileValue
|
|
}
|
|
|
|
func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) {
|
|
prefixRegexp := regexp.MustCompile(EnvConfigKeyPrefixGitea)
|
|
for _, kv := range envs {
|
|
idx := strings.IndexByte(kv, '=')
|
|
if idx < 0 {
|
|
continue
|
|
}
|
|
|
|
// parse the environment variable to config section name and key name
|
|
envKey := kv[:idx]
|
|
envValue := kv[idx+1:]
|
|
ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(prefixRegexp, EnvConfigKeySuffixFile, envKey)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
// use environment value as config value, or read the file content as value if the key indicates a file
|
|
keyValue := envValue
|
|
if useFileValue {
|
|
fileContent, err := os.ReadFile(envValue)
|
|
if err != nil {
|
|
log.Error("Error reading file for %s : %v", envKey, envValue, err)
|
|
continue
|
|
}
|
|
if bytes.HasSuffix(fileContent, []byte("\r\n")) {
|
|
fileContent = fileContent[:len(fileContent)-2]
|
|
} else if bytes.HasSuffix(fileContent, []byte("\n")) {
|
|
fileContent = fileContent[:len(fileContent)-1]
|
|
}
|
|
keyValue = string(fileContent)
|
|
}
|
|
|
|
// try to set the config value if necessary
|
|
section, err := cfg.GetSection(sectionName)
|
|
if err != nil {
|
|
section, err = cfg.NewSection(sectionName)
|
|
if err != nil {
|
|
log.Error("Error creating section: %s : %v", sectionName, err)
|
|
continue
|
|
}
|
|
}
|
|
key := ConfigSectionKey(section, keyName)
|
|
if key == nil {
|
|
changed = true
|
|
key, err = section.NewKey(keyName, keyValue)
|
|
if err != nil {
|
|
log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, keyValue, err)
|
|
continue
|
|
}
|
|
}
|
|
oldValue := key.Value()
|
|
if !changed && oldValue != keyValue {
|
|
changed = true
|
|
}
|
|
key.SetValue(keyValue)
|
|
}
|
|
return changed
|
|
}
|