code.gitea.io/gitea@v1.22.3/modules/setting/config_env.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package setting 5 6 import ( 7 "bytes" 8 "os" 9 "regexp" 10 "strconv" 11 "strings" 12 13 "code.gitea.io/gitea/modules/log" 14 ) 15 16 const ( 17 EnvConfigKeyPrefixGitea = "GITEA__" 18 EnvConfigKeySuffixFile = "__FILE" 19 ) 20 21 const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_" 22 23 var escapeRegex = regexp.MustCompile(escapeRegexpString) 24 25 func CollectEnvConfigKeys() (keys []string) { 26 for _, env := range os.Environ() { 27 if strings.HasPrefix(env, EnvConfigKeyPrefixGitea) { 28 k, _, _ := strings.Cut(env, "=") 29 keys = append(keys, k) 30 } 31 } 32 return keys 33 } 34 35 func ClearEnvConfigKeys() { 36 for _, k := range CollectEnvConfigKeys() { 37 _ = os.Unsetenv(k) 38 } 39 } 40 41 // decodeEnvSectionKey will decode a portable string encoded Section__Key pair 42 // Portable strings are considered to be of the form [A-Z0-9_]* 43 // We will encode a disallowed value as the UTF8 byte string preceded by _0X and 44 // followed by _. E.g. _0X2C_ for a '-' and _0X2E_ for '.' 45 // Section and Key are separated by a plain '__'. 46 // The entire section can be encoded as a UTF8 byte string 47 func decodeEnvSectionKey(encoded string) (ok bool, section, key string) { 48 inKey := false 49 last := 0 50 escapeStringIndices := escapeRegex.FindAllStringIndex(encoded, -1) 51 for _, unescapeIdx := range escapeStringIndices { 52 preceding := encoded[last:unescapeIdx[0]] 53 if !inKey { 54 if splitter := strings.Index(preceding, "__"); splitter > -1 { 55 section += preceding[:splitter] 56 inKey = true 57 key += preceding[splitter+2:] 58 } else { 59 section += preceding 60 } 61 } else { 62 key += preceding 63 } 64 toDecode := encoded[unescapeIdx[0]+3 : unescapeIdx[1]-1] 65 decodedBytes := make([]byte, len(toDecode)/2) 66 for i := 0; i < len(toDecode)/2; i++ { 67 // Can ignore error here as we know these should be hexadecimal from the regexp 68 byteInt, _ := strconv.ParseInt(toDecode[2*i:2*i+2], 16, 0) 69 decodedBytes[i] = byte(byteInt) 70 } 71 if inKey { 72 key += string(decodedBytes) 73 } else { 74 section += string(decodedBytes) 75 } 76 last = unescapeIdx[1] 77 } 78 remaining := encoded[last:] 79 if !inKey { 80 if splitter := strings.Index(remaining, "__"); splitter > -1 { 81 section += remaining[:splitter] 82 key += remaining[splitter+2:] 83 } else { 84 section += remaining 85 } 86 } else { 87 key += remaining 88 } 89 section = strings.ToLower(section) 90 ok = key != "" 91 if !ok { 92 section = "" 93 key = "" 94 } 95 return ok, section, key 96 } 97 98 // decodeEnvironmentKey decode the environment key to section and key 99 // The environment key is in the form of GITEA__SECTION__KEY or GITEA__SECTION__KEY__FILE 100 func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, section, key string, useFileValue bool) { 101 if !strings.HasPrefix(envKey, prefixGitea) { 102 return false, "", "", false 103 } 104 if strings.HasSuffix(envKey, suffixFile) { 105 useFileValue = true 106 envKey = envKey[:len(envKey)-len(suffixFile)] 107 } 108 ok, section, key = decodeEnvSectionKey(envKey[len(prefixGitea):]) 109 return ok, section, key, useFileValue 110 } 111 112 func EnvironmentToConfig(cfg ConfigProvider, envs []string) (changed bool) { 113 for _, kv := range envs { 114 idx := strings.IndexByte(kv, '=') 115 if idx < 0 { 116 continue 117 } 118 119 // parse the environment variable to config section name and key name 120 envKey := kv[:idx] 121 envValue := kv[idx+1:] 122 ok, sectionName, keyName, useFileValue := decodeEnvironmentKey(EnvConfigKeyPrefixGitea, EnvConfigKeySuffixFile, envKey) 123 if !ok { 124 continue 125 } 126 127 // use environment value as config value, or read the file content as value if the key indicates a file 128 keyValue := envValue 129 if useFileValue { 130 fileContent, err := os.ReadFile(envValue) 131 if err != nil { 132 log.Error("Error reading file for %s : %v", envKey, envValue, err) 133 continue 134 } 135 if bytes.HasSuffix(fileContent, []byte("\r\n")) { 136 fileContent = fileContent[:len(fileContent)-2] 137 } else if bytes.HasSuffix(fileContent, []byte("\n")) { 138 fileContent = fileContent[:len(fileContent)-1] 139 } 140 keyValue = string(fileContent) 141 } 142 143 // try to set the config value if necessary 144 section, err := cfg.GetSection(sectionName) 145 if err != nil { 146 section, err = cfg.NewSection(sectionName) 147 if err != nil { 148 log.Error("Error creating section: %s : %v", sectionName, err) 149 continue 150 } 151 } 152 key := ConfigSectionKey(section, keyName) 153 if key == nil { 154 changed = true 155 key, err = section.NewKey(keyName, keyValue) 156 if err != nil { 157 log.Error("Error creating key: %s in section: %s with value: %s : %v", keyName, sectionName, keyValue, err) 158 continue 159 } 160 } 161 oldValue := key.Value() 162 if !changed && oldValue != keyValue { 163 changed = true 164 } 165 key.SetValue(keyValue) 166 } 167 return changed 168 }