[Feature](database): Add Support for SSL Connections and Database URL Configuration (#2540)

* feat(database): add support for SSL connections and database URL configuration

* feat(config): update Redis configuration to use TLS in configurre name instead of SSL

* fix(database): remove default values for DatabaseURL and SSLMode in DatabaseConfig

* chore(.gitignore): add cloudreve built binary to ignore list
pull/2499/merge
charlieJ107 2025-06-23 10:12:20 +01:00 committed by GitHub
parent fec549f5ec
commit 1bd62e8feb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 85 additions and 62 deletions

4
.gitignore vendored
View File

@ -31,4 +31,6 @@ conf/conf.ini
dist/ dist/
data/ data/
tmp/ tmp/
.devcontainer/
cloudreve

View File

@ -329,11 +329,7 @@ func (d *dependency) KV() cache.Driver {
d.kv = cache.NewRedisStore( d.kv = cache.NewRedisStore(
d.Logger(), d.Logger(),
10, 10,
config.Network, config,
config.Server,
config.User,
config.Password,
config.DB,
) )
} else { } else {
d.kv = cache.NewMemoStore(util.DataPath(cache.DefaultCacheFile), d.Logger()) d.kv = cache.NewMemoStore(util.DataPath(cache.DefaultCacheFile), d.Logger())

View File

@ -58,45 +58,56 @@ func NewRawEntClient(l logging.Logger, config conf.ConfigProvider) (*ent.Client,
client *sql.Driver client *sql.Driver
) )
switch confDBType { // Check if the database type is supported.
case conf.SQLiteDB: if confDBType != conf.SQLiteDB && confDBType != conf.MySqlDB && confDBType != conf.PostgresDB {
dbFile := util.RelativePath(dbConfig.DBFile) return nil, fmt.Errorf("unsupported database type: %s", confDBType)
l.Info("Connect to SQLite database %q.", dbFile) }
client, err = sql.Open("sqlite3", util.RelativePath(dbConfig.DBFile)) // If Database connection string provided, use it directly.
case conf.PostgresDB: if dbConfig.DatabaseURL != "" {
l.Info("Connect to Postgres database %q.", dbConfig.Host) l.Info("Connect to database with connection string %q.", dbConfig.DatabaseURL)
client, err = sql.Open("postgres", fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable", client, err = sql.Open(string(confDBType), dbConfig.DatabaseURL)
dbConfig.Host, } else {
dbConfig.User,
dbConfig.Password, switch confDBType {
dbConfig.Name, case conf.SQLiteDB:
dbConfig.Port)) dbFile := util.RelativePath(dbConfig.DBFile)
case conf.MySqlDB, conf.MsSqlDB: l.Info("Connect to SQLite database %q.", dbFile)
l.Info("Connect to MySQL/SQLServer database %q.", dbConfig.Host) client, err = sql.Open("sqlite3", util.RelativePath(dbConfig.DBFile))
var host string case conf.PostgresDB:
if dbConfig.UnixSocket { l.Info("Connect to Postgres database %q.", dbConfig.Host)
host = fmt.Sprintf("unix(%s)", client, err = sql.Open("postgres", fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=allow",
dbConfig.Host)
} else {
host = fmt.Sprintf("(%s:%d)",
dbConfig.Host, dbConfig.Host,
dbConfig.Port) dbConfig.User,
dbConfig.Password,
dbConfig.Name,
dbConfig.Port))
case conf.MySqlDB, conf.MsSqlDB:
l.Info("Connect to MySQL/SQLServer database %q.", dbConfig.Host)
var host string
if dbConfig.UnixSocket {
host = fmt.Sprintf("unix(%s)",
dbConfig.Host)
} else {
host = fmt.Sprintf("(%s:%d)",
dbConfig.Host,
dbConfig.Port)
}
client, err = sql.Open(string(confDBType), fmt.Sprintf("%s:%s@%s/%s?charset=%s&parseTime=True&loc=Local",
dbConfig.User,
dbConfig.Password,
host,
dbConfig.Name,
dbConfig.Charset))
default:
return nil, fmt.Errorf("unsupported database type %q", confDBType)
} }
client, err = sql.Open(string(confDBType), fmt.Sprintf("%s:%s@%s/%s?charset=%s&parseTime=True&loc=Local", if err != nil {
dbConfig.User, return nil, fmt.Errorf("failed to open database: %w", err)
dbConfig.Password, }
host,
dbConfig.Name,
dbConfig.Charset))
default:
return nil, fmt.Errorf("unsupported database type %q", confDBType)
}
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
} }
// Set connection pool // Set connection pool
db := client.DB() db := client.DB()
db.SetMaxIdleConns(50) db.SetMaxIdleConns(50)

18
pkg/cache/redis.go vendored
View File

@ -3,10 +3,12 @@ package cache
import ( import (
"bytes" "bytes"
"encoding/gob" "encoding/gob"
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"strconv" "strconv"
"time" "time"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
"github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/gomodule/redigo/redis" "github.com/gomodule/redigo/redis"
) )
@ -44,7 +46,7 @@ func deserializer(value []byte) (any, error) {
} }
// NewRedisStore 创建新的redis存储 // NewRedisStore 创建新的redis存储
func NewRedisStore(l logging.Logger, size int, network, address, user, password, database string) *RedisStore { func NewRedisStore(l logging.Logger, size int, redisConfig *conf.Redis) *RedisStore {
return &RedisStore{ return &RedisStore{
pool: &redis.Pool{ pool: &redis.Pool{
MaxIdle: size, MaxIdle: size,
@ -54,17 +56,19 @@ func NewRedisStore(l logging.Logger, size int, network, address, user, password,
return err return err
}, },
Dial: func() (redis.Conn, error) { Dial: func() (redis.Conn, error) {
db, err := strconv.Atoi(database) db, err := strconv.Atoi(redisConfig.DB)
if err != nil { if err != nil {
return nil, err return nil, err
} }
c, err := redis.Dial( c, err := redis.Dial(
network, redisConfig.Network,
address, redisConfig.Server,
redis.DialDatabase(db), redis.DialDatabase(db),
redis.DialPassword(password), redis.DialPassword(redisConfig.Password),
redis.DialUsername(user), redis.DialUsername(redisConfig.User),
redis.DialUseTLS(redisConfig.UseTLS),
redis.DialTLSSkipVerify(redisConfig.TLSSkipVerify),
) )
if err != nil { if err != nil {
l.Panic("Failed to create Redis connection: %s", err) l.Panic("Failed to create Redis connection: %s", err)

View File

@ -2,12 +2,13 @@ package conf
import ( import (
"fmt" "fmt"
"os"
"strings"
"github.com/cloudreve/Cloudreve/v4/pkg/logging" "github.com/cloudreve/Cloudreve/v4/pkg/logging"
"github.com/cloudreve/Cloudreve/v4/pkg/util" "github.com/cloudreve/Cloudreve/v4/pkg/util"
"github.com/go-ini/ini" "github.com/go-ini/ini"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"os"
"strings"
) )
const ( const (

View File

@ -24,6 +24,10 @@ type Database struct {
Port int Port int
Charset string Charset string
UnixSocket bool UnixSocket bool
// 允许直接使用DATABASE_URL来配置数据库连接
DatabaseURL string
// SSLMode 允许使用SSL连接数据库, 用户可以在sslmode string中添加证书等配置
SSLMode string
} }
type SysMode string type SysMode string
@ -65,11 +69,13 @@ type Slave struct {
// Redis 配置 // Redis 配置
type Redis struct { type Redis struct {
Network string Network string
Server string Server string
User string User string
Password string Password string
DB string DB string
UseTLS bool
TLSSkipVerify bool
} }
// 跨域配置 // 跨域配置
@ -85,18 +91,21 @@ type Cors struct {
// RedisConfig Redis服务器配置 // RedisConfig Redis服务器配置
var RedisConfig = &Redis{ var RedisConfig = &Redis{
Network: "tcp", Network: "tcp",
Server: "", Server: "",
Password: "", Password: "",
DB: "0", DB: "0",
UseTLS: false,
TLSSkipVerify: true,
} }
// DatabaseConfig 数据库配置 // DatabaseConfig 数据库配置
var DatabaseConfig = &Database{ var DatabaseConfig = &Database{
Charset: "utf8mb4", Charset: "utf8mb4",
DBFile: util.DataPath("cloudreve.db"), DBFile: util.DataPath("cloudreve.db"),
Port: 3306, Port: 3306,
UnixSocket: false, UnixSocket: false,
DatabaseURL: "",
} }
// SystemConfig 系统公用配置 // SystemConfig 系统公用配置