github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/setting/database.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package setting 7 8 import ( 9 "errors" 10 "fmt" 11 "net/url" 12 "os" 13 "path" 14 "path/filepath" 15 "strings" 16 "time" 17 18 "github.com/gitbundle/modules/log" 19 ) 20 21 var ( 22 // SupportedDatabaseTypes includes all XORM supported databases type, sqlite3 maybe added by `database_sqlite3.go` 23 SupportedDatabaseTypes = []string{"mysql", "postgres"} 24 // DatabaseTypeNames contains the friendly names for all database types 25 DatabaseTypeNames = map[string]string{"mysql": "MySQL", "postgres": "PostgreSQL", "mssql": "MSSQL", "sqlite3": "SQLite3"} 26 27 // EnableSQLite3 use SQLite3, set by build flag 28 EnableSQLite3 bool 29 30 // Database holds the database settings 31 Database = struct { 32 Type string 33 Host string 34 Name string 35 User string 36 Passwd string 37 Schema string 38 SSLMode string 39 Path string 40 LogSQL bool 41 Charset string 42 Timeout int // seconds 43 UseSQLite3 bool 44 UseMySQL bool 45 UseMSSQL bool 46 UsePostgreSQL bool 47 DBConnectRetries int 48 DBConnectBackoff time.Duration 49 MaxIdleConns int 50 MaxOpenConns int 51 ConnMaxLifetime time.Duration 52 IterateBufferSize int 53 }{ 54 Timeout: 500, 55 IterateBufferSize: 50, 56 } 57 ) 58 59 // InitDBConfig loads the database settings 60 func InitDBConfig() { 61 sec := Cfg.Section("database") 62 Database.Type = sec.Key("DB_TYPE").String() 63 defaultCharset := "utf8" 64 Database.UseMySQL = false 65 Database.UseSQLite3 = false 66 Database.UsePostgreSQL = false 67 Database.UseMSSQL = false 68 69 switch Database.Type { 70 case "sqlite3": 71 Database.UseSQLite3 = true 72 case "mysql": 73 Database.UseMySQL = true 74 defaultCharset = "utf8mb4" 75 case "postgres": 76 Database.UsePostgreSQL = true 77 case "mssql": 78 Database.UseMSSQL = true 79 } 80 Database.Host = sec.Key("HOST").String() 81 Database.Name = sec.Key("NAME").String() 82 Database.User = sec.Key("USER").String() 83 if len(Database.Passwd) == 0 { 84 Database.Passwd = sec.Key("PASSWD").String() 85 } 86 Database.Schema = sec.Key("SCHEMA").String() 87 Database.SSLMode = sec.Key("SSL_MODE").MustString("disable") 88 Database.Charset = sec.Key("CHARSET").In(defaultCharset, []string{"utf8", "utf8mb4"}) 89 if Database.UseMySQL && defaultCharset != "utf8mb4" { 90 log.Error("Deprecated database mysql charset utf8 support, please use utf8mb4 or convert utf8 to utf8mb4.") 91 } 92 93 Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitbundle.db")) 94 Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500) 95 Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2) 96 if Database.UseMySQL { 97 Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(3 * time.Second) 98 } else { 99 Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0) 100 } 101 Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0) 102 103 Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50) 104 Database.LogSQL = sec.Key("LOG_SQL").MustBool(true) 105 Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) 106 Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) 107 } 108 109 // DBConnStr returns database connection string 110 func DBConnStr() (string, error) { 111 connStr := "" 112 Param := "?" 113 if strings.Contains(Database.Name, Param) { 114 Param = "&" 115 } 116 switch Database.Type { 117 case "mysql": 118 connType := "tcp" 119 if len(Database.Host) > 0 && Database.Host[0] == '/' { // looks like a unix socket 120 connType = "unix" 121 } 122 tls := Database.SSLMode 123 if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL 124 tls = "false" 125 } 126 connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s", 127 Database.User, Database.Passwd, connType, Database.Host, Database.Name, Param, Database.Charset, tls) 128 case "postgres": 129 connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Param, Database.SSLMode) 130 case "mssql": 131 host, port := ParseMSSQLHostPort(Database.Host) 132 connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd) 133 case "sqlite3": 134 if !EnableSQLite3 { 135 return "", errors.New("this binary version does not build support for SQLite3") 136 } 137 if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil { 138 return "", fmt.Errorf("Failed to create directories: %v", err) 139 } 140 connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate", Database.Path, Database.Timeout) 141 default: 142 return "", fmt.Errorf("Unknown database type: %s", Database.Type) 143 } 144 145 return connStr, nil 146 } 147 148 // parsePostgreSQLHostPort parses given input in various forms defined in 149 // https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING 150 // and returns proper host and port number. 151 func parsePostgreSQLHostPort(info string) (string, string) { 152 host, port := "127.0.0.1", "5432" 153 if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") { 154 idx := strings.LastIndex(info, ":") 155 host = info[:idx] 156 port = info[idx+1:] 157 } else if len(info) > 0 { 158 host = info 159 } 160 return host, port 161 } 162 163 func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) { 164 host, port := parsePostgreSQLHostPort(dbHost) 165 if host[0] == '/' { // looks like a unix socket 166 connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s", 167 url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbsslMode, host) 168 } else { 169 connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s", 170 url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbsslMode) 171 } 172 return 173 } 174 175 // ParseMSSQLHostPort splits the host into host and port 176 func ParseMSSQLHostPort(info string) (string, string) { 177 host, port := "127.0.0.1", "0" 178 if strings.Contains(info, ":") { 179 host = strings.Split(info, ":")[0] 180 port = strings.Split(info, ":")[1] 181 } else if strings.Contains(info, ",") { 182 host = strings.Split(info, ",")[0] 183 port = strings.TrimSpace(strings.Split(info, ",")[1]) 184 } else if len(info) > 0 { 185 host = info 186 } 187 return host, port 188 }