conf.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. package conf
  2. import (
  3. "net/mail"
  4. "net/url"
  5. "os"
  6. "path/filepath"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "github.com/cockroachdb/errors"
  11. _ "github.com/flamego/cache/redis"
  12. _ "github.com/flamego/session/redis"
  13. "github.com/gogs/go-libravatar"
  14. "gopkg.in/ini.v1"
  15. log "unknwon.dev/clog/v2"
  16. "gogs.io/gogs/conf"
  17. "gogs.io/gogs/internal/osutil"
  18. "gogs.io/gogs/internal/semverutil"
  19. )
  20. func init() {
  21. // Initialize the primary logger until logging service is up.
  22. err := log.NewConsole()
  23. if err != nil {
  24. panic("init console logger: " + err.Error())
  25. }
  26. }
  27. // File is the configuration object.
  28. var File *ini.File
  29. // Init initializes configuration from conf assets and given custom configuration file.
  30. // If `customConf` is empty, it falls back to default location, i.e. "<WORK DIR>/custom".
  31. // It is safe to call this function multiple times with desired `customConf`, but it is
  32. // not concurrent safe.
  33. //
  34. // NOTE: The order of loading configuration sections matters as one may depend on another.
  35. //
  36. // ⚠️ WARNING: Do not print anything in this function other than warnings.
  37. func Init(customConf string) error {
  38. data, err := conf.Files.ReadFile("app.ini")
  39. if err != nil {
  40. return errors.Wrap(err, `read default "app.ini"`)
  41. }
  42. File, err = ini.LoadSources(ini.LoadOptions{
  43. IgnoreInlineComment: true,
  44. }, data)
  45. if err != nil {
  46. return errors.Wrap(err, `parse "app.ini"`)
  47. }
  48. File.NameMapper = ini.SnackCase
  49. File.ValueMapper = os.ExpandEnv
  50. if customConf == "" {
  51. customConf = filepath.Join(CustomDir(), "conf", "app.ini")
  52. } else {
  53. customConf, err = filepath.Abs(customConf)
  54. if err != nil {
  55. return errors.Wrap(err, "get absolute path")
  56. }
  57. }
  58. CustomConf = customConf
  59. if osutil.IsFile(customConf) {
  60. if err = File.Append(customConf); err != nil {
  61. return errors.Wrapf(err, "append %q", customConf)
  62. }
  63. } else {
  64. log.Warn("Custom config %q not found. Ignore this warning if you're running for the first time", customConf)
  65. }
  66. if err = File.Section(ini.DefaultSection).MapTo(&App); err != nil {
  67. return errors.Wrap(err, "mapping default section")
  68. }
  69. // ***************************
  70. // ----- Server settings -----
  71. // ***************************
  72. if err = File.Section("server").MapTo(&Server); err != nil {
  73. return errors.Wrap(err, "mapping [server] section")
  74. }
  75. Server.AppDataPath = ensureAbs(Server.AppDataPath)
  76. if !strings.HasSuffix(Server.ExternalURL, "/") {
  77. Server.ExternalURL += "/"
  78. }
  79. Server.URL, err = url.Parse(Server.ExternalURL)
  80. if err != nil {
  81. return errors.Wrapf(err, "parse '[server] EXTERNAL_URL' %q", err)
  82. }
  83. // Subpath should start with '/' and end without '/', i.e. '/{subpath}'.
  84. Server.Subpath = strings.TrimRight(Server.URL.Path, "/")
  85. Server.SubpathDepth = strings.Count(Server.Subpath, "/")
  86. unixSocketMode, err := strconv.ParseUint(Server.UnixSocketPermission, 8, 32)
  87. if err != nil {
  88. return errors.Wrapf(err, "parse '[server] UNIX_SOCKET_PERMISSION' %q", Server.UnixSocketPermission)
  89. }
  90. if unixSocketMode > 0o777 {
  91. unixSocketMode = 0o666
  92. }
  93. Server.UnixSocketMode = os.FileMode(unixSocketMode)
  94. // ************************
  95. // ----- SSH settings -----
  96. // ************************
  97. SSH.RootPath = filepath.Join(HomeDir(), ".ssh")
  98. SSH.KeyTestPath = os.TempDir()
  99. if err = File.Section("server").MapTo(&SSH); err != nil {
  100. return errors.Wrap(err, "mapping SSH settings from [server] section")
  101. }
  102. SSH.RootPath = ensureAbs(SSH.RootPath)
  103. SSH.KeyTestPath = ensureAbs(SSH.KeyTestPath)
  104. if !SSH.Disabled {
  105. if !SSH.StartBuiltinServer {
  106. if err := os.MkdirAll(SSH.RootPath, 0o700); err != nil {
  107. return errors.Wrap(err, "create SSH root directory")
  108. } else if err = os.MkdirAll(SSH.KeyTestPath, 0o644); err != nil {
  109. return errors.Wrap(err, "create SSH key test directory")
  110. }
  111. } else {
  112. SSH.RewriteAuthorizedKeysAtStart = false
  113. }
  114. // Check if server is eligible for minimum key size check when user choose to enable.
  115. // Windows server and OpenSSH version lower than 5.1 are forced to be disabled because
  116. // the "ssh-keygen" in Windows does not print key type.
  117. // See https://github.com/gogs/gogs/issues/4507.
  118. if SSH.MinimumKeySizeCheck {
  119. sshVersion, err := openSSHVersion()
  120. if err != nil {
  121. return errors.Wrap(err, "get OpenSSH version")
  122. }
  123. if IsWindowsRuntime() || semverutil.Compare(sshVersion, "<", "5.1") {
  124. log.Warn(`SSH minimum key size check is forced to be disabled because server is not eligible:
  125. 1. Windows server
  126. 2. OpenSSH version is lower than 5.1`)
  127. } else {
  128. SSH.MinimumKeySizes = map[string]int{}
  129. for _, key := range File.Section("ssh.minimum_key_sizes").Keys() {
  130. if key.MustInt() != -1 {
  131. SSH.MinimumKeySizes[strings.ToLower(key.Name())] = key.MustInt()
  132. }
  133. }
  134. }
  135. }
  136. }
  137. // *******************************
  138. // ----- Repository settings -----
  139. // *******************************
  140. Repository.Root = filepath.Join(HomeDir(), "gogs-repositories")
  141. if err = File.Section("repository").MapTo(&Repository); err != nil {
  142. return errors.Wrap(err, "mapping [repository] section")
  143. }
  144. Repository.Root = ensureAbs(Repository.Root)
  145. Repository.Upload.TempPath = ensureAbs(Repository.Upload.TempPath)
  146. // *****************************
  147. // ----- Database settings -----
  148. // *****************************
  149. if err = File.Section("database").MapTo(&Database); err != nil {
  150. return errors.Wrap(err, "mapping [database] section")
  151. }
  152. Database.Path = ensureAbs(Database.Path)
  153. // *****************************
  154. // ----- Security settings -----
  155. // *****************************
  156. if err = File.Section("security").MapTo(&Security); err != nil {
  157. return errors.Wrap(err, "mapping [security] section")
  158. }
  159. // Check run user when the install is locked.
  160. if Security.InstallLock {
  161. currentUser, match := CheckRunUser(App.RunUser)
  162. if !match {
  163. return errors.Newf("user configured to run Gogs is %q, but the current user is %q", App.RunUser, currentUser)
  164. }
  165. }
  166. // **************************
  167. // ----- Email settings -----
  168. // **************************
  169. if err = File.Section("email").MapTo(&Email); err != nil {
  170. return errors.Wrap(err, "mapping [email] section")
  171. }
  172. if Email.Enabled {
  173. if Email.From == "" {
  174. Email.From = Email.User
  175. }
  176. parsed, err := mail.ParseAddress(Email.From)
  177. if err != nil {
  178. return errors.Wrapf(err, "parse mail address %q", Email.From)
  179. }
  180. Email.FromEmail = parsed.Address
  181. }
  182. // ***********************************
  183. // ----- Authentication settings -----
  184. // ***********************************
  185. if err = File.Section("auth").MapTo(&Auth); err != nil {
  186. return errors.Wrap(err, "mapping [auth] section")
  187. }
  188. // *************************
  189. // ----- User settings -----
  190. // *************************
  191. if err = File.Section("user").MapTo(&User); err != nil {
  192. return errors.Wrap(err, "mapping [user] section")
  193. }
  194. // ****************************
  195. // ----- Session settings -----
  196. // ****************************
  197. if err = File.Section("session").MapTo(&Session); err != nil {
  198. return errors.Wrap(err, "mapping [session] section")
  199. }
  200. // *******************************
  201. // ----- Attachment settings -----
  202. // *******************************
  203. if err = File.Section("attachment").MapTo(&Attachment); err != nil {
  204. return errors.Wrap(err, "mapping [attachment] section")
  205. }
  206. Attachment.Path = ensureAbs(Attachment.Path)
  207. // *************************
  208. // ----- Time settings -----
  209. // *************************
  210. if err = File.Section("time").MapTo(&Time); err != nil {
  211. return errors.Wrap(err, "mapping [time] section")
  212. }
  213. Time.FormatLayout = map[string]string{
  214. "ANSIC": time.ANSIC,
  215. "UnixDate": time.UnixDate,
  216. "RubyDate": time.RubyDate,
  217. "RFC822": time.RFC822,
  218. "RFC822Z": time.RFC822Z,
  219. "RFC850": time.RFC850,
  220. "RFC1123": time.RFC1123,
  221. "RFC1123Z": time.RFC1123Z,
  222. "RFC3339": time.RFC3339,
  223. "RFC3339Nano": time.RFC3339Nano,
  224. "Kitchen": time.Kitchen,
  225. "Stamp": time.Stamp,
  226. "StampMilli": time.StampMilli,
  227. "StampMicro": time.StampMicro,
  228. "StampNano": time.StampNano,
  229. }[Time.Format]
  230. if Time.FormatLayout == "" {
  231. Time.FormatLayout = time.RFC3339
  232. }
  233. // ****************************
  234. // ----- Picture settings -----
  235. // ****************************
  236. if err = File.Section("picture").MapTo(&Picture); err != nil {
  237. return errors.Wrap(err, "mapping [picture] section")
  238. }
  239. Picture.AvatarUploadPath = ensureAbs(Picture.AvatarUploadPath)
  240. Picture.RepositoryAvatarUploadPath = ensureAbs(Picture.RepositoryAvatarUploadPath)
  241. switch Picture.GravatarSource {
  242. case "gravatar":
  243. Picture.GravatarSource = "https://secure.gravatar.com/avatar/"
  244. case "libravatar":
  245. Picture.GravatarSource = "https://seccdn.libravatar.org/avatar/"
  246. }
  247. if Server.OfflineMode {
  248. Picture.DisableGravatar = true
  249. Picture.EnableFederatedAvatar = false
  250. }
  251. if Picture.DisableGravatar {
  252. Picture.EnableFederatedAvatar = false
  253. }
  254. if Picture.EnableFederatedAvatar {
  255. gravatarURL, err := url.Parse(Picture.GravatarSource)
  256. if err != nil {
  257. return errors.Wrapf(err, "parse Gravatar source %q", Picture.GravatarSource)
  258. }
  259. Picture.LibravatarService = libravatar.New()
  260. if gravatarURL.Scheme == "https" {
  261. Picture.LibravatarService.SetUseHTTPS(true)
  262. Picture.LibravatarService.SetSecureFallbackHost(gravatarURL.Host)
  263. } else {
  264. Picture.LibravatarService.SetUseHTTPS(false)
  265. Picture.LibravatarService.SetFallbackHost(gravatarURL.Host)
  266. }
  267. }
  268. // ***************************
  269. // ----- Mirror settings -----
  270. // ***************************
  271. if err = File.Section("mirror").MapTo(&Mirror); err != nil {
  272. return errors.Wrap(err, "mapping [mirror] section")
  273. }
  274. if Mirror.DefaultInterval <= 0 {
  275. Mirror.DefaultInterval = 8
  276. }
  277. // *************************
  278. // ----- I18n settings -----
  279. // *************************
  280. I18n = new(i18nConf)
  281. if err = File.Section("i18n").MapTo(I18n); err != nil {
  282. return errors.Wrap(err, "mapping [i18n] section")
  283. }
  284. I18n.dateLangs = File.Section("i18n.datelang").KeysHash()
  285. // *************************
  286. // ----- LFS settings -----
  287. // *************************
  288. if err = File.Section("lfs").MapTo(&LFS); err != nil {
  289. return errors.Wrap(err, "mapping [lfs] section")
  290. }
  291. LFS.ObjectsPath = ensureAbs(LFS.ObjectsPath)
  292. handleDeprecated()
  293. if err = File.Section("cache").MapTo(&Cache); err != nil {
  294. return errors.Wrap(err, "mapping [cache] section")
  295. } else if err = File.Section("http").MapTo(&HTTP); err != nil {
  296. return errors.Wrap(err, "mapping [http] section")
  297. } else if err = File.Section("release").MapTo(&Release); err != nil {
  298. return errors.Wrap(err, "mapping [release] section")
  299. } else if err = File.Section("webhook").MapTo(&Webhook); err != nil {
  300. return errors.Wrap(err, "mapping [webhook] section")
  301. } else if err = File.Section("markdown").MapTo(&Markdown); err != nil {
  302. return errors.Wrap(err, "mapping [markdown] section")
  303. } else if err = File.Section("smartypants").MapTo(&Smartypants); err != nil {
  304. return errors.Wrap(err, "mapping [smartypants] section")
  305. } else if err = File.Section("admin").MapTo(&Admin); err != nil {
  306. return errors.Wrap(err, "mapping [admin] section")
  307. } else if err = File.Section("cron").MapTo(&Cron); err != nil {
  308. return errors.Wrap(err, "mapping [cron] section")
  309. } else if err = File.Section("git").MapTo(&Git); err != nil {
  310. return errors.Wrap(err, "mapping [git] section")
  311. } else if err = File.Section("api").MapTo(&API); err != nil {
  312. return errors.Wrap(err, "mapping [api] section")
  313. } else if err = File.Section("ui").MapTo(&UI); err != nil {
  314. return errors.Wrap(err, "mapping [ui] section")
  315. } else if err = File.Section("prometheus").MapTo(&Prometheus); err != nil {
  316. return errors.Wrap(err, "mapping [prometheus] section")
  317. } else if err = File.Section("other").MapTo(&Other); err != nil {
  318. return errors.Wrap(err, "mapping [other] section")
  319. }
  320. HasRobotsTxt = osutil.IsFile(filepath.Join(CustomDir(), "robots.txt"))
  321. return nil
  322. }
  323. // MustInit panics if configuration initialization failed.
  324. func MustInit(customConf string) {
  325. err := Init(customConf)
  326. if err != nil {
  327. panic(err)
  328. }
  329. }