You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

database.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package setting
  4. import (
  5. "errors"
  6. "fmt"
  7. "net"
  8. "net/url"
  9. "os"
  10. "path/filepath"
  11. "strings"
  12. "time"
  13. )
  14. var (
  15. // SupportedDatabaseTypes includes all XORM supported databases type, sqlite3 maybe added by `database_sqlite3.go`
  16. SupportedDatabaseTypes = []string{"mysql", "postgres", "mssql"}
  17. // DatabaseTypeNames contains the friendly names for all database types
  18. DatabaseTypeNames = map[string]string{"mysql": "MySQL", "postgres": "PostgreSQL", "mssql": "MSSQL", "sqlite3": "SQLite3"}
  19. // EnableSQLite3 use SQLite3, set by build flag
  20. EnableSQLite3 bool
  21. // Database holds the database settings
  22. Database = struct {
  23. Type DatabaseType
  24. Host string
  25. Name string
  26. User string
  27. Passwd string
  28. Schema string
  29. SSLMode string
  30. Path string
  31. LogSQL bool
  32. MysqlCharset string
  33. Timeout int // seconds
  34. SQLiteJournalMode string
  35. DBConnectRetries int
  36. DBConnectBackoff time.Duration
  37. MaxIdleConns int
  38. MaxOpenConns int
  39. ConnMaxLifetime time.Duration
  40. IterateBufferSize int
  41. AutoMigration bool
  42. }{
  43. Timeout: 500,
  44. IterateBufferSize: 50,
  45. }
  46. )
  47. // LoadDBSetting loads the database settings
  48. func LoadDBSetting() {
  49. loadDBSetting(CfgProvider)
  50. }
  51. func loadDBSetting(rootCfg ConfigProvider) {
  52. sec := rootCfg.Section("database")
  53. Database.Type = DatabaseType(sec.Key("DB_TYPE").String())
  54. Database.Host = sec.Key("HOST").String()
  55. Database.Name = sec.Key("NAME").String()
  56. Database.User = sec.Key("USER").String()
  57. if len(Database.Passwd) == 0 {
  58. Database.Passwd = sec.Key("PASSWD").String()
  59. }
  60. Database.Schema = sec.Key("SCHEMA").String()
  61. Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
  62. Database.MysqlCharset = sec.Key("MYSQL_CHARSET").MustString("utf8mb4") // do not document it, end users won't need it.
  63. Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
  64. Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
  65. Database.SQLiteJournalMode = sec.Key("SQLITE_JOURNAL_MODE").MustString("")
  66. Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2)
  67. if Database.Type.IsMySQL() {
  68. Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(3 * time.Second)
  69. } else {
  70. Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0)
  71. }
  72. Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0)
  73. Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50)
  74. Database.LogSQL = sec.Key("LOG_SQL").MustBool(false)
  75. Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
  76. Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
  77. Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
  78. }
  79. // DBConnStr returns database connection string
  80. func DBConnStr() (string, error) {
  81. var connStr string
  82. paramSep := "?"
  83. if strings.Contains(Database.Name, paramSep) {
  84. paramSep = "&"
  85. }
  86. switch Database.Type {
  87. case "mysql":
  88. connType := "tcp"
  89. if len(Database.Host) > 0 && Database.Host[0] == '/' { // looks like a unix socket
  90. connType = "unix"
  91. }
  92. tls := Database.SSLMode
  93. if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL
  94. tls = "false"
  95. }
  96. connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
  97. Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, Database.MysqlCharset, tls)
  98. case "postgres":
  99. connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Database.SSLMode)
  100. case "mssql":
  101. host, port := ParseMSSQLHostPort(Database.Host)
  102. connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
  103. case "sqlite3":
  104. if !EnableSQLite3 {
  105. return "", errors.New("this Gitea binary was not built with SQLite3 support")
  106. }
  107. if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil {
  108. return "", fmt.Errorf("Failed to create directories: %w", err)
  109. }
  110. journalMode := ""
  111. if Database.SQLiteJournalMode != "" {
  112. journalMode = "&_journal_mode=" + Database.SQLiteJournalMode
  113. }
  114. connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s",
  115. Database.Path, Database.Timeout, journalMode)
  116. default:
  117. return "", fmt.Errorf("unknown database type: %s", Database.Type)
  118. }
  119. return connStr, nil
  120. }
  121. // parsePostgreSQLHostPort parses given input in various forms defined in
  122. // https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
  123. // and returns proper host and port number.
  124. func parsePostgreSQLHostPort(info string) (host, port string) {
  125. if h, p, err := net.SplitHostPort(info); err == nil {
  126. host, port = h, p
  127. } else {
  128. // treat the "info" as "host", if it's an IPv6 address, remove the wrapper
  129. host = info
  130. if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") {
  131. host = host[1 : len(host)-1]
  132. }
  133. }
  134. // set fallback values
  135. if host == "" {
  136. host = "127.0.0.1"
  137. }
  138. if port == "" {
  139. port = "5432"
  140. }
  141. return host, port
  142. }
  143. func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbsslMode string) (connStr string) {
  144. dbName, dbParam, _ := strings.Cut(dbName, "?")
  145. host, port := parsePostgreSQLHostPort(dbHost)
  146. connURL := url.URL{
  147. Scheme: "postgres",
  148. User: url.UserPassword(dbUser, dbPasswd),
  149. Host: net.JoinHostPort(host, port),
  150. Path: dbName,
  151. OmitHost: false,
  152. RawQuery: dbParam,
  153. }
  154. query := connURL.Query()
  155. if strings.HasPrefix(host, "/") { // looks like a unix socket
  156. query.Add("host", host)
  157. connURL.Host = ":" + port
  158. }
  159. query.Set("sslmode", dbsslMode)
  160. connURL.RawQuery = query.Encode()
  161. return connURL.String()
  162. }
  163. // ParseMSSQLHostPort splits the host into host and port
  164. func ParseMSSQLHostPort(info string) (string, string) {
  165. // the default port "0" might be related to MSSQL's dynamic port, maybe it should be double-confirmed in the future
  166. host, port := "127.0.0.1", "0"
  167. if strings.Contains(info, ":") {
  168. host = strings.Split(info, ":")[0]
  169. port = strings.Split(info, ":")[1]
  170. } else if strings.Contains(info, ",") {
  171. host = strings.Split(info, ",")[0]
  172. port = strings.TrimSpace(strings.Split(info, ",")[1])
  173. } else if len(info) > 0 {
  174. host = info
  175. }
  176. if host == "" {
  177. host = "127.0.0.1"
  178. }
  179. if port == "" {
  180. port = "0"
  181. }
  182. return host, port
  183. }
  184. type DatabaseType string
  185. func (t DatabaseType) String() string {
  186. return string(t)
  187. }
  188. func (t DatabaseType) IsSQLite3() bool {
  189. return t == "sqlite3"
  190. }
  191. func (t DatabaseType) IsMySQL() bool {
  192. return t == "mysql"
  193. }
  194. func (t DatabaseType) IsMSSQL() bool {
  195. return t == "mssql"
  196. }
  197. func (t DatabaseType) IsPostgreSQL() bool {
  198. return t == "postgres"
  199. }